To maintain such types of bi-directional relationships, Hibernate provides a flexibility to define which side will be responsible for maintaining the association. The ‘inverse’ attribute helps Hibernate in deciding this. Read this attribute as “I am not responsible for maintaining this association”. By default, the value of this attribute is false, which means the association is maintained from both sides. Making it true, instructs Hibernate that this side (entity) is not responsible for persisting the association and hence it generates SQL accordingly.
Let’s try to better understand this using a many-to-many bi-directional relationship. Consider the relationship between student and course. Assume that one student can opt many courses and one course can be opted by many students. Also, to make it a bi-directional relationship, we designed our domain model accordingly so that the Student class has a Set of Course objects while Course Class also has set of Student objects. Thus by knowing a student, you can know all courses opted by him/her (by iterating on courses set) while knowing a course, you can know all students opted for it (by iterating on student set).
For this example our DB model will look like this:
Table student [int id, String fName, String lName] Table course [int id, String name] Table student_course [int student_id, int course_id]
Our Student and Course domain classes would look like this:
public class Student { private int id; private String fName; private String lName; private Set courses; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getFName() { return fName; } public void setFName(String name) { this.fName = name; } public String getLName() { return lName; } public void setLName(String name) { this.lName = name; } public Set getCourses() { return courses; } public void setCourses(Set courses) { this.courses = courses; } } public class Course { private int id; private String name; private Set students; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Set getStudents() { return students; } public void setStudents(Set students) { this.students = students; } }
And finally our mapping files will look like this:
<hibernate-mapping> <class lazy="true" schema="example" table="student" name="com.example.hibernate.bean.Student"> <id name="id"> <generator> <param name="sequence">example.student_id_seq</param> </generator> </id> <property name="fName" type="java.lang.String"></property> <property name="lName" type="java.lang.String"></property> <set schema="example" table="student_course" name="courses" inverse="false"> <key column="student_id"></key> <many-to-many column="course_id" unique="false"></many-to-many> </set> </class> </hibernate-mapping> <hibernate-mapping> <class lazy="true" schema="example" table="course" name="com.example.hibernate.bean.Course"> <id name="id"> <generator> <param name="sequence">example.course_id_seq</param> </generator> </id> <property name="name" type="java.lang.String"></property> <set schema="example" table="student_course" name="students" inverse="true"> <key column="course_id"></key> <many-to-many column="student_id"></many-ti-many> </set> </class> </hibernate-mapping>
Let’s see this by running some code. Assume that we add 3 new courses using following code.
public void addCourses(){ Session session = null; try { session = HibertnateUtil.getSessionFactory().openSession(); Transaction tx = session.beginTransaction(); for (int i=1; i<=3; i++) { Course course = new Course(); String name = "Course " + i; course.setName(name); session.persist(course); } tx.commit(); } catch (Exception e) {e.printStackTrace();} finally {if (session!= null) {session.close();} }
* Note that I have avoided the batch processing and other optimizations for simplicity.
Here HibernateUtil is a simple utility class to create SessionFactory.
public class HibertnateUtil { private static SessionFactory factory; public static SessionFactory getSessionFactory() { if (factory == null) { Configuration cfg = new Configuration().configure(); factory = cfg.buildSessionFactory(); } return factory; } }
The SQL output will be:
Hibernate: select nextval ('example.course_id_seq') Hibernate: select nextval ('example.course_id_seq') Hibernate: select nextval ('example.course_id_seq') Hibernate: insert into example.course (name, available, id) values (?, ?, ?) Hibernate: insert into example.course (name, available, id) values (?, ?, ?) Hibernate: insert into example.course (name, available, id) values (?, ?, ?)
Now let’s add 3 students using following code:
public void addStudents(){ Session session = null; try { session = HibertnateUtil.getSessionFactory().openSession(); Transaction tx = session.beginTransaction(); for (int i=1; i<=3; i++) { Student student = new Student(); student.setFName("First Name " + i); student.setLName("Last Name " + i); session.persist(student); } tx.commit(); } catch (Exception e) {e.printStackTrace();} finally {if (session!= null) {session.close();} }
The SQL output will be:
Hibernate: select nextval ('example.student_id_seq') Hibernate: select nextval ('example.student_id_seq') Hibernate: select nextval ('example.student_id_seq') Hibernate: insert into example.student (fName, lName, id) values (?, ?, ?) Hibernate: insert into example.student (fName, lName, id) values (?, ?, ?)
Now we will add courses to students.
Student student = (Student) session.get(Student.class, 1); student.getCourses().add((Course)session.get(Course.class, 1)); student.getCourses().add((Course)session.get(Course.class, 2));
The SQL output will be:
Hibernate: select student0_.id as id0_, student0_.fName as f2_1_0_, student0_.lName as l3_1_0_ from example.student student0_ where student0_.id=? Hibernate: select courses0_.student_id as student1_1_, courses0_.course_id as course2_1_, course1_.id as id0_, course1_.name as name3_0_ from example.student_course courses0_ inner join example.course course1_ on courses0_.course_id=course1_.id where courses0_.student_id=? Hibernate: select courses0_.student_id as student1_1_, courses0_.course_id as course2_1_, course1_.id as id0_, course1_.name as name3_0_ from example.student_course courses0_ inner join example.course course1_ on courses0_.course_id=course1_.id where courses0_.student_id=? Hibernate: insert into example.student_course (student_id, course_id) values (?, ?)
Now let’s try to update the student set in Course entity.
Course course = (Course) session.get(Course.class, 1); course.getStudents().add((Student)session.load(Student.class, 3));
The SQL output will be:
Hibernate: select student0_.id as id0_, student0_.fName as f2_1_0_, student0_.lName as l3_1_0_, from example.student student0_ where student0_.id=? Hibernate: select courses0_.student_id as student1_1_, courses0_.course_id as course2_1_, course1_.id as id0_, course1_.name as name3_0_ from example.student_course courses0_ inner join example.course course1_ on courses0_.course_id=course1_.id where courses0_.student_id=?
Thus, the inverse attribute is used as a simple mechanism by hibernate to know which side of the association is responsible for maintaining it.