• Lightrun还可以这样用?


    DefaultLoadEventListener

    当使用Hibernate获取JPA实体时,会触发一个 LoadEvent ,由 DefaultLoadEventListener ,处理方式如下。

    DefaultLoadEventListener 将检查该实体是否位于当前JPA持久化上下文或第一级缓存中。如果在那里找到了实体,那么就会返回相同的对象引用。

    这意味着,两个连续的实体获取调用将总是返回相同的Java Object 引用。这就是JPA和Hibernate提供应用级可重复读取的原因。

    如果在第一级缓存中没有找到实体,Hibernate将尝试从第二级缓存中加载它,如果且仅当第二级缓存被启用。

    最后,如果实体不能从任何缓存中加载,它将被从数据库中加载。

    现在,这个过程可以在调用 EntityManager.find ,在遍历一个关联时发生,也可以间接地用于 FetchType.EAGER 策略。

    检查N+1查询问题

    JPA关联获取验证器文章解释了如何以编程方式断言JPA关联获取。这个工具在测试过程中非常有用,但是对于那些要第一次检查生产系统的顾问来说就不太实用了。

    例如,让我们举一个 Spring PetClinic应用程序的例子。

    1. class="prettyprint hljs less" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">@Entity
    2. @Table(name = "pets")
    3. public class Pet extends NamedEntity {
    4. @Column(name = "birth_date")
    5. @DateTimeFormat(pattern = "yyyy-MM-dd")
    6. private LocalDate birthDate;
    7. @ManyToOne
    8. @JoinColumn(name = "type_id")
    9. private PetType type;
    10. @ManyToOne
    11. @JoinColumn(name = "owner_id")
    12. private Owner owner;
    13. }
  • Pet 实体有两个父关联, type 和 owner ,每个都被注解为 @ManyToOne 。然而,默认情况下, @ManyToOne 关联使用 FetchType.EAGER 的获取策略。

    因此,如果我们加载2个 Pet 实体,同时也获取它们相关的 owner 关联。

    1. class="prettyprint hljs awk" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">List pets = entityManager.createQuery("""
    2. select p
    3. from Pet p
    4. join fetch p.owner
    5. where p.id in :petIds
    6. """)
    7. .setParameter("petIds", List.of(3L, 6L))
    8. .getResultList();
  • Hibernate将执行3次查询。

    1. class="prettyprint hljs sql" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">SELECT 
    2. p.id as id1_1_1_,
    3. p.name as name2_1_1_,
    4. p.birth_date as birth_da3_1_1_,
    5. p.owner_id as owner_id4_1_1_,
    6. p.type_id as type_id5_1_1_,
    7. o.id as id1_0_0_,
    8. o.first_name as first_na2_0_0_,
    9. o.last_name as last_nam3_0_0_,
    10. o.address as address4_0_0_,
    11. o.city as city5_0_0_,
    12. o.telephone as telephon6_0_0_
    13. FROM
    14. pets p
    15. JOIN
    16. owners o ON o.id = p.owner_id
    17. WHERE
    18. p.id IN (3, 6)
    19. SELECT
    20. pt.id as id1_3_0_,
    21. pt.name as name2_3_0_
    22. FROM
    23. types pt
    24. WHERE
    25. pt.id = 3
    26. SELECT
    27. pt.id as id1_3_0_,
    28. pt.name as name2_3_0_
    29. FROM
    30. types pt
    31. WHERE
    32. pt.id = 6

    那么,为什么会有3个查询被执行,而不是只有一个?这就是臭名昭著的N+1查询问题。

    使用Lightrun进行Java性能调优

    虽然你可以使用集成测试来检测N+1查询问题,但有时你不能这样做,因为你被雇来分析的系统已经部署到生产中,而你还没有看到源代码。

    在这种情况下,像Lightrun这样的工具就变得非常方便,因为你可以简单地动态注入一个运行时快照,这个快照只有在满足特定条件时才会被记录。

    第一步是在 DefaultLoadEventListener Hibernate类的 loadFromDatasource 方法中添加一个运行时快照。

    注意,快照只记录相关的 LoadEvent 的 isAssociationFetch() 方法返回 true 。这个条件允许我们捕获N+1查询问题所执行的二次查询。

    现在,当加载所有姓戴维斯的宠物主人时,PetClinic应用程序会执行以下SQL查询。

    1. class="prettyprint hljs sql" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">SELECT DISTINCT 
    2. o.id AS id1_0_0_,
    3. p.id AS id1_1_1_,
    4. o.first_name AS first_na2_0_0_,
    5. o.last_name AS last_nam3_0_0_,
    6. o.address AS address4_0_0_,
    7. o.city AS city5_0_0_,
    8. o.telephone AS telephon6_0_0_,
    9. p.name AS name2_1_1_,
    10. p.birth_date AS birth_da3_1_1_,
    11. p.owner_id AS owner_id4_1_1_,
    12. p.type_id AS type_id5_1_1_,
    13. p.owner_id AS owner_id4_1_0__,
    14. p.id AS id1_1_0__
    15. FROM
    16. owners o
    17. LEFT OUTER JOIN
    18. pets p ON o.id=p.owner_id
    19. WHERE
    20. o.last_name LIKE 'Davis%'
    21. SELECT
    22. pt.id as id1_3_0_,
    23. pt.name as name2_3_0_
    24. FROM
    25. types pt
    26. WHERE
    27. pt.id = 6
    28. SELECT
    29. pt.id as id1_3_0_,
    30. pt.name as name2_3_0_
    31. FROM
    32. types pt
    33. WHERE
    34. pt.id = 3

    而在检查Lightrun快照控制台时,我们可以看到有两条记录被注册。

    第一个快照看起来如下。

    而第二个快照看起来是这样的。

    请注意,由于大量使用了 FetchType.EAGER 策略,这两个快照对应于Spring Petclinic应用程序执行的二级查询。

    结论

    虽然你可以在测试过程中使用JPA关联获取验证器来检测这些N+1查询问题,但如果你的任务是分析一个你从未见过的运行时系统,那么Lightrun就是一个发现各种问题及其发生原因的伟大工具。

  • 相关阅读:
    猿创正文|C++——模板初阶|泛型编程|函数模板|函数模板概念 |函数模板格式|函数模板的实例化|模板参数的匹配原则|类模板 |类模板定义格式|习题
    一行代码让你的项目轻松使用Dapr
    ONNXRuntime学习笔记(四)
    二次封装 Spring Data JPA/MongoDB,打造更易用的数据访问层
    泛型的基础 装箱拆箱
    【空间&单细胞组学】第1期:单细胞结合空间转录组研究PDAC肿瘤微环境
    【剑指 Offer II 032. 有效的变位词】
    下载神器-IDM使用教程及下载
    解决vite+TS中使用element-plus你可能遇到的各种问题
    C++ 学习(19)STL - list容器、set容器
  • 原文地址:https://blog.csdn.net/Java_ttcd/article/details/126135595