【问题标题】:Hibernate throws MultipleBagFetchException - cannot simultaneously fetch multiple bagsHibernate 抛出 MultipleBagFetchException - 不能同时获取多个包
【发布时间】:2011-05-19 02:39:15
【问题描述】:

Hibernate 在创建 SessionFactory 期间抛出此异常:

org.hibernate.loader.MultipleBagFetchException: 不能同时获取多个包

这是我的测试用例:

Parent.java

@Entity
public Parent {

 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 private Long id;

 @OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
 // @IndexColumn(name="INDEX_COL") if I had this the problem solve but I retrieve more children than I have, one child is null.
 private List<Child> children;

}

Child.java

@Entity
public Child {

 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 private Long id;

 @ManyToOne
 private Parent parent;

}

这个问题怎么样?我能做什么?


编辑

好的,我遇到的问题是另一个“父”实体在我的父级内部,我的真实行为是这样的:

Parent.java

@Entity
public Parent {

 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 private Long id;

 @ManyToOne
 private AnotherParent anotherParent;

 @OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
 private List<Child> children;

}

AnotherParent.java

@Entity
public AnotherParent {

 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 private Long id;

 @OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
 private List<AnotherChild> anotherChildren;

}

Hibernate 不喜欢使用FetchType.EAGER 的两个集合,但这似乎是一个错误,我没有做不寻常的事情......

ParentAnotherParent 中删除FetchType.EAGER 可以解决问题,但我需要它,所以真正的解决方案是使用@LazyCollection(LazyCollectionOption.FALSE) 而不是FetchType(感谢Bozho 的解决方案)。

【问题讨论】:

  • 我会问,您希望生成什么 SQL 查询来同时检索两个单独的集合?能够实现这些的 SQL 类型要么需要笛卡尔连接(可能非常低效),要么需要不相交列的 UNION(也很丑陋)。据推测,无法在 SQL 中以干净高效的方式实现这一点影响了 API 设计。
  • @ThomasW 这些是它应该生成的 sql 查询:select * from master; select * from child1 where master_id = :master_id; select * from child2 where master_id = :master_id
  • 如果您有多个List&lt;child&gt; 并且为多个 List&lt;clield&gt; 定义了fetchType,您可能会收到类似的错误

标签: java hibernate jpa one-to-many bag


【解决方案1】:

我认为更新版本的 hibernate(支持 JPA 2.0)应该可以处理这个问题。但除此之外,您可以通过以下方式注释集合字段来解决它:

@LazyCollection(LazyCollectionOption.FALSE)

记得从@*ToMany注解中删除fetchType属性。

但请注意,在大多数情况下,Set&lt;Child&gt;List&lt;Child&gt; 更合适,所以除非你真的需要 List - 选择 Set

但请注意,使用集合您不会消除Vlad Mihalcea in his answer所描述的底层笛卡尔积

【讨论】:

  • 奇怪,它对我有用。您是否从 @*ToMany 中删除了 fetchType
  • 问题在于 JPA 注释被解析为不允许超过 2 个急切加载的集合。但是特定于休眠的注解允许它。
  • 需要超过 1 个 EAGER 似乎是完全现实的。这个限制只是 JPA 的疏忽吗?拥有多个 EAGER 时我应该注意哪些问题?
  • 问题是,hibernate 无法通过一个查询获取两个集合。因此,当您查询父实体时,每个结果将需要 2 个额外查询,这通常是您不想要的。
  • 很高兴能解释一下为什么这样可以解决问题。
【解决方案2】:

只需将List 类型更改为Set 类型即可。

但请注意,您不会消除Vlad Mihalcea in his answer所描述的底层笛卡尔积

【讨论】:

  • List 和 Set 不是一回事:set 不保持顺序
  • LinkedHashSet 保留顺序
  • 这是一个重要的区别,仔细想想,它是完全正确的。由数据库中的外键实现的典型多对一实际上不是一个列表,而是一个集合,因为没有保留顺序。所以Set确实更合适。我认为这在休眠中有所不同,尽管我不知道为什么。
  • 我有同样的不能同时提取多个包,但不是因为注释。就我而言,我正在使用两个*ToMany 进行左连接和分离。将类型更改为Set 也解决了我的问题。优秀而整洁的解决方案。这应该是官方的答案。
  • 我喜欢这个答案,但百万美元的问题是:为什么?为什么 Set 不显示异常?谢谢
【解决方案3】:

在您的代码中添加特定于 Hibernate 的 @Fetch 注解:

@OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
@Fetch(value = FetchMode.SUBSELECT)
private List<Child> childs;

这应该可以解决与 Hibernate 错误 HHH-1718 相关的问题

