JPA/Hibernate Bidirectional Lazy Loading Done Right

Mohammad Anik Islam
Monstar Lab Bangladesh Engineering
5 min readAug 19, 2019

Hibernate is one of the most popular ORM tools used by JAVA developers. JPA along with Hibernate can do wonders in managing entity relations. One crucial database optimization technique is to lazily load relations in order to make less queries to the database. In this article, I would like to discuss how we can achieve bidirectional lazy loading for different kind of entity relations like one to one, one to many and many to one.

Entity Relations

Let us consider the following entity relations for our discussion.

Entity Relations

As you can see, users entity has a one to one relation to student_profiles and a one to many relation to roles entity. On the other hand, student_profiles entity has a many to one relation to cities entity.

Defining entity classes and repositories

We can represent the relationship among these entities in JAVA classes in the following way.

For users entity,

@Entity
@Table(name = "users")
public class User { @Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;

private String email;
@OneToOne(mappedBy = "user", fetch = FetchType.LAZY,
cascade = CascadeType.ALL)
private StudentProfile studentProfile;

@OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<Role> roles;
//Setters and Getters
}

For student_profiles entity,

@Entity
@Table(name = "student_profiles")
@Data
public class StudentProfile {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@OneToOne(fetch = FetchType.LAZY)
private User user;
@ManyToOne(fetch = FetchType.LAZY)
private City city;
//Setters and Getters
}

For roles entity,

@Entity
@Table(name = "roles")
public class Role{

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name; @ManyToOne(fetch = FetchType.LAZY)
private User user;
//Setters and Getters
}

For cities entity,

@Entity
@Table(name = "cities")
public class City { @Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name; @OneToMany(mappedBy = "city", cascade = CascadeType.ALL)
private List<User> users;

//Setters and Getters
}

Notice that, we have instructed hibernate to lazily load all the associated entities in the annotation. For simplicity, we are defining the repository layer using Spring Data JPA. You can use any implementation as you like.

For user repository,

public interface UserRepository extends JpaRepository<User, Integer> {}

For student profile repository,

public interface StudentProfileRepository extends JpaRepository<StudentProfile, Integer> {}

Now, let us try to fetch a user with id 1 using the following code,

User user = userRepository.getOne(1);

Which generates the following queries,

Hibernate:
select
user0_.id as id1_29_0_,
user0_.email as email2_29_0_
from
users user0_
where
user0_.id=?
Hibernate:
select
studentpro0_.id as id1_28_0_
from
student_profiles studentpro0_
where
studentpro0_.user_id=?

Notice that, hibernate is fetching student profile entity along with user entity although we have instructed hibernate to lazily load entities. The problem is, by default only one to many relation is lazily loaded in hibernate. Both one to many and many to one relations are loader eagerly. Even if we instruct hibernate to lazily load them, hibernate disregards that hint.

To overcome this problem, we need to enable bytecode enhancement in the project.

Bytecode Enhancement

In our case, we are using the bytecode enhancement plugin that enhances the bytecode of entity classes and allows us to utilize No-proxy lazy fetching strategy. We can define the plugin in pom.xml file in the following way,

<build>
<plugins>
<plugin>
<groupId>org.hibernate.orm.tooling</groupId
<artifactId>hibernate-enhance-maven-plugin</artifactId>
<version>5.4.2.Final</version>
<executions>
<execution>
<configuration>
<enableLazyInitialization>true</enableLazyInitialization> </configuration>
<goals>
<goal>enhance</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

Enabling no proxy lazy associations

Now we need to add @LazyToOne annotation in entity classes to let hibernate know that we want to enable no proxy lazy fetching for associated entities.

For users entity,

@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;

private String email;
@OneToOne(mappedBy = "user", fetch = FetchType.LAZY,
cascade = CascadeType.ALL)
@LazyToOne(LazyToOneOption.NO_PROXY)
private StudentProfile studentProfile;

@OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<Role> roles;
//Setters and Getters
}

Now if we try to fetch user with id 1 using the code,

User user = userRepository.getOne(1);

We can see lazy loading is working properly and it is fetching only user entity generating the following query only,

Hibernate:
select
user0_.id as id1_29_0_,
user0_.email as email2_29_0_
from
users user0_
where
user0_.id=?

Lazy loading grouping

Now, let us focus on another problem. Let’s try to fetch a student profile with id 1 using the following code.

StudentProfile studentProfile = studentProfileRepository.getOne(1);

We can see that it is working properly and fetching just the student profile.

Hibernate:
select
studentpro0_.id as id1_28_0_
from
student_profiles studentpro0_
where
studentpro0_.id=?

However, now if we try to get city object from studentProfile like so,

City city = studentProfile.getCity();

It fetches both the city and user entity and the following query is generated,

Hibernate:
select
city0_.id as id1_5_0_,
city0_.name as name2_5_0_
from
cities city0_
where
city0_.id=?
Hibernate:
select
user0_.id as id1_29_0_,
user0_.email as email2_29_0_
from
users user0_
where
user0_.id=?

Here, the issue is, by default all the lazy properties of an entity class belongs to a group named DEFAULT. And fetching any property of the DEFAULT group fetches others as well. To solve this problem, we need to define groups that we wish to fetch individually using the @LazyGroup annotation.

In our case, we want the nested objects to be fetched individually. So, we are annotating both city and user with @LazyGroup annotation as below.

For student_profiles entity,

@Entity
@Table(name = "student_profiles")
@Data
public class StudentProfile {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@OneToOne(fetch = FetchType.LAZY)
@LazyToOne(LazyToOneOption.NO_PROXY)
@LazyGroup("user")
private User user;
@ManyToOne(fetch = FetchType.LAZY)
@LazyToOne(LazyToOneOption.NO_PROXY)
@LazyGroup("city")
private City city;
//Setters and Getters
}

Now, if we run the statements again,

StudentProfile studentProfile = studentProfileRepository.getOne(1);
City city = studentProfile.getCity();

We will see that, only city is being fetched and user is not being fetched anymore.

Hibernate:
select
studentpro0_.id as id1_28_0_
from
student_profiles studentpro0_
where
studentpro0_.id=?
Hibernate:
select
city0_.id as id1_5_0_,
city0_.name as name2_5_0_
from
cities city0_
where
city0_.id=?

However, there is this issue with bytecode enhancement that foreign key column is not loaded with the parent entity. So, an additional query is generated to get the foreign key value first.

If you have made it this far, thanks a lot for your patience. Hopefully, this will help if you are trying to find a solution for hibernate lazy loading issue.

Please let me know if there is more to add. Feel free to connect.

Check out other articles from our engineering team:
https://medium.com/monstar-lab-bangladesh-engineering

Visit our website to learn more about us:
www.monstar-lab.co.bd

--

--