FetchType.LAZY
用于实体关联时会出现休眠 N+1 问题。如果您执行查询以选择 n 个实体,并且如果您尝试调用实体的惰性关联的任何访问方法,Hibernate 将执行 n 个附加查询来加载延迟获取的对象。
例如,我们有以下具有一对多书籍集合的作者实体:
- public class Author {
- @Id
- @GeneratedValue(strategy = GenerationType.IDENTITY)
- private Integer id;
- private String fullName;
- @OneToMany(fetch = FetchType.LAZY)
- private Set
books; - }
让我们尝试加载所有作者,并打印每个作者的姓名及其书籍收藏大小:
- entityManager.createQuery("select a from Author a", Author.class)
- .getResultList()
- .forEach(a -> System.out.printf("%s had written %d books\n",
- a.getFullName(), a.getBooks().size()));
Hibernate将生成的第一个查询是选择所有作者:
- SELECT author0_.id AS id1_0_,
- author0_.fullName AS fullname2_0_
- FROM authors author0_;
之后,当我们在书籍集合上调用size()
method时,需要初始化这个关联,所以Hibernate将执行一个额外的查询:
- SELECT books0_.author_id AS author_i4_1_0_,
- books0_.id AS id1_1_0_,
- books0_.id AS id1_1_1_,
- books0_.author_id AS author_i4_1_1_,
- books0_.title AS title2_1_1_,
- books0_.year AS year3_1_1_
- FROM books books0_
- WHERE books0_.author_id=?;
除了第一个查询之外,当我们打印书籍数量时,将为每个作者调用 n 次此查询。因此,查询总数将等于 N+1。
- entityManager.createQuery("select a from Author a left join fetch a.books",
- Author.class);
- SELECT author0_.id AS id1_0_0_,
- books1_.id AS id1_1_1_,
- author0_.fullName AS fullname2_0_0_,
- books1_.author_id AS author_i4_1_1_,
- books1_.title AS title2_1_1_,
- books1_.year AS year3_1_1_,
- books1_.author_id AS author_i4_1_0__,
- books1_.id AS id1_1_0__
- FROM authors author0_
- LEFT OUTER JOIN books books1_ ON author0_.id=books1_.author_id;
此查询工作正常,但它有一个问题:它不允许我们使用分页,因为限制不会应用于作者。如果指定query.setMaxResults(n)
,Hibernate将获取所有现有行并在内存中进行分页,从而显着增加内存消耗。
@BatchSize
懒惰关联:- public class Author {
- …
- @OneToMany(fetch = FetchType.LAZY, mappedBy = "author")
- @BatchSize(size = 10)
- private Set
books; - }
Hibernate将创建第一个查询来检索所有作者:
- SELECT author0_.id AS id1_0_,
- author0_.fullName AS fullname2_0_
- FROM authors author0_;
在这种情况下,我们可以轻松地对作者进行分页。然后,当我们在书籍集合上调用size()
method时,Hibernate将执行以下查询:
- /* load one-to-many Author.books */
- SELECT books0_.author_id AS author_i4_1_1_,
- books0_.id AS id1_1_1_,
- books0_.id AS id1_1_0_,
- books0_.author_id AS author_i4_1_0_,
- books0_.title AS title2_1_0_,
- books0_.year AS year3_1_0_
- FROM books books0_
- WHERE books0_.author_id in (?, ?, ?, ?, ?, ?, ?, ?, ?, ? /*batch size*/);
此查询将称为 N/M 次,其中 N 是作者的数量,M 是指定的批大小。我们将总共调用 N/M+1 查询。
Hibernate通过设置@Fetch(FetchMode.SUBSELECT)
懒惰关联提供了这个机会:
- public class Author {
- …
- @OneToMany(fetch = FetchType.LAZY, mappedBy = "author")
- @Fetch(FetchMode.SUBSELECT)
- private Set
books; - }
第一个查询将加载所有作者:
- SELECT author0_.id AS id1_0_,
- author0_.fullName AS fullname2_0_
- FROM authors author0_;
第二个查询将使用作者子查询获取书籍:
- SELECT books0_.author_id AS author_i4_1_1_,
- books0_.id AS id1_1_1_,
- books0_.id AS id1_1_0_,
- books0_.author_id AS author_i4_1_0_,
- books0_.title AS title2_1_0_,
- books0_.year AS year3_1_0_
- FROM books books0_
- WHERE books0_.author_id in
- (SELECT author0_.id
- FROM authors author0_);
如果仔细观察 IN 条件,您会发现子查询中的代码几乎重复了第一个查询。如果我们必须执行一次非常复杂的查询两次,它可能会降低性能。为了加快这种情况,我们可以通过第一个查询过滤和页面作者检索他们的 ID。然后我们可以将这些标识符直接传递给第二个查询的子查询:
- List
authorIds = em.createQuery("select a.id from Author a", Integer.class) - .setFirstResult(5)
- .setMaxResults(10)
- .getResultList();
- List
resultList = entityManager.createQuery("select a from Author a" - + " left join fetch a.books"
- + " where a.id in :authorIds", Author.class)
- .setParameter("authorIds", authorIds)
- .getResultList();