2nd level cache not working with 1:m lazy loaded collection

Now that I’ve spent several hours without any satisfying result trying to understand and fix a problem I encounter with Hibernate’s 2nd level cache I hope I can find help here. I’ve read almost every blogpost but am still not able to built a proper caching solution.

The issue is, that when I want to access a collection from a cached entity, where the collection is set to fetch = FetchType.LAZY, Hibernate throws a LazyInitializationException: failed to lazily initialize a collection of role, although the entities inside the collection are properly cached during the first time the parent entity is requested due to overriding the lazy load via an EntityGraph.

Briefly in advance: when the 1:m collection’s fetch type is set to EAGER everything works fine.

So here is 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 {
    // ...

    @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);
}

Since I use SpringBoot I utilize JpaRepository to gain easy database access. As you can see, I override the default fetching behavior for the postComments collection via EntityGraph.EntityGraphType.FETCH. The first time I request a Post entity everything works as expected:

Post post = postRepository.findById(1L).get();

Hibernate puts the Post into its 2nd level cache as well as all associated PostComment in their corresponding cache. I can even demand a PostComment and Hibernate will take it from the cache:

PostComment postComment = postCommentRepository.findById(1L).get();

So at least for my understanding, at this point everything is fine. But when I want to request the recently cached Post a second time and then access the postComments collection, Hibernate is not able to build the collection out from the cache. It tries, but fails with a LazyInitializationException:

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

And that’s the point where I am stuck.

  • Can somebody please tell me, why this happens, although all related entities are cached properly?
  • And why is everything working when collections are set to eager fetching by default (which is no option at all)?
  • And as a last point: why does this not affect query caches? (Query caches work as expected without any further modification of the parent entity, I don’t even have to annotate the postComments collection.)

Many thanks for any help

  • Can somebody please tell me, why this happens, although all related entities are cached properly?

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

  • And why is everything working when collections are set to eager fetching by default (which is no option at all)?

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

  • And as a last point: why does this not affect query caches? (Query caches work as expected without any further modification of the parent entity, I don’t even have to annotate the postComments collection.)

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

1 Like

Well, that are really good news about v6.5. And many thanks for the detailed explanation about the things behind the scenes. I know a lot of developers who also struggle with this partly unexpected behavior and also nobody could tell me anything about this for sure. What I’ve really wondered was, that even Stack Overflow has not a single proper answer to exact these my questions, although there are some questions to find on SO.

Again, many thanks for your help and making me eager^^ awaiting 6.5 :wink:

Edit:

If it’s ok with you, I’d ask you to repost this very understandable answer at SO here: How to 2nd level cache a Lazy Loaded Collection in Hibernate?. If you just don’t wanna spent time to do this I can do it as well, but your name should have some weight so people would maybe trust you little more :wink:

1 Like