Persistence top

Get started with Spring Data JPA through the reference Learn Spring Data JPA course:

>> 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 Session, Lazy Initialisation, and Proxy Object are, 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 won't 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 occurs when 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. We'll 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's a one-to-many relationship between User and Role.

3.3. Creating User With Roles

Then we'll create two Role objects:

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

We'll also 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 this 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, and therefore, there's no error.

3.5. Fetching Roles Failure

In this 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 this case, we try to access the roles after the session is closed, and as a result, the code throws a LazyInitializationException.

4. How to Avoid the Error

Now 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, using the DAO Pattern, for example.

We can open the session in the upper layers to access the associated objects in a safe manner. For instance, 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 a new session running in a new transaction:

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

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

This approach isn't efficient and is also considered an anti-pattern.

4.3. Using  FetchType.EAGER Strategy

We can use this strategy along with the @OneToMany annotation:

@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.

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 also use a JOIN FETCH directive in JPQL to fetch the associated collection on-demand:

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 learned how to deal with the org.hibernate.LazyInitializationException : could not initialize proxy – no Session error.

We explored different approaches, along with performance issues. We also discussed the importance of using a simple and efficient solution in order to avoid affecting performance.

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

As always, the code is available over on GitHub.

Persistence bottom
Get started with Spring Data JPA through the reference Learn Spring Data JPA course: >> CHECK OUT THE COURSE
Persistence footer banner
Comments are closed on this article!