Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NamedEntityGraph - JPA / Hibernate throwing org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags

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?

like image 980
theadam Avatar asked Nov 14 '14 16:11

theadam


People also ask

What is MultipleBagFetchException?

Fetching two or more Bags at the same time on an Entity could form a Cartesian Product. Since a Bag doesn't have an order, Hibernate would not be able to map the right columns to the right entities. Hence, in this case, it throws a MultipleBagFetchException.

What is entity graph in JPA?

JPA 2.1 has introduced the Entity Graph feature as a more sophisticated method of dealing with performance loading. It allows defining a template by grouping the related persistence fields which we want to retrieve and lets us choose the graph type at runtime.


1 Answers

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:

  • https://stackoverflow.com/a/17567590/225217
  • https://stackoverflow.com/a/24676806/225217
like image 64
Brice Roncace Avatar answered Sep 28 '22 01:09

Brice Roncace