【讨论】:

  • @DaveRlz 为什么 subSelect 解决了这个问题。我尝试了您的解决方案及其工作原理,但不知道使用此解决方案如何解决问题?
  • 这是最好的答案,除非 Set 真的有意义。使用Set 拥有单个OneToMany 关系会导致1+&lt;# relationships&gt; 查询,而使用FetchMode.SUBSELECT 会导致1+1 查询。此外,在接受的答案 (LazyCollectionOption.FALSE) 中使用注释会导致执行更多查询。
  • FetchType.EAGER 不是一个合适的解决方案。需要继续使用 Hibernate Fetch Profiles 并需要解决它
  • 另外两个最佳答案并没有解决我的问题。这个做到了。谢谢!
  • 有谁知道为什么 SUBSELECT 修复它,但 JOIN 没有?
【解决方案4】:

我发现了一篇关于 Hibernate 在这种对象映射中的行为的好博客文章:http://blog.eyallupu.com/2010/06/hibernate-exception-simultaneously.html

【讨论】:

  • 欢迎来到 Stack Overflow。但是,通常不鼓励仅链接的答案。请参阅常见问题解答,尤其是:stackoverflow.com/faq#deletion
【解决方案5】:

在尝试了这篇文章和其他文章中描述的每一个选项后,我得出的结论是,修复如下。

在每个 XToMany 地方@XXXToMany(mappedBy="parent", fetch=FetchType.EAGER) 和中间之后

@Fetch(value = FetchMode.SUBSELECT)

这对我有用

【讨论】:

  • 添加@Fetch(value = FetchMode.SUBSELECT)就足够了
  • 这是一个仅限 Hibernate 的解决方案。如果您使用的是共享 JPA 库怎么办?
  • 我确定你不是故意的,但 DaveRlz 已经在 3 年前写过同样的东西
【解决方案6】:

要修复它,只需将 Set 替换为嵌套对象的 List

@OneToMany
Set<Your_object> objectList;

别忘了使用fetch=FetchType.EAGER

它会起作用的。

如果你只想坚持使用列表,Hibernate 中还有一个概念 CollectionId

但请注意,您不会消除Vlad Mihalcea in his answer所描述的底层笛卡尔积

【讨论】:

  • 您提出的两个建议在性能方面都非常糟糕。
【解决方案7】:

如果您有过于复杂的对象并使用 saveral 集合,将所有对象都使用 EAGER fetchType 是一个好主意,最好使用 LAZY,当您确实需要加载集合时,请使用:Hibernate.initialize(parent.child) 来获取数据。

