抛出MultipleBagFetchException

Luc*_*uke 10 java hibernate jpa java-ee

我想在我的存储库层中有一个选项来急切加载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"),
Run Code Online (Sandbox Code Playgroud)

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

Arj*_*jms 32

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

会发生的是,许多(获取)连接会导致创建相当大的笛卡尔积.即永远的其他连接新列和新行出现在结果中,导致(相当)大'方'结果.

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

例如

假设我们有结果

A B C
A B D
Run Code Online (Sandbox Code Playgroud)

哪个需要成为:

 A
 |
 B
 /\
C  D
Run Code Online (Sandbox Code Playgroud)

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;

    // ...
}
Run Code Online (Sandbox Code Playgroud)

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

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

进一步阅读:

  • 应该注意的是,现在不推荐使用@IndexColumn(当前使用的是Hibernate 5.2.4.Final)。 (2认同)
  • 从 JPA 2.0 开始使用 `@OrderColumn` (2认同)

Vla*_*cea 6

Hibernate 不允许获取多个 bag,因为这会生成笛卡尔积,而对于无序列表(在 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();
Run Code Online (Sandbox Code Playgroud)

您可以执行以下技巧:

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();
Run Code Online (Sandbox Code Playgroud)

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