【问题标题】:Can't fetch join lazy initialized collections无法获取加入延迟初始化的集合
【发布时间】:2019-11-09 15:46:20
【问题描述】:

由于 MultipleBagFetchException,我无法执行简单的 fetchjoin。

@Entity
public class Person {

    @OneToMany(mappedBy="person",fetch=FetchType.LAZY)
    private List<Auto> autos;    
}


@Entity
public class Auto {

    @ManyToOne
    @JoinColumn(name = "person_id", nullable = false)
    private Person person;

   @OneToMany(mappedBy="auto",fetch=FetchType.LAZY)
   private List<Tool> tools;

}

@Entity
@Table(name="tool")
public class Tool {

    @ManyToOne
    @JoinColumn(name = "auto_id", nullable = false)
    private Auto auto;
}

如您所见,我所有的关联都使用默认的 fetchtype。

 @Query("SELECT p FROM Person p JOIN FETCH p.autos a JOIN FETCH a.tools")
 List<Person>findAll();

结果:

Caused by: org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags: [com.example.entities.Person.autos, com.example.entities.Auto.tools]

我已阅读过有关此异常的信息,但在这些情况下,此异常的原因是对集合使用了 EAGER 提取类型。那这个呢?这是最简单的Entity关系。

最重要的是,假设我们不允许触摸实体。 仅在查询端如何解决?

【问题讨论】:

  • 在多个集合提取的情况下解决 MultipleBagFetchException 并非易事。请注意,在您的查询中,您急切地获取多个集合(JOIN FETCH)。 [参考这个答案](stackoverflow.com/questions/13334831/…) 可能的解决方案。您可以尝试不加入 FETCH 工具,让它在访问时延迟加载。或者如果实体可以更新,则将列表更改为设置。
  • 所以如果我们不想接触实体本身,基本上不可能避免这种情况下的 n+1 个查询?

标签: java hibernate


【解决方案1】:

有一种方法可以在不接触实体的情况下避免 n+1 次查询,只需更改 findAll 的查询。我们可以编写一个包装函数,它首先会加载带有汽车的人员,然后他们在一次选择中获取所有工具。

PersonRepository

    @Query("SELECT distinct p FROM Person p JOIN FETCH p.autos a")
    List<Person> findAll();

包装代码

    List<Person> persons = personRepository.findAll();
    Session session = (Session) entityManager.getDelegate();
    List<Auto> autos = new ArrayList<>();
    for (Person person : persons) {
        if(!CollectionUtils.isEmpty(person.getAutos())) {
            autos.addAll(person.getAutos());
        }
    }
    try{
        autos = session.createQuery("select distinct a from Auto a Join fetch a.tools " +
                " where a in :autos", Auto.class)
                .setParameter("autos", autos)
                .setHint(QueryHints.PASS_DISTINCT_THROUGH, false)
                .getResultList();
    } catch (Exception ex) {
        ex.printStackTrace();
    }

第一个查询将是:

SELECT DISTINCT
          person0_.id       AS id1_6_0_,
          autos1_.id        AS id1_0_1_,
          person0_.name     AS name2_6_0_,
          autos1_.name      AS name2_0_1_,
          autos1_.person_id AS person_i3_0_1_,
          autos1_.person_id AS person_i3_0_0__,
          autos1_.id        AS id1_0_0__
FROM
          Person person0_
          INNER JOIN
                    Auto autos1_
          ON
                    person0_.id=autos1_.person_id

生成的第二个查询将是:

    SELECT
          auto0_.id        AS id1_0_0_,
          tools1_.id       AS id1_8_1_,
          auto0_.name      AS name2_0_0_,
          auto0_.person_id AS person_i3_0_0_,
          tools1_.auto_id  AS auto_id3_8_1_,
          tools1_.name     AS name2_8_1_,
          tools1_.auto_id  AS auto_id3_8_0__,
          tools1_.id       AS id1_8_0__
FROM
          Auto auto0_
          INNER JOIN
                    Tool tools1_
          ON
                    auto0_.id=tools1_.auto_id
WHERE
          auto0_.id IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)

除此之外,我认为我们的选择有限,我们将不得不更改 Tool 实体 FetchMode 或为默认 FetchMode.SELECT 添加 BatchSize 以便在单独的查询中获取 Tools。

    @OneToMany(mappedBy = "auto", fetch = FetchType.LAZY)
    @Fetch(FetchMode.SUBSELECT)
    private List<Tool> tools;

查询将是

SELECT
          tools0_.auto_id AS auto_id3_8_1_
        , tools0_.id      AS id1_8_1_
        , tools0_.id      AS id1_8_0_
        , tools0_.auto_id AS auto_id3_8_0_
        , tools0_.name    AS name2_8_0_
FROM
          Tool tools0_
WHERE
          tools0_.auto_id IN
          (
                    SELECT
                              autos1_.id
                    FROM
                              Person person0_
                              INNER JOIN
                                        Auto autos1_
                              ON
                                        person0_.id=autos1_.person_id
          )

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-12-19
    • 1970-01-01
    • 2020-05-10
    • 2022-01-05
    • 2018-06-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多