June 29, 2022

Fix LazyInitializationException: could not initialize proxy Error

While working with Hibernate you might have encountered the following error-

org.hibernate.LazyInitializationException could not initialize proxy - no Session

In this tutorial we’ll see why this error comes up and what is the best way to fix it.

Why LazyInitializationException : could not initialize proxy

You may encounter this error while having JPA mappings and trying to access associated object (child object) from the parent object. As you must be knowing that as an optimization associations are fetched lazily. In fact in JPA by default OneToMany and ManyToMany are LAZY and associated objects are loaded into session only when explicitly accessed in code. That should give you the first clue why LazyInitializationException is thrown.

Taking this explanation further to how these associated objects are actually loaded. Hibernate creates a proxy object for the child object and populate the parent object with this proxy. This proxy has a reference back to the Hibernate session. Whenever a method is called on the proxy object, it checks to see if the proxy has been initialized or not. If not initialized then it uses the Hibernate session to create a new query to the database and populates the object. That pertains to the other part of the error where it says "could not initialize proxy - no Session".

With this explanation it should be clear to you that this error is coming up because you are trying to access an object that has to be lazily loaded. When you actually try to access that object by that time session is closed so the proxy object can not to be initialized to get the real object.

Spring JPA example to see LazyInitializationException

In the example we’ll have two Entities User and Accounts with a relationship that User can have more than one Account. That means we’ll be using One-to-Many mapping.

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

  @Column(name="name")
  private String userName;

  @OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
  private Set accounts = new HashSet<>();
// getters and setters
}
Account entity
@Entity
@Table(name="accounts")
public class Account {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private int id;

  @Column(name="acct_number", unique=true)
  private int accountNumber;

  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "user_id", nullable = false)
  private User user;
  // Getters and Setters
}

Then we have UserService interface and corresponding implementation class and also UserDAO interface and corresponding implementation class.

UserService interface
public interface UserService {
    public void addUser(User user);
    public List<User> findAllUsers();
    public User findUserById(int id);
    public void deleteUserById(int id);
}
UserServiceImpl class
@Service
@Transactional(readOnly = true)
public class UserServiceImpl implements UserService {
  @Autowired
  private UserDAO dao;
  
  @Override
  @Transactional
  public void addUser(User user) {
    dao.addUser(user);
  }

  @Override
  public List<User> findAllUsers() {
    return dao.findAllUsers();
  }

  @Override
  public User findUserById(int id) {
    User user = dao.findUserById(id);
    return user;
  }

  @Override
  @Transactional
  public void deleteUserById(int id) {
    dao.deleteUserById(id);
  }
}
UserDAO interface
public interface UserDAO {
  public void addUser(User user);
  public List<User> findAllUsers();
  public User findUserById(int id);
  public void deleteUserById(int id);
}
UserDAOImpl class
@Repository
public class UserDAOImpl implements UserDAO {
  @PersistenceContext
  private EntityManager em;
  
  @Override
  public void addUser(User user) {
    em.persist(user);
  }

  @Override
  public List<User> findAllUsers() {
    
    List<User> users = em.createQuery("Select u from User u", User.class)
                         .getResultList();
    return users;
  }

  @Override
  public User findUserById(int id) {
    return em.find(User.class, id);
  }

  @Override
  public void deleteUserById(int id) {
    User user = findUserById(id);
    if(user != null)
      em.remove(user);
  }
}

Here the methods of interest are the findAllUsers() or findUserById() where we can try to access the associated user accounts once the user is fetched. Let’s try to do that.

public class DriverClass {

  public static void main(String[] args) {
    AbstractApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
    UserService userService =  context.getBean("userServiceImpl", UserService.class);
      
    List<User> users = userService.findAllUsers();

    for(User u : users) {
      System.out.println("User ID: " + u.getUserId() 
        + " User Name: " + u.getUserName());
      System.out.println("---------------");
      // Trying to access associated accounts
      for(Account acct : u.getAccounts()) {
        System.out.println(acct.getAccountNumber());
      }
      System.out.println("---------------");
    }
    context.close();
  }
}

On trying to run it you will get the following error.

Exception in thread "main" org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.knpcode.entities.User.accounts, could not initialize proxy - no Session

As already discussed, Hibernate creates a proxy Account object and try to get the real object only when it is accessed in the code. In the code it is this line where actual object has to be fetched.

for(Account acct : u.getAccounts()) 

But the problem is session is already closed in the DAO layer and there is no way to initialize the proxy object now.

Fixing could not initialize proxy - no session error

You may be thinking of keeping the session open for a longer time (opening and closing it in view layer rather than in Service) or to have fetch mode as ‘Eager’ while configuring @OneToMany mapping.

Of course, keeping the session for a longer duration is not a good solution and it will create more problems in terms of transaction handling and slowing down the application.

Using FetchType.EAGER will fix the error but then you are always fetching the associations even when you are not using them. Also the number of additional queries that are executed grows. For example, if I make the change in User entity to include FetchType.EAGER

