抛出MultipleBagFetchException

时间:2012-11-11 19:54:21

标签: java hibernate java-ee jpa

我希望在我的存储库层中有一个选项来急切加载entites,所以我尝试添加一个方法,该方法应该急切加载带有所有关系的问题实体,但它会抛出MultipleBagFetchException。我怎样才能解决这个问题?我正在使用Hibernate 4.16。

@NamedQuery(name = Question.FIND_BY_ID_EAGER, query = "SELECT q FROM Question q LEFT JOIN FETCH q.answers LEFT JOIN FETCH q.categories LEFT JOIN FETCH q.feedback LEFT JOIN FETCH q.participant WHERE q.id = :id"),

如何获得最初延迟加载的问题对象,以及所有关系的热切加载?

2 个答案:

答案 0 :(得分:27)

这在Hibernate中实际上是一个非常讨厌的问题,实际上是ORM。

许多(获取)连接会导致创建相当大的笛卡尔积。即,其他连接新的列和新行出现在结果中,导致(相当)大的“正方形”结果。

Hibernate需要从此表中提取图形,但是将右列与正确的实体匹配并不够智能。

E.g。

假设我们有结果

A B C
A B D

需要成为:

 A
 |
 B
 /\
C  D

Hibernate可以从主键和一些编码魔法中扣除,图形必须是什么,但实际上它需要明确的帮助来解决这个问题。

执行此操作的一种方法是在关系上指定特定于Hibernate的@IndexColumn或JPA标准@OrderColumn

E.g。

@Entity
public class Question {


    @ManyToMany
    @JoinTable(
        name = "question_to_answer",
        joinColumns = @JoinColumn(name = "question_id"),
        inverseJoinColumns = @JoinColumn(name = "answer_id")
    )
    @IndexColumn(name = "answer_order")
    private List<Answer> answers;

    // ...
}

在这个例子中,我使用了一个连接表,还有一个额外的列answer_order。通过此列,每个问题/答案关系具有唯一的序列号,Hibernate可以区分结果表中的条目并创建所需的对象图。

一个注意事项,如果涉及多个实体,使用如此多的热切联接可能会导致比您根据所涉及的实体数量更大的结果集。

进一步阅读:

答案 1 :(得分:1)

  

这是我在StackOverflow或Hibernate论坛上都看到的一个重复出现的问题,所以我决定将答案变成an article

Hibernate不允许提取多个包,因为这会生成一个Cartesian product,对于无序列表(在Hibernate术语中称为 bags ),这甚至会导致重复的条目如果基础集合没有那些重复的行。因此,在编译JPQL查询时,Hibernate只是避免了这种情况。

现在,您会发现很多答案,博客文章,视频或其他资源,它们告诉您对集合使用Set而不是List

那是可怕的建议。不要这样做!

使用Sets代替Lists会使MultipleBagFetchException消失,但笛卡尔积仍然存在。

正确的解决方法

不是在单个JPQL或Criteria API查询中使用多个JOIN FETCH

List<Post> posts = entityManager
.createQuery(
    "select p " +
    "from Post p " +
    "left join fetch p.comments " +
    "left join fetch p.tags " +
    "where p.id between :minId and :maxId", Post.class)
.setParameter("minId", 1L)
.setParameter("maxId", 50L)
.getResultList();

您可以执行以下技巧:

List<Post> posts = entityManager
.createQuery(
    "select distinct p " +
    "from Post p " +
    "left join fetch p.comments " +
    "where p.id between :minId and :maxId ", Post.class)
.setParameter("minId", 1L)
.setParameter("maxId", 50L)
.setHint(QueryHints.PASS_DISTINCT_THROUGH, false)
.getResultList();

posts = entityManager
.createQuery(
    "select distinct p " +
    "from Post p " +
    "left join fetch p.tags t " +
    "where p in :posts ", Post.class)
.setParameter("posts", posts)
.setHint(QueryHints.PASS_DISTINCT_THROUGH, false)
.getResultList();

只要您使用JOIN FETCH最多获取一个收藏集,就可以了。通过使用多个查询,您将避免使用笛卡尔乘积,因为除第一个集合外,其他任何集合都是使用辅助查询来获取的。