【问题标题】:MultipleBagFetchException thrown by HibernateHibernate 抛出 MultipleBagFetchException
【发布时间】:2012-10-31 08:27:26
【问题描述】:

我想在我的存储库层中有一个选项来预加载实体,所以我尝试添加一个方法来预加载包含所有关系的问题实体,但它会抛出 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"),

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

【问题讨论】:

    标签: java hibernate jpa jakarta-ee jpql


    【解决方案1】:

    这在 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。通过该列,每个 Question/Answer 关系都有一个唯一的序号,Hibernate 可以区分结果表中的条目并创建所需的 Object 图。

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

    进一步阅读:

    【讨论】:

    • 需要注意的是@IndexColumn现在已经被弃用了(目前使用Hibernate 5.2.4.Final)
    • 自 JPA 2.0 起使用@OrderColumn
    【解决方案2】:

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

    现在,您会发现很多答案、博客文章、视频或其他资源告诉您在收藏中使用 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 最多获取一个集合,就可以了。通过使用多个查询,您将避免笛卡尔积,因为任何其他集合,但第一个集合是使用辅助查询获取的。

    【讨论】:

    • 我尝试了这个解决方案,但生成的帖子只包含标签(因为这是最后获取的)而不是 cmets。评论被延迟加载。尝试访问 posts.get(0).getComments().size() 会抛出 LazyIntializationEXception
    • 所有代码都在GitHub 上,并且工作起来非常有魅力。
    • 是的,它有效,我必须在我的方法中添加@Transactional。在上面的示例中,如果我使用 Set posts 而不是 list 并删除查询提示和 distinct 关键字并像上面一样运行 2 个查询,我基本上会得到相同的结果吗?
    • 嗨,对我来说,同样的情况,我得到每个查询的外部查询,我使用的是 spring,我必须创建一个 bean 来获取 entityFactory 并使用'@persistenceContext',我标记了带有'@transactional'注释的方法来查询我想要获取的'bag'或关系,有没有简单的方法可以做?春天
    猜你喜欢
    • 2011-05-19
    • 2012-10-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-25
    相关资源
    最近更新 更多