Persistence top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

1. Overview

Working with Hibernate, we might have encountered an error that says: org.hibernate.LazyInitializationException : could not initialize proxy – no Session.

In this quick tutorial, we’ll take a closer look at the root cause of the error and learn how to avoid it.

2 Understanding the Error

Access to a lazy-loaded object outside of the context of an open Hibernate session will result in this exception.

It's important to understand what is Session, Lazy Initialisation, and Proxy Object and how they come together in the Hibernate framework.

  • Session is a persistence context that represents a conversation between an application and the database
  • Lazy Loading means that the object will not be loaded to the Session context until it is accessed in code.
  • Hibernate creates a dynamic Proxy Object subclass that will hit the database only when we first use the object.

This error means that we try to fetch a lazy-loaded object from the database by using a proxy object, but the Hibernate session is already closed.

3. Example for LazyInitializationException

Let's see the exception in a concrete scenario.

We want to create a simple User object with associated roles. Let's use JUnit to demonstrate the LazyInitializationException error.

3.1. Hibernate Utility Class

First, let's define a HibernateUtil class to create a SessionFactory with configuration.

We’ll use the in-memory HSQLDB database.

3.2. Entities

Here's our User entity :

@Entity
@Table(name = "user")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private int id;

    @Column(name = "first_name")
    private String firstName;
    
    @Column(name = "last_name")
    private String lastName;
    
    @OneToMany
    private Set<Role> roles;
    
}

And the associated Role entity :

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

    @Column(name = "role_name")
    private String roleName;
}

As we can see, there is a one-to-many relationship between User and Role.

3.3. Creating User with Roles

Next, let's create two Role objects :

Role admin = new Role("Admin");
Role dba = new Role("DBA");

Then, we create a User with the roles :

User user = new User("Bob", "Smith");
user.addRole(admin);
user.addRole(dba);

Finally, we can open a session and persist the objects :

Session session = sessionFactory.openSession();
session.beginTransaction();
user.getRoles().forEach(role -> session.save(role));
session.save(user);
session.getTransaction().commit();
session.close();

3.4. Fetching Roles

In the first scenario, we’ll see how to fetch user roles in a proper way :

@Test
public void whenAccessUserRolesInsideSession_thenSuccess() {

    User detachedUser = createUserWithRoles();

    Session session = sessionFactory.openSession();
    session.beginTransaction();
		
    User persistentUser = session.find(User.class, detachedUser.getId());
		
    Assert.assertEquals(2, persistentUser.getRoles().size());
		
    session.getTransaction().commit();
    session.close();
}

Here, we access the object inside the session, therefore there's no error.

3.5. Fetching Roles Failure

In the second scenario, we’ll call a getRoles method outside the session :

@Test
public void whenAccessUserRolesOutsideSession_thenThrownException() {
		
    User detachedUser = createUserWithRoles();

    Session session = sessionFactory.openSession();
    session.beginTransaction();
		
    User persistentUser = session.find(User.class, detachedUser.getId());
		
    session.getTransaction().commit();
    session.close();

    thrown.expect(LazyInitializationException.class);
    System.out.println(persistentUser.getRoles().size());
}

In that case, we try to access the roles after the session was closed, and, as a result, the code throws a LazyInitializationException.

4. How to Avoid the Error

Let's take a look at four different solutions to overcome the error.

4.1. Open Session in Upper Layer

The best practice is to open a session in the persistence layer, for example using the DAO Pattern.

We can open the session in the upper layers to access the associated objects in a safe manner. For example, we can open the session in the View layer.

As a result, we’ll see an increase in response time, which will affect the performance of the application.

This solution is an anti-pattern in terms of the Separation of Concerns principle. In addition, it can cause data integrity violations and long-running transactions.

4.2. Turning on enable_lazy_load_no_trans Property

This Hibernate property is used to declare a global policy for lazy-loaded object fetching.

By default, this property is false. Turning it on means that each access to an associated lazy-loaded entity will be wrapped in new session running in a new transaction:

<property name="hibernate.enable_lazy_load_no_trans" value="true"/>

Using this property to avoid LazyInitializationException error is not recommended since it will slow down the performance of our application. This is because we'll end up with an n + 1 problem. Simply put, that means one SELECT for the User and N additional SELECTs to fetch the roles of each user.

This approach is not efficient and also considered an anti-pattern.

4.3. Using  FetchType.EAGER Strategy

We can use this strategy along with a @OneToMany annotation, for example :

@OneToMany(fetch = FetchType.EAGER)
@JoinColumn(name = "user_id")
private Set<Role> roles;

This is a kind of compromised solution for a particular usage when we need to fetch the associated collection for most of our use cases.

So it's much easier to declare the EAGER fetch type instead of explicitly fetching the collection for most of the different business flows.

4.4. Using Join Fetching

We can use a JOIN FETCH directive in JPQL to fetch the associated collection on-demand, for example :

SELECT u FROM User u JOIN FETCH u.roles

Or we can use the Hibernate Criteria API :

Criteria criteria = session.createCriteria(User.class);
criteria.setFetchMode("roles", FetchMode.EAGER);

Here, we specify the associated collection that should be fetched from the database along with the User object on the same round trip. Using this query improves the efficiency of iteration since it eliminates the need for retrieving the associated objects separately.

This is the most efficient and fine-grained solution to avoid the LazyInitializationException error.

5. Conclusion

In this article, we saw how to deal with the org.hibernate.LazyInitializationException : could not initialize proxy – no Session error.

We explored different approaches along with performance issues. It's important to use a simple and efficient solution to avoid affecting performance.

Finally, We saw how the join-fetching approach is a good way to avoid the error.

As always, the code is available over on GitHub.

Persistence bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE
Comments are closed on this article!