Lazy loading not happening in case of One to One relationship sharing Primary Key

There was issue developer was facing, where the relationship between 2 entity was defined to be lazy loaded. Base on the hibernate show sql logs, it was observed that the entity was being eager loaded.

It fired my curiosity and i got involved. To explain the exact scenario i use example from “Hibernate make easy by Cameron Wallace McKenzie”.

The relationship between the entity was one to one mapping. It was parent child relationship. So the parent entity was Exam, defined something like this –>

@Entity
@Table(name = “exam”)
public class Exam {

……….

@OneToOne(cascade=CascadeType.ALL, fetch=FetchType.LAZY)
@PrimaryKeyJoinColumn
public ExamDetail getDetail() {
return detail;

……….

}

There was exam details child entity defined as –>

@Entity
@Table(name = “exam_detail”)
public class ExamDetail {

……….

@OneToOne(mappedBy = “detail”, cascade = CascadeType.ALL)
public Exam getExam() {
return exam;
}

……….

}

Now in such case when the exam object is loaded by the hibernate, along with it exam detail object also get loaded.

Session session = HibernateUtil.beginTransaction();
Exam exam = (Exam) session.get(Exam.class, 1);

For the above following is output in the log.

Hibernate: select exam0_.id as id0_0_, exam0_.shortName as shortName0_0_ from exam exam0_ where exam0_.id=?
Hibernate: select examdetail0_.id as id1_1_, examdetail0_.fullName as fullName1_1_, examdetail0_.numberOfQuestions as numberOf3_1_1_, examdetail0_.passingPercentage as passingP4_1_1_, exam1_.id as id0_0_, exam1_.shortName as shortName0_0_ from exam_detail examdetail0_ left outer join exam exam1_ on examdetail0_.id=exam1_.id where examdetail0_.id=?

This may sound wrong since we have define exam detail to be lazy loaded. But once we understand how hibernate operates, this become obvious and clear. Hibernate typically does lazy loading using proxy objects. So it creates proxy object and loads the proxy with actual object when we first reference the attribute or variable or property of that object.  Thats lazy loading, loading when we actual use the object.

In above parent children relationship, the parent entity can exist without child entity which means, the exam can exist without exam details records. So when hibernate loads exam entity it does not know whether to populate the exam detail entity within exam, with proxy or null. Hibernate can only make that decision once it loads exam details.

Solutions 1

One work around would be to define the relationship as mandatory. Which means that each exam records should have exam details record. This makes things straight forward for hibernate, it always loads the exam details entity within exam as proxy. because exam details can never be null.

We can change the relationship between exam and exam details to be mandatory by changing the notation as follows

@OneToOne(cascade=CascadeType.ALL, fetch=FetchType.LAZY, optional=false)
@PrimaryKeyJoinColumn
public ExamDetail getDetail() {

The Code –>

Session session = HibernateUtil.beginTransaction();
Exam exam = (Exam) session.get(Exam.class, 1);
System.out.println(exam.getShortName());
exam.getDetail().getFullName();
session.getTransaction().commit();

Gives following out –>

Hibernate: select exam0_.id as id0_0_, exam0_.shortName as shortName0_0_ from exam exam0_ where exam0_.id=?
SCJA
Hibernate: select examdetail0_.id as id1_1_, examdetail0_.fullName as fullName1_1_, examdetail0_.numberOfQuestions as numberOf3_1_1_, examdetail0_.passingPercentage as passingP4_1_1_, exam1_.id as id0_0_, exam1_.shortName as shortName0_0_ from exam_detail examdetail0_ left outer join exam exam1_ on examdetail0_.id=exam1_.id where examdetail0_.id=?

Solution 2

Other solution is not sharing the Primary between the parent and child entities and define a separate column in the exam table to link the parent and child table. In this case the exam table looks something like this.

id int(11)
shortName varchar(255)
detail_id int(11)

In this case hibernate can look at detail_id column to figure out, if the exam detail entity within exam entity, should be populated with null or proxy. 

If detail_id is null then exam detail entity within exam object will be null. Conversely if detail_id is not null then exam detail entity within exam object will be proxy.

The annotation on the java object would look like

@OneToOne(cascade=CascadeType.ALL, fetch=FetchType.LAZY)
@JoinColumn(name = “detail_id”)
public ExamDetail getDetail() {

The code –>

Session session = HibernateUtil.beginTransaction();
Exam exam = (Exam) session.get(Exam.class, 1);
System.out.println(exam.getShortName());
exam.getDetail().getFullName();
session.getTransaction().commit();

Give following output –>

Hibernate: select exam0_.id as id0_0_, exam0_.detail_id as detail3_0_0_, exam0_.shortName as shortName0_0_ from exam exam0_ where exam0_.id=?
SCJA
Hibernate: select examdetail0_.id as id1_1_, examdetail0_.fullName as fullName1_1_, examdetail0_.numberOfQuestions as numberOf3_1_1_, examdetail0_.passingPercentage as passingP4_1_1_, exam1_.id as id0_0_, exam1_.detail_id as detail3_0_0_, exam1_.shortName as shortName0_0_ from exam_detail examdetail0_ left outer join exam exam1_ on examdetail0_.id=exam1_.detail_id where examdetail0_.id=?
Hibernate: select exam0_.id as id0_0_, exam0_.detail_id as detail3_0_0_, exam0_.shortName as shortName0_0_ from exam exam0_ where exam0_.detail_id=?

Based on the context solution 1 or 2 may work for you. In some case neither solutions will work, and you will have live with eager loading, but aleast you know why it is eager loading 🙂

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s