15

我想在我的存储库层中有一个选项来急切加载实体,所以我尝试添加一个应该急切加载具有所有关系的问题实体的方法,但它会抛出 MultipleBagFetchException。我怎样才能解决这个问题?我正在使用休眠 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"),

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

4

2 回答 2

37

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

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

Hibernate 需要从这个表中提取一个图形,但它不够聪明,无法将正确的列与正确的实体匹配。

例如

假设我们有结果

A B C
A B D

需要变成:

 A
 |
 B
 /\
C  D

Hibernate 可以从主键和一些编码魔法中推断出图形必须是什么,但实际上它需要明确的帮助才能实现这一点。

一种方法是在关系上指定 Hibernate 特定@IndexColumn或 JPA 标准@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;

    // ...
}

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

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

进一步阅读:

于 2012-11-11T20:30:34.823 回答
6

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

现在,您会发现很多答案、博客文章、视频或其他资源告诉您在收藏中使用 aSet而不是 a List

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

使用Sets而不是Lists将使MultipleBagFetchException消失,但笛卡尔积仍然存在。

正确的修复

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

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,就可以了。通过使用多个查询,您将避免笛卡尔积,因为任何其他集合,但第一个集合是使用辅助查询获取的。

于 2019-11-12T19:12:54.423 回答