How to solve the “failed to lazily initialize a collection of role” Hibernate exception

Viewed 680417

I have this problem:

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: mvc3.model.Topic.comments, no session or session was closed

Here is the model:

@Entity
@Table(name = "T_TOPIC")
public class Topic {

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private int id;

    @ManyToOne
    @JoinColumn(name="USER_ID")
    private User author;

    @Enumerated(EnumType.STRING)    
    private Tag topicTag;

    private String name;
    private String text;

    @OneToMany(mappedBy = "topic", cascade = CascadeType.ALL)
    private Collection<Comment> comments = new LinkedHashSet<Comment>();

    ...

    public Collection<Comment> getComments() {
           return comments;
    }

}

The controller, which calls model looks like the following:

@Controller
@RequestMapping(value = "/topic")
public class TopicController {

    @Autowired
    private TopicService service;

    private static final Logger logger = LoggerFactory.getLogger(TopicController.class);


    @RequestMapping(value = "/details/{topicId}", method = RequestMethod.GET)
    public ModelAndView details(@PathVariable(value="topicId") int id)
    {

            Topic topicById = service.findTopicByID(id);
            Collection<Comment> commentList = topicById.getComments();

            Hashtable modelData = new Hashtable();
            modelData.put("topic", topicById);
            modelData.put("commentList", commentList);

            return new ModelAndView("/topic/details", modelData);

     }

}

The jsp-page looks li the following:

<%@page import="com.epam.mvc3.helpers.Utils"%>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
<head>
      <title>View Topic</title>
</head>
<body>

<ul>
<c:forEach items="${commentList}" var="item">
<jsp:useBean id="item" type="mvc3.model.Comment"/>
<li>${item.getText()}</li>

</c:forEach>
</ul>
</body>
</html>

Exception is rised, when viewing jsp. In the line with c:forEach loop

32 Answers

The best way to handle the LazyInitializationException is to join fetch upon query time, like this:

select t
from Topic t
left join fetch t.comments

You should ALWAYS avoid the following anti-patterns:

Therefore, make sure that your FetchType.LAZY associations are initialized at query time or within the original @Transactional scope using Hibernate.initialize for secondary collections.

One of the best solutions is to add the following in your application.properties file: spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true

Two things you should have for fetch = FetchType.LAZY.

@Transactional

and

Hibernate.initialize(topicById.getComments());

There are multiple solution for this Lazy Initialisation issue -

1) Change the association Fetch type from LAZY to EAGER but this is not a good practice because this will degrade the performance.

2) Use FetchType.LAZY on associated Object and also use Transactional annotation in your service layer method so that session will remain open and when you will call topicById.getComments(), child object(comments) will get loaded.

3) Also, please try to use DTO object instead of entity in controller layer. In your case, session is closed at controller layer. SO better to convert entity to DTO in service layer.

I got this error after a second execution of a method to generate a JWT token.

The line user.getUsersRole().stream().forEachOrdered((ur) -> roles.add(ur.getRoleId())); generated the error.

// MyUserDetails.java

@Service
public class MyUserDetails implements UserDetailsService {

