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 interfacepublic 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 clauseBest 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
- Spring + JPA (Hibernate) OneToMany Example
- Spring Data JPA Example
- Spring Boot + Spring Data JPA + MySQL + Spring RESTful
- Java Stream – Convert a Stream to Set
- ZonedDateTime in Java With Examples
- How to Convert String to Byte Array in Java
- Method Overloading in Python With Examples
- Distributed Cache in Hadoop
No comments:
Post a Comment