使用Lightrun对Java应用程序进行性能调整

简介: 简介在这篇文章中,我将向你展示使用Lightrun分析一个Java应用程序,这样你就可以发现各种性能调整的改进,你可以应用到你当前的Java应用程序。在上一篇文章中,我解释了什么是Lightrun,以及你如何使用它来注入动态日志、捕获运行时快照或添加动态指标。在这篇文章中,我将使用Lightrun作为我的JPA关联获取验证器的替代品。

简介

在这篇文章中,我将向你展示使用Lightrun分析一个Java应用程序,这样你就可以发现各种性能调整的改进,你可以应用到你当前的Java应用程序。

在上一篇文章中,我解释了什么是Lightrun,以及你如何使用它来注入动态日志、捕获运行时快照或添加动态指标。

在这篇文章中,我将使用Lightrun作为我的JPA关联获取验证器的替代品。

DefaultLoadEventListener

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

网络异常,图片无法展示
|

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

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

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

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

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

检查N+1查询问题

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

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

@Entity
@Table(name = "pets")
public class Pet extends NamedEntity {
    @Column(name = "birth_date")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate birthDate;
    @ManyToOne
    @JoinColumn(name = "type_id")
    private PetType type;
    @ManyToOne
    @JoinColumn(name = "owner_id")
    private Owner owner;
}
复制代码

Pet 实体有两个父关联, typeowner ,每个都被注解为 @ManyToOne 。然而,默认情况下, @ManyToOne 关联使用 FetchType.EAGER 的获取策略。

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

List<Pet> pets = entityManager.createQuery("""
    select p
    from Pet p
    join fetch p.owner
    where p.id in :petIds
    """)
.setParameter("petIds", List.of(3L, 6L))
.getResultList();
复制代码

Hibernate将执行3次查询。

SELECT 
    p.id as id1_1_1_,
    p.name as name2_1_1_, 
    p.birth_date as birth_da3_1_1_, 
    p.owner_id as owner_id4_1_1_, 
    p.type_id as type_id5_1_1_, 
    o.id as id1_0_0_, 
    o.first_name as first_na2_0_0_, 
    o.last_name as last_nam3_0_0_, 
    o.address as address4_0_0_, 
    o.city as city5_0_0_, 
    o.telephone as telephon6_0_0_
FROM 
    pets p 
JOIN 
    owners o ON o.id = p.owner_id 
WHERE 
    p.id IN (3, 6)
SELECT 
    pt.id as id1_3_0_, 
    pt.name as name2_3_0_ 
FROM 
    types pt 
WHERE 
    pt.id = 3
SELECT 
    pt.id as id1_3_0_, 
    pt.name as name2_3_0_ 
FROM 
    types pt 
WHERE 
    pt.id = 6
复制代码

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

使用Lightrun进行Java性能调优

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

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

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

网络异常,图片无法展示
|

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

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

SELECT DISTINCT 
    o.id AS id1_0_0_,
    p.id AS id1_1_1_,
    o.first_name AS first_na2_0_0_,
    o.last_name AS last_nam3_0_0_,
    o.address AS address4_0_0_,
    o.city AS city5_0_0_,
    o.telephone AS telephon6_0_0_,
    p.name AS name2_1_1_,
    p.birth_date AS birth_da3_1_1_,
    p.owner_id AS owner_id4_1_1_,
    p.type_id AS type_id5_1_1_,
    p.owner_id AS owner_id4_1_0__,
    p.id AS id1_1_0__
FROM 
    owners o
LEFT OUTER JOIN 
    pets p ON o.id=p.owner_id
WHERE 
    o.last_name LIKE 'Davis%'
SELECT 
    pt.id as id1_3_0_, 
    pt.name as name2_3_0_ 
FROM 
    types pt 
WHERE 
    pt.id = 6
SELECT 
    pt.id as id1_3_0_, 
    pt.name as name2_3_0_ 
FROM 
    types pt 
WHERE 
    pt.id = 3
复制代码

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

第一个快照看起来如下。

网络异常,图片无法展示
|

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

网络异常,图片无法展示
|

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

很酷,对吗?

结论

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

特别是因为Java性能调优是我被雇用的最常见的原因之一,Lightrun是我的工具集的一个很好的补充。


相关文章
|
1月前
|
存储 缓存 Java
Java 12相比Java 11有哪些性能上的提升?
Java 12相比Java 11有哪些性能上的提升?
66 3
|
1月前
|
Java 测试技术 API
Java Stream API:被低估的性能陷阱与优化技巧
Java Stream API:被低估的性能陷阱与优化技巧
300 114
|
1月前
|
人工智能 算法 Java
Java与AI驱动区块链:构建智能合约与去中心化AI应用
区块链技术和人工智能的融合正在开创去中心化智能应用的新纪元。本文深入探讨如何使用Java构建AI驱动的区块链应用,涵盖智能合约开发、去中心化AI模型训练与推理、数据隐私保护以及通证经济激励等核心主题。我们将完整展示从区块链基础集成、智能合约编写、AI模型上链到去中心化应用(DApp)开发的全流程,为构建下一代可信、透明的智能去中心化系统提供完整技术方案。
234 3
|
1月前
|
消息中间件 缓存 Java
Spring框架优化:提高Java应用的性能与适应性
以上方法均旨在综合考虑Java Spring 应该程序设计原则, 数据库交互, 编码实践和系统架构布局等多角度因素, 旨在达到高效稳定运转目标同时也易于未来扩展.
125 8
|
1月前
|
安全 Java
Java异常处理:程序世界的“交通规则
Java异常处理:程序世界的“交通规则
310 98
|
2月前
|
人工智能 Java API
Java与大模型集成实战:构建智能Java应用的新范式
随着大型语言模型(LLM)的API化,将其强大的自然语言处理能力集成到现有Java应用中已成为提升应用智能水平的关键路径。本文旨在为Java开发者提供一份实用的集成指南。我们将深入探讨如何使用Spring Boot 3框架,通过HTTP客户端与OpenAI GPT(或兼容API)进行高效、安全的交互。内容涵盖项目依赖配置、异步非阻塞的API调用、请求与响应的结构化处理、异常管理以及一些面向生产环境的最佳实践,并附带完整的代码示例,助您快速将AI能力融入Java生态。
467 12
|
2月前
|
Java Spring
如何优化Java异步任务的性能?
本文介绍了Java中四种异步任务实现方式:基础Thread、线程池、CompletableFuture及虚拟线程。涵盖多场景代码示例,展示从简单异步到复杂流程编排的演进,适用于不同版本与业务需求,助你掌握高效并发编程实践。(239字)
227 6
|
2月前
|
安全 Java API
Java SE 与 Java EE 区别解析及应用场景对比
在Java编程世界中,Java SE(Java Standard Edition)和Java EE(Java Enterprise Edition)是两个重要的平台版本,它们各自有着独特的定位和应用场景。理解它们之间的差异,对于开发者选择合适的技术栈进行项目开发至关重要。
400 1
存储 jenkins 持续交付
481 2