Thursday, September 2, 2010

Hibernate inverse attribute - Explained Again

After exploring many articles and notes on ‘inverse’ attribute used in Hibernate, I realized that, although the attribute is simple to understand, the explanation, most of the times, is more complicated and ambiguous. So, I decided to give one more try to put simple words and examples to explain it.

Hibernate supports bidirectional relationships in which the association between entities can be traversed in both directions. Consider the example of Department and Employee. It can be designed as one-to-many bidirectional relationship with a collection of employees in department entity and reference of department entity in employee. Thus one can know all employees within a particular department (by iterating through the employee collection) and the department to which an employee belongs to from employee entity. (I have assumed that one employee can belong to one department only).










To maintain such types of bidirectional 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 SQLs accordingly.

Let’s try to better understand this using a many-to-many bidirectional 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 bidirectional 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 class="sequence">
<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 class="com.example.hibernate.bean.Course" 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 class="sequence">
<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 class="com.example.hibernate.bean.Student" column="student_id"></many-ti-many>
</set>
</class>
</hibernate-mapping>


If you have a closer look at the mappings, you will notice that the inverse attribute inside <set> has different values in student and course mapping. It has false value in case of student entity mapping, which instructs hibernate that Student entity is responsible for association mapping or, in other words, adding/deleting or updating courses for a student will persist the relationship information in student_course table. On the other hand, since this attribute has a true value in course mapping, it instructs that we will not add/delete or update student collection for a particular course and even if we do that hibernate won’t persist that information. In more simpler terms, if we add a student in the students set of course, this addition will not be persisted by hibernate as hibernate will not generate SQL to persist this information.
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 (?, ?)


The last SQL above indicates that hibernate persisted the mapping information between student and courses in student_course table as soon as course list in student is updated. Now change the value of inverse attribute to true in student maping file and try to repeat the above code (change the student or course ID). In that case the last SQL will not be executed by hibernate and hence no row will be added to student_course table.

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=?
Note that there is no SQL to update the student_course table because the inverse attribute has true value for Course mapping which means it’s not course which is responsible for maintaining the association.

Thus, the inverse attribute is used as a simple mechanism by hibernate to know which side of the association is responsible for maintaining it.

3 comments:

JOBS said...

Nice explanation ...thanks a lot

Pratap Chandra Mohanty said...

Very good explanation...good job

Ashish Pathak said...

thanks for nice article