  @Override
  public UserDetails loadUserByUsername(String email) {

    /* ERROR
    /* org.hibernate.LazyInitializationException: failed to 
    /* lazily initialize a collection of role: 
    /* com.organizator.backend.model.User.usersRole, 
    /* could not initialize proxy - no Session */
    user.getUsersRole().stream().forEachOrdered((ur) ->
           roles.add(ur.getRoleId()));

In my case the @Transactional annotation solved it,

// MyUserDetails.java

import org.springframework.transaction.annotation.Transactional;

@Service
public class MyUserDetails implements UserDetailsService {

  @Override
  @Transactional // <-- added
  public UserDetails loadUserByUsername(String email) {

    /* No Error */
    user.getUsersRole().stream().forEachOrdered((ur) ->
           roles.add(ur.getRoleId()));

The problem is caused because the code is accessing a lazy JPA relation when the "connection" to the database is closed (persistence context is the correct name in terms of Hibernate/JPA).

A simple way of solving it in Spring Boot is by defining a service layer and using the @Transactional annotation. This annotation in a method creates a transaction that propagates into the repository layer and keeps open the persistence context until the method finish. If you access the collection inside the transactional method Hibernate/JPA will fetch the data from the database.

In your case, you just need to annotate with @Transactional the method findTopicByID(id) in your TopicService and force the fetch of the collection in that method (for instance, by asking its size):

    @Transactional(readOnly = true)
    public Topic findTopicById(Long id) {
        Topic topic = TopicRepository.findById(id).orElse(null);
        topic.getComments().size();
        return topic;
    }

it was the problem i recently faced which i solved with using

<f:attribute name="collectionType" value="java.util.ArrayList" />

more detailed decription here and this saved my day.

By using the hibernate @Transactional annotation, if you get an object from the database with lazy fetched attributes, you can simply get these by fetching these attributes like this :

@Transactional
public void checkTicketSalePresence(UUID ticketUuid, UUID saleUuid) {
        Optional<Ticket> savedTicketOpt = ticketRepository.findById(ticketUuid);
        savedTicketOpt.ifPresent(ticket -> {
            Optional<Sale> saleOpt = ticket.getSales().stream().filter(sale -> sale.getUuid() == saleUuid).findFirst();
            assertThat(saleOpt).isPresent();
        });
}

Here, in an Hibernate proxy-managed transaction, the fact of calling ticket.getSales() do another query to fetch sales because you explicitly asked it.

To get rid of lazy initialization exception you should not call for lazy collection when you operate with detached object.

From my opinion, best approach is to use DTO, and not entity. In this case you can explicitly set fields which you want to use. As usual it's enough. No need to worry that something like jackson ObjectMapper, or hashCode generated by Lombok will call your methods implicitly.

For some specific cases you can use @EntityGrpaph annotation, which allow you to make eager load even if you have fetchType=lazy in your entity.

This is an old question but the below information may help people looking for an answer to this.

@VladMihalcea 's answer is useful. You must not rely on FetchType.EAGER , instead you should load the comments into the Topic entity when required.

If you are not explicitly defining your queries so that you could specify a join fetch, then using @NamedEntityGraph and @EntityGraph you could override the FetchType.LAZY (@OneToMany associations use LAZY by default) at runtime and load the comments at the same time as the Topic only when required. Which means that you restrict loading the comments to only those methods (queries) which really require that. An entity graph as JPA defines it:

An entity graph can be used with the find method or as a query hint to override or augment FetchType semantics.

You could use it based on the JPA example here. Alternatively, if you use Spring Data JPA, then you could use it based on the example provided by Spring.

Not the best solution, but for those who are facing LazyInitializationException especially on Serialization this will help. Here you will check lazily initialized properties and setting null to those. For that create the below class

public class RepositoryUtil {
    public static final boolean isCollectionInitialized(Collection<?> collection) {
        if (collection instanceof PersistentCollection)
            return ((PersistentCollection) collection).wasInitialized();
        else 
            return true;
    }   
}

Inside your Entity class which you are having lazily initialized properties add a method like shown below. Add all your lazily loading properties inside this method.

public void checkLazyIntialzation() {
    if (!RepositoryUtil.isCollectionInitialized(yourlazyproperty)) {
        yourlazyproperty= null;
    }

Call this checkLazyIntialzation() method after on all the places where you are loading data.

 YourEntity obj= entityManager.find(YourEntity.class,1L);
  obj.checkLazyIntialzation();

In my case, I had the mapping b/w A and B like

A has

@OneToMany(mappedBy = "a", cascade = CascadeType.ALL)
Set<B> bs;

in the DAO layer, the method needs to be annotated with @Transactional if you haven't annotated the mapping with Fetch Type - Eager

The collection comments in your model class Topic is lazily loaded, which is the default behaviour if you don't annotate it with fetch = FetchType.EAGER specifically.

It is mostly likely that your findTopicByID service is using a stateless Hibernate session. A stateless session does not have the first level cache, i.e., no persistence context. Later on when you try to iterate comments, Hibernate will throw an exception.

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: mvc3.model.Topic.comments, no session or session was closed

The solution can be:

  1. Annotate comments with fetch = FetchType.EAGER

    @OneToMany(fetch = FetchType.EAGER, mappedBy = "topic", cascade = CascadeType.ALL)   
    private Collection<Comment> comments = new LinkedHashSet<Comment>();
    
  2. If you still would like comments to be lazily loaded, use Hibernate's stateful sessions, so that you'll be able to fetch comments later on demand.

Hi All posting quite late hope it helps others, Thanking in advance to @GMK for this post Hibernate.initialize(object)

when Lazy="true"

Set<myObject> set=null;
hibernateSession.open
set=hibernateSession.getMyObjects();
hibernateSession.close();

now if i access 'set' after closing session it throws exception.

My solution :

Set<myObject> set=new HashSet<myObject>();
hibernateSession.open
set.addAll(hibernateSession.getMyObjects());
hibernateSession.close();

now i can access 'set' even after closing Hibernate Session.

Yet another way to do the thing, you can use TransactionTemplate to wrap around the lazy fetch. Like

Collection<Comment> commentList = this.transactionTemplate.execute
(status -> topicById.getComments());

In my Spring-Boot project spring.jpa.open-in-view was set to false in application.properties. Setting it to true solved the problem.

Related