• 解决Hibernate N+1问题的3种方法


    FetchType.LAZY用于实体关联时会出现休眠 N+1 问题。如果您执行查询以选择 n 个实体,并且如果您尝试调用实体的惰性关联的任何访问方法,Hibernate 将执行 n 个附加查询来加载延迟获取的对象。

    例如,我们有以下具有一对多书籍集合的作者实体:

    1. public class Author {
    2. @Id
    3. @GeneratedValue(strategy = GenerationType.IDENTITY)
    4. private Integer id;
    5. private String fullName;
    6. @OneToMany(fetch = FetchType.LAZY)
    7. private Set books;
    8. }

    让我们尝试加载所有作者,并打印每个作者的姓名及其书籍收藏大小:

    1. entityManager.createQuery("select a from Author a", Author.class)
    2. .getResultList()
    3. .forEach(a -> System.out.printf("%s had written %d books\n",
    4. a.getFullName(), a.getBooks().size()));

    Hibernate将生成的第一个查询是选择所有作者:

    1. SELECT author0_.id AS id1_0_,
    2. author0_.fullName AS fullname2_0_
    3. FROM authors author0_;

    之后,当我们在书籍集合上调用size() method时,需要初始化这个关联,所以Hibernate将执行一个额外的查询:

    1. SELECT books0_.author_id AS author_i4_1_0_,
    2. books0_.id AS id1_1_0_,
    3. books0_.id AS id1_1_1_,
    4. books0_.author_id AS author_i4_1_1_,
    5. books0_.title AS title2_1_1_,
    6. books0_.year AS year3_1_1_
    7. FROM books books0_
    8. WHERE books0_.author_id=?;

    除了第一个查询之外,当我们打印书籍数量时,将为每个作者调用 n 次此查询。因此,查询总数将等于 N+1。

    Hibernate提供了几种方法来消除此问题:

    1. 第一个解决方案是使用联接获取:
    1. entityManager.createQuery("select a from Author a left join fetch a.books",
    2. Author.class);
    1. SELECT author0_.id AS id1_0_0_,
    2. books1_.id AS id1_1_1_,
    3. author0_.fullName AS fullname2_0_0_,
    4. books1_.author_id AS author_i4_1_1_,
    5. books1_.title AS title2_1_1_,
    6. books1_.year AS year3_1_1_,
    7. books1_.author_id AS author_i4_1_0__,
    8. books1_.id AS id1_1_0__
    9. FROM authors author0_
    10. LEFT OUTER JOIN books books1_ ON author0_.id=books1_.author_id;

    此查询工作正常,但它有一个问题:它不允许我们使用分页,因为限制不会应用于作者。如果指定query.setMaxResults(n),Hibernate将获取所有现有行并在内存中进行分页,从而显着增加内存消耗。

    1. 另一种方法是使用@BatchSize懒惰关联:
    1. public class Author {
    2. @OneToMany(fetch = FetchType.LAZY, mappedBy = "author")
    3. @BatchSize(size = 10)
    4. private Set books;
    5. }

    Hibernate将创建第一个查询来检索所有作者:

    1. SELECT author0_.id AS id1_0_,
    2. author0_.fullName AS fullname2_0_
    3. FROM authors author0_;

    在这种情况下,我们可以轻松地对作者进行分页。然后,当我们在书籍集合上调用size() method时,Hibernate将执行以下查询:

    1. /* load one-to-many Author.books */
    2. SELECT books0_.author_id AS author_i4_1_1_,
    3. books0_.id AS id1_1_1_,
    4. books0_.id AS id1_1_0_,
    5. books0_.author_id AS author_i4_1_0_,
    6. books0_.title AS title2_1_0_,
    7. books0_.year AS year3_1_0_
    8. FROM books books0_
    9. WHERE books0_.author_id in (?, ?, ?, ?, ?, ?, ?, ?, ?, ? /*batch size*/);

    此查询将称为 N/M 次,其中 N 是作者的数量,M 是指定的批大小。我们将总共调用 N/M+1 查询。

    1. 第三种方法是使用返回作者标识符列表的子查询

    Hibernate通过设置@Fetch(FetchMode.SUBSELECT)懒惰关联提供了这个机会:

    1. public class Author {
    2. @OneToMany(fetch = FetchType.LAZY, mappedBy = "author")
    3. @Fetch(FetchMode.SUBSELECT)
    4. private Set books;
    5. }

    第一个查询将加载所有作者:

    1. SELECT author0_.id AS id1_0_,
    2. author0_.fullName AS fullname2_0_
    3. FROM authors author0_;

    第二个查询将使用作者子查询获取书籍:

    1. SELECT books0_.author_id AS author_i4_1_1_,
    2. books0_.id AS id1_1_1_,
    3. books0_.id AS id1_1_0_,
    4. books0_.author_id AS author_i4_1_0_,
    5. books0_.title AS title2_1_0_,
    6. books0_.year AS year3_1_0_
    7. FROM books books0_
    8. WHERE books0_.author_id in
    9. (SELECT author0_.id
    10. FROM authors author0_);

    如果仔细观察 IN 条件,您会发现子查询中的代码几乎重复了第一个查询。如果我们必须执行一次非常复杂的查询两次,它可能会降低性能。为了加快这种情况,我们可以通过第一个查询过滤和页面作者检索他们的 ID。然后我们可以将这些标识符直接传递给第二个查询的子查询:

    1. List authorIds = em.createQuery("select a.id from Author a", Integer.class)
    2. .setFirstResult(5)
    3. .setMaxResults(10)
    4. .getResultList();
    5. List resultList = entityManager.createQuery("select a from Author a"
    6. + " left join fetch a.books"
    7. + " where a.id in :authorIds", Author.class)
    8. .setParameter("authorIds", authorIds)
    9. .getResultList();
  • 相关阅读:
    SpringBoot知识点总结-DX的笔记
    (6)Mybatis-plus DML编程控制
    基于微信小程序的火车购票系统源码
    微信管理系统的便捷功能:自动回复
    Java String.indexOf()方法具有什么功能呢?
    JSON数组(array)
    基于Lattice的密码学
    c++图像的边缘检测
    Sqoop数据导入 第3关:Mysql导入数据至Hive中
    移动端测试理论知识
  • 原文地址:https://blog.csdn.net/allway2/article/details/127652396