内容提要
本文是对Jos Roseboom的访谈。他因生产事故关注JPA性能:连接被外部系统占满、实体关联查询过多导致缓慢。他建议将数据库连接视为预算,避免闲置占用;用Prometheus、Grafana、Gatling等监控压测。新项目仍推荐Spring Data JPA加Hibernate,但须理解SQL。他还想深入探讨REQUIRES_NEW死锁、实体状态与LazyInitializationException、equals和hashCode。
延伸解读
连接池是预算,不是无限资源
Jos Roseboom 将数据库最大连接数视为必须分配给所有应用实例的预算。如果多个实例或多个应用共享同一个 DataSource,每个实例都配置大连接池并不能保证连接可用。开发者需要了解整个设置的连接限制,并避免在不需要与数据库交互时占用连接,否则可能因外部系统问题导致连接耗尽,使系统无响应。
监控与压测:提前发现性能瓶颈
Jos 推荐使用 Prometheus 和 Grafana 监控应用指标,如最慢的端点和消耗服务器时间最多的端点。频繁调用但稍慢的端点可能比极少调用的极慢端点影响更大。开发时可用 Gatling 进行负载测试,用 IntelliJ 分析器诊断,用 datasource-proxy 记录查询。这些工具帮助在开发阶段发现性能问题,避免影响生产。
新项目仍可选 JPA,但必须理解 SQL
对于大多数使用关系数据库的应用,Jos 仍会选择 Spring Data JPA 加 Hibernate。写数据时喜欢 Hibernate 的状态管理,读数据时喜欢 Spring Data JPA 的仓库查询,因为 JPQL 可在启动时验证,拼写错误能快速失败。但他强调,不能把 Hibernate 当作避免理解 SQL 的方式,仍需了解底层发生了什么,否则可能因关联查询过多导致性能问题。
值得深入探讨的 JPA 陷阱
Jos 提到,如果时间更充裕,他会深入讨论连接池中由 REQUIRES_NEW 事务传播导致的死锁如何耗尽连接池。在 JPA 方面,他会花更多时间讲解不同实体状态及其与 LazyInitializationException 的关系,以及实体的 equals 和 hashCode 方法。这些主题在实际开发中容易出错,值得开发者关注。
Q&A
Jos Roseboom 为什么开始关注 JPA 性能问题?
他因亲身经历的生产事故而关注:一次系统因所有数据库连接被等待外部系统的请求占满而完全无响应;另一次应用因通过获取实体再遍历关联来收集数据,导致查询过多而变得异常缓慢。这些反复出现的问题促使他决定分享这个话题。
如何正确管理数据库连接池以避免连接耗尽?
应将最大数据库连接数视为一个预算,在所有使用该数据库的应用实例间分配。开发者需了解整个设置的限制,不能简单地为每个实例配置大连接池并假设连接都可用。同时,确保不需要与数据库交互时不要持有连接。
Jos 推荐哪些工具来监控和优化 Java 应用性能?
他推荐使用 Prometheus 和 Grafana 自行搭建监控,获取最慢端点及消耗服务器时间最多的端点等指标;开发时用 Gatling 进行负载测试,用 IntelliJ 分析器诊断;用 datasource-proxy 记录查询;用 Hypersistence Optimizer 正确使用 JPA。
在新项目中,Jos 是否仍推荐使用 JPA 和 Hibernate?
对于大多数使用关系数据库的应用,他仍会选择 Spring Data JPA 加 Hibernate。写数据时喜欢 Hibernate 的状态管理;读数据时喜欢 Spring Data JPA 的组合,仓库查询可在应用启动时验证,JPQL 可读性好。但强调不能把 Hibernate 当作避免理解 SQL 的方式。
如果举办更长的 workshop,Jos 还想深入讨论哪些性能问题?
他想讨论:连接池部分中,使用 REQUIRES_NEW 事务传播导致的死锁如何耗尽连接池;JPA 方面,不同实体状态及其与 LazyInitializationException 的关系;以及实体的 equals 和 hashCode 方法。
使用 Hibernate 时为什么还需要理解 SQL?
因为如果不理解 SQL,可能会误以为不需要了解底层数据库操作,从而通过获取实体再遍历关联来收集数据,导致产生过多查询。这在开发机上可能不是问题,但在生产环境中会成为噩梦。