spring-boothibernatesecond-level-cache

How to 2nd level cache a Lazy Loaded Collection in Hibernate?


In a Spring Boot application I've set up an Ehcache for Hibernate to use as 2nd level cache. The issue is, that Hibernate throws a LazyInitializationException: failed to lazily initialize a collection of role when I want to access a collection, which is by default set to lazy load.

So here's the setup:

  1. Post:
@Entity
@Access(AccessType.FIELD)
@org.hibernate.annotations.Cache(region = "post-cache",
                                 usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@Table(name = "post")
public class Post implements Serializable {

    // ...

    @JsonManagedReference
    @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
    @OneToMany(mappedBy = "post",
               fetch = FetchType.LAZY,
               cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH})
    private List<PostComment> postComments = new ArrayList<>();

    // ...

}
  1. PostComment:
@Entity
@Access(AccessType.FIELD)
@org.hibernate.annotations.Cache(region = "post-comment-cache",
                                 usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@Table(name = "post_comment")
public class PostComment implements Serializable {

    // ...

}
  1. PostRepository:
public interface PostRepository extends JpaRepository<Post, Long> {

    @EntityGraph(type = EntityGraph.EntityGraphType.FETCH, attributePaths = {
            "postComments"
    })
    Optional<Post> findById(Long id);

Unfortunately, this doesn't work and Hibernate throws as mentioned above an exception:

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.example.Post.postComments: could not initialize proxy - no Session

Does anybody know why this won't work although all tutorials I've read are doing the same?

Many thanks for any help

Edit:

Just for information, everything works fine when using FetchType.EAGER on the collection within the Post entity like that:

@org.hibernate.annotations.Cache(region = "post-comment-cache",
                                     usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@OneToMany(mappedBy = "post",
           fetch = FetchType.EAGER,
           cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH})
private List<PostComment> postComments = new ArrayList<>();

But that's not what I want. I want to have all OneToMany relations set to lazy loading.


Solution

  • This is a limitation that is going to be fixed in Hibernate ORM 6.5 via https://github.com/hibernate/hibernate-orm/pull/7543

    When Hibernate ORM loads an entity/collection from cache, it will ensure that the configured FetchType of nested associations are respected. If you run a query that join fetches a lazy association or specify a fetch/load graph for such an association, then Hibernate ORM is currently not able to efficiently initialize these associations. Until version 6.5 you will have to initialize associations manually within your transaction with e.g. Hibernate.initialize( object ).

    Query caches in Hibernate ORM 6 store the full data, whereas in 5 it stored only the keys of entities. I guess you are using ORM 6, so you won’t have any trouble there with query caches, but note that if you also want to cache entity data by id, using the full query cache layout might not be the best choice, especially if you expect that the entity cache hit rate is very high (i.e. if the cached entity is reference data).

    You’ll be happy to hear though that this is also covered by this PR. It introduces a way to specify how an entity should be stored in a query cache. By default, ORM 6.5 will only store the primary key of an entity if the entity is marked as cacheable i.e. assuming a high cache hit rate. You can fine tune that though.