JPA 和 Hibernate:预加载执行后续查询以获取所有数据,而不是只在一个查询中执行

JPA & Hibernate: Eager loading performing subsequent queries to fetch all data, instead of doing it in just one query

我有以下疑问。我想知道为什么在使用 JPA 和 Hibernate 时,在 ManyToOneOneToMany 关系中执行预加载时,它会按顺序调用数据库获取实体信息,但另外,生成后续查询以获取每个子项。

另一方面,当使用带有 JOIN FETCH 的查询时,它会按我预期的那样执行查询,同时获取所有信息,因为 fetchType 表示为 "EAGER"。

这是一个简单的例子:

我有 Class 学生,它与 Class Class 房间有 ManyToOne 关系。

@Entity
@Table(name = "STUDENT")
public class Student {

@ManyToOne(optional = true, fetch = FetchType.EAGER)
        @JoinColumn(name = "ClassroomID")
        private Classroom mainClass;

另一边有class名为Class的房间如下:

@Entity
public class Classroom {

@OneToMany(cascade = CascadeType.ALL, mappedBy = "mainClass", fetch = FetchType.EAGER)
private List<Student> studentsList;

在获取Classroom对象时,先查询一次获取自己的信息,再查询一次获取studentsList中每个学生的信息对于每个 classRoom 对象。

第一次查询:

Hibernate: 
/* SELECT
         r 
     FROM
         Classroom r 
     LEFT JOIN
         r.classStudents */ 

     select
         classroom0_.id as id1_0_,
         classroom0_.number as number2_0_ 
     from
         Classroom classroom0_ 
     left outer join
         STUDENT classstude1_ 
             on classroom0_.id=classstude1_.ClassroomID

然后它执行下一个查询的次数与分配给每个 class 房间的学生一样多。

  Hibernate: 
      /* load one-to-many com.hw.access.Classroom.classStudents */ 
      select
          classstude0_.ClassroomID as Classroo4_0_1_,
          classstude0_.id as id1_1_1_,
          classstude0_.id as id1_1_0_,
          classstude0_.FIRST_NAME as FIRST_NA2_1_0_,
          classstude0_.LAST_NAME as LAST_NAM3_1_0_,
          classstude0_.ClassroomID as Classroo4_1_0_ 
      from
          STUDENT classstude0_ 
      where
          classstude0_.ClassroomID=?

问题是:为什么不一次获取所有信息?为什么它不在一次查询中获取信息?因为它已经在那里执行 Join 子句.

为什么只在查询中显式添加 Fetch 时,它会按要求执行?

例如:

SELECT
         r 
     FROM
         Classroom r 
     LEFT JOIN FETCH
         r.classStudents */ 

然后,输出查询确实在一个查询中获取了所有信息:

Hibernate: 

        select
              classroom0_.id as id1_0_0_,
              classstude1_.id as id1_1_1_,
              classroom0_.number as number2_0_0_,
              classstude1_.FIRST_NAME as FIRST_NA2_1_1_,
              classstude1_.LAST_NAME as LAST_NAM3_1_1_,
              classstude1_.ClassroomID as Classroo4_1_1_,
              classstude1_.ClassroomID as Classroo4_0_0__,
              classstude1_.id as id1_1_0__ 
          from
              Classroom classroom0_ 
          left outer join
              STUDENT classstude1_ 
                  on classroom0_.id=classstude1_.ClassroomID

默认情况下,fetchtype 是惰性的,这意味着如果您不在请求中请求列表,Hibernate 将不会收集它。

在第一个请求中,您要求提供 Classroom r 的所有属性,包括学生列表,因此 Hibernate 将延迟加载它们(在发现您需要它们之后)。

但是当 fetchtype 设置为 eager hibernate 时,即使你不询问它也会收集它。

因为您有从 ClassroomStudentOneToMany 关系,使用单个查询会导致每一行重复 Classroom 字段。 现在假设你有第二个 OneToManyClassroomCourse 的关系;如果对于给定的 Classroom,您有 N Student 和 M Course,您将有一个查询返回 N+M 行,每行包含 class 的相同字段Classroom.

我发现它在 https://vladmihalcea.com/eager-fetching-is-a-code-smell/ 中有描述 在 EAGER 下获取不一致:

Both JPQL and Criteria queries default to select fetching, therefore issuing a secondary select for each individual EAGER association. The larger the associations’ number, the more additional individual SELECTS, the more it will affect our application performance.

另外,请注意 Hibernate 同样会忽略 HQL 查询的提取注释: https://developer.jboss.org/wiki/HibernateFAQ-AdvancedProblems#jive_content_id_Hibernate_ignores_my_outerjointrue_or_fetchjoin_setting_and_fetches_an_association_lazily_using_n1_selects

Hibernate ignores my outer-join="true" or fetch="join" setting and fetches an association lazily, using n+1 selects!

HQL queries always ignore the setting for outer-join or fetch="join" defined in mapping metadata. This setting applies only to associations fetched using get() or load(), Criteria queries, and graph navigation. If you need to enable eager fetching for a HQL query, use an explicit LEFT JOIN FETCH.