Monday, 19 February 2018

Best way to use @OneToOne in Hibernate

Creating a proper hibernate entity relationship design is one of the difficult task of projects. There are multiple factors that you need to consider while making relationships in entities. You must have proper domain or business knowledge of your project to perform these tasks.

Below factors affects the execution of relationship :
  • Object Fetching strategies
  • Lazy-Eager fetching
  • Performance tuning
  • Making the right table as Owner of relationship
  • Unidirectional or Bidirectional relationship

You need to consider all these factors while creating hibernate entity relationships.

In this blog I will be talking about @OneToOne mapping in Hibernate. I will be explaining two different examples of @OneToOne to understand its lazy feature.

Case 1 : Worker - WorkerProfile relation
Case 2 : Manager - ManagerProfile relation

Lets start with Case 1 :
       
@Entity
public class Worker {
    @Id
    private Long id;

    private String name;

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "profile_id")
    private WorkerProfile profile;

    //getters and setters
}

@Entity
public class WorkerProfile {
    @Id
    @GeneratedValue
    private Long id;

    private String phone;

    //getters and setters
}

       
desc Worker
Name       Null     Type               
---------- -------- ------------------ 
ID         NOT NULL NUMBER(19)         
NAME                VARCHAR2(255 CHAR) 
PROFILE_ID          NUMBER(19)   


desc WorkerProfile
Name  Null     Type               
----- -------- ------------------ 
ID    NOT NULL NUMBER(19)         
PHONE          VARCHAR2(255 CHAR) 
Here one worker has one worker profile and workerProfile id is saved in worker table as foreign key. So here Worker is owner table of relationship.
Now lets check if lazy loading works here or not. Lets consider that our fetching strategy would be : Worker -> WorkerProfile  i.e. get WorkerProfile by Worker


       
Worker worker = (Worker) session.get(Worker.class, 1l);
WorkerProfile profile = worker.getProfile();

Below is the Select query fired for above code (Lazy loading working fine here)
       
Hibernate: select worker0_.id as id1_31_0_, worker0_.name as name2_31_0_, 
worker0_.profile_id as profile_id3_31_0_ from Worker worker0_ where worker0_.id=?

Note that Select query for WorkerProfile will be fired when you call :

       
String phone = profile.getPhone();

Now lets consider Case 2 :


       
@Entity
public class Manager {
    @Id
    private Long id;

    private String name;

    @OneToOne(fetch = FetchType.LAZY, mappedBy = "manager")
    private ManagerProfile profile;
 
    //getters and setters
}

@Entity
public class ManagerProfile {
    @Id
    @GeneratedValue
    private Long id;

    private String phone;

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "manager_id")
    private Manager manager;
 
    //getters and setters
}


       
desc Manager
Name Null     Type               
---- -------- ------------------ 
ID   NOT NULL NUMBER(19)         
NAME          VARCHAR2(255 CHAR) 


desc ManagerProfile
Name       Null     Type               
---------- -------- ------------------ 
ID         NOT NULL NUMBER(19)         
PHONE               VARCHAR2(255 CHAR) 
MANAGER_ID          NUMBER(19)  
Here one Manager has one Manager profile and Manager id is saved in ManagerProfile table as foreign key.
So here ManagerProfile is owner table of relationship which is just opposite of Case 1 example.

Our fetching strategy would be similar to Case 1 scenario :
Manager -> ManagerProfile  i.e. get ManagerProfile by Manager




       
Manager manager = (Manager) session.get(Manager.class, 1l);

Below is the Select query fired for above code (Lazy loading not working here)
       
Hibernate: select manager0_.id as id1_7_0_, manager0_.name as name2_7_0_ from 
 Manager manager0_ where manager0_.id=?

Hibernate: select managerpro0_.id as id1_8_0_, managerpro0_.manager_id as 
 manager_id3_8_0_, managerpro0_.phone as phone2_8_0_ from ManagerProfile 
 managerpro0_ where managerpro0_.manager_id=? 

Note that, 2 SQL Select queries are fired with just loading Manager entity. Basically lazy loading is not working here.

Conclusion:

So what is root cause of not working Lazy loading in Case 2. Our fetching strategy should match our conditions of owner table. In case 1 , Worker is owner of relationship and fetching strategy is  Worker -> WorkerProfile  ,i.e. get WorkerProfile by Worker.
So here hibernate Lazy loading is working fine.

In case 2 , ManagerProfile is owner of relationship and fetching strategy is Manager -> ManagerProfile , i.e. get ManagerProfile by Manager.
Here hibernate Lazy loading doesn't work.

Note :
For every managed entity, the Persistence Context requires both the entity type and the identifier, so the child identifier must be known when loading the parent entity, and the only way to find the associated ManagerProfile primary key is to execute a secondary query.

Both above mentioned cases are unidirectional. To make them bidirectional, either you can apply a @OneToOne or @ManyToOne annotation or you can add a Hql named query to fetch other side of relationship.

You can also improve case 2 by doing byte code enhancement but
that should be implemented only after proper framework knowledge and strenuous monitoring.

Code available on GitHub


5 comments:

  1. Great Information. Edge cases such as this always causes problem.

    ReplyDelete
  2. Very well explained corner cases of hibernate

    ReplyDelete
  3. please write more blogs on hibernate

    ReplyDelete
  4. Thanks for sharing this informative post. It's really very helpful by the way. If anyone looking for best Ms Office training institute in Delhi Contact Here-+91-9311002620 Or Visit our website https://www.htsindia.com/Courses/microsoft-courses/ms-office-course

    ReplyDelete