【讨论】:

    【解决方案8】:

    您可以在 JPA 中保留展位 EAGER 列表,并在其中至少一个中添加 JPA 注释 @OrderColumn(显然是要订购的字段的名称)。不需要特定的休眠注释。 但请记住,如果所选字段的值不是从 0 开始的,它可能会在列表中创建空元素

     [...]
     @OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
     @OrderColumn(name="orderIndex")
     private List<Child> children;
     [...]
    

    在 Children 那么你应该添加 orderIndex 字段

    【讨论】:

      【解决方案9】:

      你可以使用一个新的注解来解决这个问题:

      @XXXToXXX(targetEntity = XXXX.class, fetch = FetchType.LAZY)
      

      其实fetch的默认值也是FetchType.LAZY。

      【讨论】:

      • JPA3.0 不存在。
      【解决方案10】:

      我们尝试了 Set 而不是 List,这是一场噩梦:当您添加两个新对象时,equals() 和 hashCode() 无法区分它们!因为他们没有任何 id。

      Eclipse 等典型工具从数据库表生成此类代码:

      @Override
      public int hashCode() {
          final int prime = 31;
          int result = 1;
          result = prime * result + ((id == null) ? 0 : id.hashCode());
          return result;
      }
      

      您还可以阅读this article,它正确地解释了 JPA/Hibernate 的混乱程度。看完这篇,我想这是我这辈子最后一次使用 ORM 了。

      我也遇到过领域驱动设计的人,他们基本上说 ORM 是一个可怕的东西。

      【讨论】:

        【解决方案11】:

        考虑到我们有以下实体:

        而且,您想要获取一些父 Post 实体以及所有 commentstags 集合。

        如果您使用多个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();
        

        Hibernate 会抛出臭名昭著的:

        org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags [
          com.vladmihalcea.book.hpjp.hibernate.fetching.Post.comments,
          com.vladmihalcea.book.hpjp.hibernate.fetching.Post.tags
        ]
        

        Hibernate 不允许获取多个包,因为这会生成笛卡尔积。

        最糟糕的“解决方案”

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

        这是一个糟糕的建议。不要那样做!

        使用Sets 而不是Lists 将使MultipleBagFetchException 消失,但笛卡尔积仍然存在,这实际上更糟糕,因为您会在应用它很久之后发现性能问题“修复”。

        正确的解决方案

        您可以使用以下技巧:

        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();
        

        在第一个 JPQL 查询中,distinct 不会转到 SQL 语句。这就是我们将PASS_DISTINCT_THROUGH JPA 查询提示设置为false 的原因。

        DISTINCT 在 JPQL 中有两个含义,在这里,我们需要它在 Java 端而不是 SQL 端对 getResultList 返回的 Java 对象引用进行去重。

        只要您使用JOIN FETCH 最多获取一个集合,就可以了。

        通过使用多个查询,您将避免笛卡尔积,因为任何其他集合,但第一个是使用辅助查询获取的。

        你可以做更多的事情

        如果您在为@OneToMany@ManyToMany 关联映射时使用FetchType.EAGER 策略,那么您很容易得到MultipleBagFetchException

        您最好从 FetchType.EAGER 切换到 Fetchype.LAZY,因为急切获取是一个糟糕的想法,可能会导致严重的应用程序性能问题。

        结论

        避免使用FetchType.EAGER 并且不要从List 切换到Set,因为这样做会使Hibernate 将MultipleBagFetchException 隐藏在地毯下。一次只获取一个集合,就可以了。

        只要您使用与要初始化的集合相同数量的查询来执行此操作,就可以了。只是不要在循环中初始化集合,因为这会触发 N+1 查询问题,这对性能也很不利。

        【讨论】:

        • 弗拉德,感谢您的帮助,我发现它真的很常用。但是,该问题与hibernate.jdbc.fetch_size 有关(最终我将其设置为 350)。碰巧,你知道如何优化嵌套关系吗?例如。 entity1 -> entity2 -> entity3.1,entity 3.2(其中 entity3.1 / 3.2 是@OneToMany 关系)
        • 不,你不能。从 SQL 的角度考虑它。如果不生成笛卡尔积,就不能加入多个一对多关联。
        • 您应该始终在调用 Spring Data Jpa 存储库的服务方法上使用 @Transactional。不这样做是一个可怕的错误。
        • 如果您使用任何策略获取两个同级 Set 关联,您将获得笛卡尔积。如果关联是相关的,例如孩子和孙子,那么您将不会得到笛卡尔积。
        • 视情况而定。对于可嵌入的,Set 更好。对于双向的一对多关联,没关系,但是List更加通用。对于多对多,集合更好。您可以在我的《高性能 Java 持久性》一书中找到详细说明。
        【解决方案12】:

        对我来说,问题在于嵌套的 EAGER 提取。

        一种解决方案是将嵌套字段设置为 LAZY 并使用 Hibernate.initialize() 加载嵌套字段:

        x = session.get(ClassName.class, id);
        Hibernate.initialize(x.getNestedField());
        

        【讨论】:

          【解决方案13】:

          最后,这发生在我使用 FetchType.EAGER 有多个集合时,如下所示:

          @ManyToMany(fetch = FetchType.EAGER, targetEntity = className.class)
          @JoinColumn(name = "myClass_id")
          @JsonView(SerializationView.Summary.class)
          private Collection<Model> ModelObjects;
          

          此外,集合在同一列中加入。

          为了解决这个问题,我将其中一个集合更改为 FetchType.LAZY,因为它适合我的用例。

          祝你好运! ~J

          【讨论】:

            【解决方案14】:

            同时评论FetchLazyCollection 有时有助于运行项目。

            @Fetch(FetchMode.JOIN)
            @LazyCollection(LazyCollectionOption.FALSE)
            

            【讨论】:

              【解决方案15】:

              @LazyCollection(LazyCollectionOption.FALSE) 的一个好处是,带有此注释的多个字段可以共存,而 FetchType.EAGER 则不能,即使在这种共存是合法的情况下也是如此。

              例如,Order 可能有一个OrderGroup 列表(很短)以及Promotions 列表(也很短)。 @LazyCollection(LazyCollectionOption.FALSE) 可以在两者上使用而不会导致LazyInitializationExceptionMultipleBagFetchException

              就我而言,@Fetch 确实解决了我的MultipleBacFetchException 问题,但随后导致了LazyInitializationException,即臭名昭著的no Session 错误。

              【讨论】:

                【解决方案16】:

                我通过注释解决了:

                @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
                

                【讨论】:

                  【解决方案17】:

                  好的,这是我的 2 美分。我在我的实体中有 Fetch Lazy 注释,但我还在会话 bean 中复制了 fetch lazy ,从而导致了多包问题。所以我只是删除了 SessionBean 中的行

                  criteria.createAlias("FIELD", "ALIAS", JoinType.LEFT_OUTER_JOIN); //已移除

                  在调用 List parent = criteria.list 后,我​​在要检索的列表上使用了 Hibernate.initialize。 Hibernate.initialize(parent.getChildList());

                  【讨论】:

                    【解决方案18】:

                    您也可以尝试使 fetch=FetchType.LAZY 并只需将 @Transactional(readOnly = true) 添加到您获取孩子的方法中

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 2012-10-31
                      • 1970-01-01
                      • 2013-05-03
                      • 2017-08-20
                      • 2016-09-13
                      • 2014-08-31
                      • 2015-11-28
                      • 2011-11-02
                      相关资源
                      最近更新 更多