@OneToMany(mappedBy = "user", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private Set<Account> accounts = new HashSet<>();

Then the queries generated by Hibernate are as given below.

Hibernate: select user0_.id as id1_1_, user0_.name as name2_1_ from user_master user0_ Hibernate: select accounts0_.user_id as user_id3_0_0_, accounts0_.id as id1_0_0_, accounts0_.id as id1_0_1_, accounts0_.acct_number as acct_num2_0_1_, accounts0_.user_id as user_id3_0_1_ from accounts accounts0_ where accounts0_.user_id=? Hibernate: select accounts0_.user_id as user_id3_0_0_, accounts0_.id as id1_0_0_, accounts0_.id as id1_0_1_, accounts0_.acct_number as acct_num2_0_1_, accounts0_.user_id as user_id3_0_1_ from accounts accounts0_ where accounts0_.user_id=? Hibernate: select accounts0_.user_id as user_id3_0_0_, accounts0_.id as id1_0_0_, accounts0_.id as id1_0_1_, accounts0_.acct_number as acct_num2_0_1_, accounts0_.user_id as user_id3_0_1_ from accounts accounts0_ where accounts0_.user_id=? Hibernate: select accounts0_.user_id as user_id3_0_0_, accounts0_.id as id1_0_0_, accounts0_.id as id1_0_1_, accounts0_.acct_number as acct_num2_0_1_, accounts0_.user_id as user_id3_0_1_ from accounts accounts0_ where accounts0_.user_id=? Hibernate: select accounts0_.user_id as user_id3_0_0_, accounts0_.id as id1_0_0_, accounts0_.id as id1_0_1_, accounts0_.acct_number as acct_num2_0_1_, accounts0_.user_id as user_id3_0_1_ from accounts accounts0_ where accounts0_.user_id=? Hibernate: select accounts0_.user_id as user_id3_0_0_, accounts0_.id as id1_0_0_, accounts0_.id as id1_0_1_, accounts0_.acct_number as acct_num2_0_1_, accounts0_.user_id as user_id3_0_1_ from accounts accounts0_ where accounts0_.user_id=? Hibernate: select accounts0_.user_id as user_id3_0_0_, accounts0_.id as id1_0_0_, accounts0_.id as id1_0_1_, accounts0_.acct_number as acct_num2_0_1_, accounts0_.user_id as user_id3_0_1_ from accounts accounts0_ where accounts0_.user_id=? Hibernate: select accounts0_.user_id as user_id3_0_0_, accounts0_.id as id1_0_0_, accounts0_.id as id1_0_1_, accounts0_.acct_number as acct_num2_0_1_, accounts0_.user_id as user_id3_0_1_ from accounts accounts0_ where accounts0_.user_id=? Hibernate: select accounts0_.user_id as user_id3_0_0_, accounts0_.id as id1_0_0_, accounts0_.id as id1_0_1_, accounts0_.acct_number as acct_num2_0_1_, accounts0_.user_id as user_id3_0_1_ from accounts accounts0_ where accounts0_.user_id=?

Using JOIN FETCH clause

Best way to fix this error is to use JOIN FETCH clause which not only joins the entities but also fetches the associated entities. Here is the changed findAllUsers() method in UserDAOImpl where JOIN FETCH is now used in the JPQL.

@Override
public List<User> findAllUsers() {
  List<User> users = em.createQuery("Select distinct u from User u join fetch u.accounts", User.class)
                 .getResultList();
  return users;
}

On running it if you inspect the generated query you will find that now all the columns for the Account are also added with in the Select query and only a single query is getting all the associated data also.

Hibernate: select distinct user0_.id as id1_1_0_, accounts1_.id as id1_0_1_, user0_.name as name2_1_0_, accounts1_.acct_number as acct_num2_0_1_, accounts1_.user_id as user_id3_0_1_, accounts1_.user_id as user_id3_0_0__, accounts1_.id as id1_0_0__ from user_master user0_ inner join accounts accounts1_ on user0_.id=accounts1_.user_id

If you are using Criteria then the same method can be written as given below-

@Override
public List<User> findAllUsers() {
  CriteriaBuilder cb = em.getCriteriaBuilder();
  CriteriaQuery<User> cq = cb.createQuery(User.class);
  Root<User> root = cq.from(User.class);
  root.fetch("accounts");
  TypedQuery<User> query = em.createQuery(cq);
  List<User> users = query.getResultList();
  return users;
}

Changed findUserById() method with JPQL.

@Override
public User findUserById(int id) {
  //return em.find(User.class, id);    
  User user = em.createQuery("SELECT u FROM User u JOIN FETCH u.accounts a where u.id = :id", User.class)
           .setParameter("id", id)
           .getSingleResult();
    return user;
}

If you are using Criteria API then the same method can be written as given below-

public User findUserById(int id) {   
  CriteriaBuilder cb = em.getCriteriaBuilder();
  CriteriaQuery<User> cq = cb.createQuery(User.class);
  Root<User> root = cq.from(User.class);
  root.fetch("accounts");
  
  cq.where(cb.equal(root.get("userId"), id));
  
  TypedQuery<User> query = em.createQuery(cq);
  User user = query.getSingleResult();
  return user;
}

That's all for the topic Fix LazyInitializationException: could not initialize proxy Error. If something is missing or you have something to share about the topic please write a comment.


You may also like

No comments:

Post a Comment