• Hibernate 和 Spring Data JPA中的 N+1 问题


    N+1 问题是对象关系映射中的一个性能问题,它为应用程序层的单个选择查询在数据库中触发多个选择查询(确切地说是 N+1,其中 N = 表中的记录数)。休眠和弹簧数据JPA提供了多种方法来捕获和解决此性能问题。

    什么是N+1问题?

    为了理解N+1问题,让我们考虑一个场景。假设我们有一个映射到数据库中表的对象集合,并且每个用户都有使用联接表的集合或映射到表。在 ORM 级别,a 与 具有许多到许多关系。Usert_usersRolet_rolest_user_rolesUserRole

    实体模型
    1. @Entity
    2. @Table(name = "t_users")
    3. public class User {
    4. @Id
    5. @GeneratedValue(strategy=GenerationType.AUTO)
    6. private Long id;
    7. private String name;
    8. @ManyToMany(fetch = FetchType.LAZY)
    9. private Set roles;
    10. //Getter and Setters removed for brevity
    11. }
    12. @Entity
    13. @Table(name = "t_roles")
    14. public class Role {
    15. @Id
    16. @GeneratedValue(strategy= GenerationType.AUTO)
    17. private Long id;
    18. private String name;
    19. //Getter and Setters removed for brevity
    20. }
    一个用户可以有多个角色。角色加载缓慢。

    现在,假设我们要从此表中提取所有用户并打印每个用户的角色。非常幼稚的对象关系实现可以是 -

    使用查找全部方法的用户存储库
    1. public interface UserRepository extends CrudRepository {
    2. List findAllBy();
    3. }

    由 ORM 执行的等效 SQL 查询将是:

    首先获取所有用户 (1)

    Select * from t_users;

    然后获取每个用户执行 N 次的角色(其中 N 是用户数)

    Select * from t_user_roles where userid = ;

    因此,我们需要为“用户”选择一个,并为每个用户获取角色选择 N 个附加选项,其中 N 是用户总数。这是 ORM 中一个经典的 N+1 问题。

    如何识别它?

    休眠提供跟踪选项,用于在控制台/日志中启用 SQL 日志记录。使用日志,您可以轻松查看休眠是否正在为给定呼叫发出 N+1 查询。

    在应用程序中启用 SQL 日志记录。
    1. spring:
    2. jpa:
    3. show-sql: true
    4. database-platform: org.hibernate.dialect.MySQL8Dialect
    5. hibernate:
    6. ddl-auto: create
    7. use-new-id-generator-mappings: true
    8. properties:
    9. hibernate:
    10. type: trace
    在跟踪中启用 SQL 日志记录。
    我们也必须启用此功能,以便在日志中显示sql查询。
    在日志中打印的典型 N+1 SQL 日志
    1. 2017-12-23 07:42:30.923 INFO 11657 --- [ main] hello.UserService : Customers found with findAll():
    2. Hibernate: select user0_.id as id1_1_, user0_.name as name2_1_ from user user0_
    3. Hibernate: select roles0_.user_id as user_id1_2_0_, roles0_.roles_id as roles_id2_2_0_, role1_.id as id1_0_1_, role1_.name as name2_0_1_ from user_roles roles0_ inner join role role1_ on roles0_.roles_id=role1_.id where roles0_.user_id=?
    4. Hibernate: select roles0_.user_id as user_id1_2_0_, roles0_.roles_id as roles_id2_2_0_, role1_.id as id1_0_1_, role1_.name as name2_0_1_ from user_roles roles0_ inner join role role1_ on roles0_.roles_id=role1_.id where roles0_.user_id=?
    5. Hibernate: select roles0_.user_id as user_id1_2_0_, roles0_.roles_id as roles_id2_2_0_, role1_.id as id1_0_1_, role1_.name as name2_0_1_ from user_roles roles0_ inner join role role1_ on roles0_.roles_id=role1_.id where roles0_.user_id=?
    6. Hibernate: select roles0_.user_id as user_id1_2_0_, roles0_.roles_id as roles_id2_2_0_, role1_.id as id1_0_1_, role1_.name as name2_0_1_ from user_roles roles0_ inner join role role1_ on roles0_.roles_id=role1_.id where roles0_.user_id=?

    如果看到给定选择查询的 SQL 有多个条目,则很有可能是由于 N+1 问题。

    N+1 分辨率

    休眠和弹簧数据JPA提供了解决N + 1 ORM问题的机制。

    在 SQL 级别,ORM 需要实现的是避免 N+1,即触发一个连接两个表的查询,并在单个查询中获取组合结果。

    提取在单个查询中检索所有内容(用户和角色)的联接 SQL
    Hibernate: select user0_.id as id1_1_0_, role2_.id as id1_0_1_, user0_.name as name2_1_0_, role2_.name as name2_0_1_, roles1_.user_id as user_id1_2_0__, roles1_.roles_id as roles_id2_2_0__ from user user0_ left outer join user_roles roles1_ on user0_.id=roles1_.user_id left outer join role role2_ on roles1_.roles_id=role2_.id
    或普通 SQL
    select user0_.id, role2_.id, user0_.name, role2_.name, roles1_.user_id, roles1_.roles_id from user user0_ left outer join user_roles roles1_ on user0_.id=roles1_.user_id left outer join role role2_ on roles1_.roles_id=role2_.id

    1. 春季数据JPA方法

    如果我们使用的是弹簧数据JPA,那么我们有两个选项来实现这一点 - 使用实体图或使用带有提取连接的选择查询。

    1. public interface UserRepository extends CrudRepository {
    2. List findAllBy();
    3. @Query("SELECT p FROM User p LEFT JOIN FETCH p.roles")
    4. List findWithoutNPlusOne();
    5. @EntityGraph(attributePaths = {"roles"})
    6. List findAll();
    7. }
    在数据库级别发出 N+1 个查询
    使用左联接获取,我们解决了 N+1 问题
    使用属性路径,弹簧数据JPA避免了N + 1问题

    2. 休眠方法

    如果它是纯休眠,则以下解决方案将起作用。

    使用 HQL 查询
    "from User u join fetch u.roles roles roles"
    使用休眠条件 API
    1. Criteria criteria = session.createCriteria(User.class);
    2. criteria.setFetchMode("roles", FetchMode.EAGER);

    在引擎盖下,所有这些方法的工作原理都相似,并且它们发出类似的数据库查询left join fetch

    就这样!

  • 相关阅读:
    休闲微信小游戏
    mysql常用语句之DDL:对数据库和表的操作
    Volcano:在离线作业混部管理平台,实现智能资源管理和作业调度
    kali linux实现arp攻击对方主机
    Python股票常用接口
    电商前台项目(二):完成Home首页模块业务
    0003号因子测试结果、代码和数据
    淘宝API常用接口与获取方式
    Emiya 家今天的饭(计数4, dp19)
    [附源码]计算机毕业设计JAVA疫苗接种管理系统
  • 原文地址:https://blog.csdn.net/allway2/article/details/127122642