由 Hibernate 抛出的 MultiBagFetchException

2022-09-03 09:04:25

我想在我的存储库层中有一个选项来预先加载entites,所以我尝试添加一个应该预先加载具有所有关系的问题实体的方法,但它会抛出MultiBagFetchException。我该如何解决这个问题?我使用的是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"),

我如何获得一个最初延迟加载的问题对象,以便预先加载所有关系?


答案 1

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

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

Hibernate 需要从此表中提炼出一个图形,但它不够智能,无法将正确的列与正确的实体进行匹配。

例如:

假设我们有结果

A B C
A B D

这需要成为:

 A
 |
 B
 /\
C  D

休眠可以从主键和一些编码魔术中扣除图形必须是什么,但实际上它需要明确的帮助来实现这一点。

一种方法是在关系上指定休眠特定或JPA标准。@IndexColumn@OrderColumn

例如:

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

    // ...
}

在此示例中,我使用的是一个连接表,其中包含一个额外的列。通过此列(每个问答关系具有唯一的序列号),Hibernate 可以区分结果表中的条目并创建所需的对象图。answer_order

顺便说一句,如果它涉及多个实体,使用如此多的急切联接可能会导致结果集比您根据所涉及的实体数量可能想象的要大得多。

延伸阅读:


答案 2

Hibernate不允许获取多个包,因为这会生成笛卡尔积,对于无序列表,在Hibernate术语中称为baggs,这将导致重复条目,即使底层集合没有这些重复的行。因此,休眠只是在编译 JPQL 查询时防止了这种情况。

现在,您会发现许多答案,博客文章,视频或其他资源,告诉您对集合使用a而不是a。SetList

这是一个可怕的建议。别这样!

使用而不是会使消失,但笛卡尔积仍然存在。SetsListsMultipleBagFetchException

正确的修复

而不是在单个 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


推荐