12

We have a project where we need to lazily load collections of an entity, but in some cases we need them loaded eagerly. We have added a @NamedEntityGraph annotation to our entity. In our repository methods we add a "javax.persistence.loadgraph" hint to eagerly load 4 of attributes defined in said annotation. When we invoke that query, Hibernate throws org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags.

Funnily, when I redefine all of those collection as eagerly fetched Hibernate does fetch them eagerly with no MultipleBagFetchException.

Here is the distilled code. Entity:

@Entity
@NamedEntityGraph(name = "Post.Full", attributeNodes = {
        @NamedAttributeNode("comments"),
        @NamedAttributeNode("plusoners"),
        @NamedAttributeNode("sharedWith")
    }
)
public class Post {
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "postId")
    private List<Comment> comments;

    @ElementCollection
    @CollectionTable(name="post_plusoners")
    private List<PostRelatedPerson> plusoners;

    @ElementCollection
    @CollectionTable(name="post_shared_with")
    private List<PostRelatedPerson> sharedWith;

}

Query method (all cramped together to make it postable):

@Override
public Page<Post> findFullPosts(Specification<Post> spec, Pageable pageable) {
    CriteriaBuilder builder = entityManager.getCriteriaBuilder();
    CriteriaQuery<Post> query = builder.createQuery(Post.class);
    Root<Post> post = query.from(Post.class);
    Predicate postsPredicate = spec.toPredicate(post, query, builder);
    query.where(postsPredicate);

    EntityGraph<?> entityGraph = entityManager.createEntityGraph("PlusPost.Full");

    TypedQuery<GooglePlusFullPost> typedQuery = entityManager.createQuery(query);
    typedQuery.setHint("javax.persistence.loadgraph", entityGraph);

    query.setFirstResult(pageable.getOffset());
    query.setMaxResults(pageable.getPageSize());

    Long total = QueryUtils.executeCountQuery(getPostCountQuery(specification));

    List<P> resultList = total > pageable.getOffset() ? query.getResultList() : Collections.<P>emptyList();
    return new PageImpl<P>(resultList, pageable, total);
}

Any hints on why is this working with eager fetches on entity level, but not with dynamic entity graphs?

theadam
  • 3,961
  • 4
  • 25
  • 41

1 Answers1

14

I'm betting the eager fetches you think were working, were actually working incorrectly.

When you eager fetch more than one "bag" (an unorder collection allowing duplicates), the sql used to perform the eager fetch (left outer join) will return multiple results for the joined associations as explained by this SO answer. So while hibernate does not throw the org.hibernate.loader.MultipleBagFetchException when you have more than one List eagerly fetched it would not return accurate results for the reason given above.

However, when you give the query the entity graph hint, hibernate will (rightly) complain. Hibernate developer, Emmanuel Bernard, addresses the reasons for this exception to be thrown:

eager fetching is not the problem per se, using multiple joins in one SQL query is. It's not limited to the static fetching strategy; it has never been supported (property), because it's conceptually not possible.

Emmanuel goes on to say in a different JIRA comment that,

most uses of "non-indexed" List or raw Collection are erroneous and should semantically be Sets.

So bottom line, in order to get the multiple eager fetching to work as you desire:

  • use a Set rather than a List
  • persist the List index using JPA 2's @OrderColumn annotation,
  • if all else fails, fallback to Hibernate specific fetch annotations (FetchMode.SELECT or FetchMode.SUBSELECT)

EDIT

related:

Community
  • 1
  • 1
Brice Roncace
  • 10,110
  • 9
  • 60
  • 69
  • Changing 'List' to 'Set' worked for me on a @ElementCollection(fetch = FetchType.EAGER) private Set colours; @ElementCollection(fetch = FetchType.EAGER) private Set accessories; – NOTiFY Dec 03 '18 at 22:20
  • According to Vlad if I understand correctly this is a problematic answer: https://vladmihalcea.com/hibernate-multiplebagfetchexception/ – Shai Almog Mar 11 '21 at 12:46
  • If everything is so wrong then why it works somehow before entity graph introduced?.. – Ivan Karotki Jun 10 '22 at 09:01
  • And how to deal with situation when you have some entities (added not by you with lists or whatever (and you actually don't know why list was used and why fetchType is lazy or whatever)) mapped to DB tables, and you just need to fetch them but you have lazy initialization exception, you trying to use entity graph and you have org.hibernate.loader.MultipleBagFetchException? – Ivan Karotki Jun 10 '22 at 09:03