【问题标题】:spring boot repository findAll() too slowspring boot 存储库 findAll() 太慢了
【发布时间】:2022-01-13 10:27:32
【问题描述】:

当我为报告目的获取表格时,我在新的 Spring Boot 应用程序中遇到了奇怪的缓慢。

这是一个简单的表,没有引用其他表,它有 5 列。行是 50k。 因此,我使用了 JpaRepository 中提供的简单 findAll() 方法。

当“目标”结果是实体时,findAll() 执行需要 5 分钟。 当我设置 DTO 类投影或接口投影时,执行需要 1-2 分钟。

我认为对于这么多的数据来说这仍然太多了。

此外,休眠统计提供了 0.5 秒的执行时间。剩下的 1-2 分钟获取 DTO 类型的数据需要什么?

【问题讨论】:

  • 您是在开发环境还是生产环境中进行测试?调试时也要考虑断点
  • 增加查询的获取大小,但是创建大约 50k 的对象需要一些时间。对象创建相对较慢,还要确保你的 bean 有正确的 equals 和 hashcode 实现。
  • 增加获取大小没有帮助

标签: java spring-boot spring-data-jpa


【解决方案1】:

这个问题的最终解决方案是从 JPA 存储库更改为 JdbcTemplate。

我构建了一个查询,并使用数据映射器参数将我的列表转换为 Java 中的 DTO 类型。 在使用 JPA 查询进行了多次试验之后,速度令人难以置信。 JPA 是 5 分钟,而 JdbcTemplate.query() 是 2 秒。

毕竟我想知道从 Spring 4.3 到 Spring Boot 2.5.5 的变化

【讨论】:

  • 信不信由你,这个问题的根源是 log4j。在运行良好的应用程序中,快速的存储库查询是 2.9.1,但是当我升级到 2.15 或 2.16(由于漏洞问题)时,查询变得非常慢。最后,我删除了 log4j 包并替换为 logback,并且存储库查询运行良好,并且再次快速。
【解决方案2】:

当然是序列化/反序列化过程很麻烦,需要太多内存。你可以做很多事情:

  • 增加应用程序的内存参数。请参阅 JVM 内存选项。
  • 使用可以比默认 JDK 系统更快/更好地处理序列化的 java 库。 Jackson 挺好的。
  • 建立一个缓存系统来存储对象并更快地检索它们。你可以使用 Spring @Cacheable 注解,@see https://www.baeldung.com/spring-cache-tutorial,或者你可以使用 Google guava libs 来获得一个相当不错的缓存系统。
  • 优化您的数据模型以获得更快的请求/响应。请参阅使用 Fetch.EAGER / Fetch.LAZY diffs 加载对象以及如何为您的应用程序获得优化的方法。
  • 使用Pagination John Thompson 解释得很好。这将是检索一小组结果并让用户在其中从 10 到 10 或从 50 到 50 导航的更快方法。
  • 使用 Redis 或 MongoDB 等 NoSQL 数据库为您的前端获取 JSON 对象:https://www.mongodb.com/compatibility/spring-boot

通过分页示例,您可以在 Spring Boot 应用程序中快速显示数据集:

public interface ProductRepository extends PagingAndSortingRepository<Product, Integer> {

    List<Product> findAllByPrice(double price, Pageable pageable);
}

https://www.baeldung.com/spring-data-jpa-pagination-sorting 开始的代码,您可以按照说明使其运行良好,这无疑是使事情运行良好的最佳方式。

【讨论】:

  • Jackson 不会帮助提高findAll 的速度。还应避免使用@Cacheable 缓存实体(为此,请为您的 jpa 提供程序使用二级缓存)。
  • @M.Deinum :findAll() 很快(0.5 秒),所以它不是问题所在。因此,使用 Jackson 构建由用户界面加载的 JSON 对象会更快……如果做得好的话。对于来自 JPA 的可缓存对象,这没问题,但我已经谈到了这一点,因为他可以将它用于它自己的 DTO 类,它一次构建了很长时间。这取决于数据是否经常变化。
  • 再次使用 Jackson 将 JDBC 结果映射到 DTO 不会变得更快。 JPA 托管实体的可缓存是一个坏主意,因为您需要自己复制 JPA 行为来管理缓存。这完全是关于将结果从 JDBC 映射到 DTO而不是关于 Web 层中的编组/序列化。
  • 首先,我只谈到了 DTO 缓存,一次构建,多次服务。不多也不少。
  • 在那之后,如果 OP 需要非常快的东西,可以直接从 Redis 等 NoSQL 数据库提供 JSON 服务,使用 JSON 对象当然是最好的选择。因此,Java 映射相关的东西根本不会浪费时间。
【解决方案3】:

@Benda 给出了很好的指示,附加 jvisualvm 并使用 CPU 分析来查看花费的时间会有所帮助。由于行的数量很大,驱动程序通常会进行多次往返以获取大量数据以避免内存不足的情况,并且当它们被休眠加载时 - 它会在第一级缓存中累积。我曾经遇到过事务未标记为只读的情况,因此最后 Hibernate 试图进行脏检查以查看是否有更改。 另外我建议您集成Javamelody。它是免费的、开源的并且非常容易使用 Spring Boot 进行设置(只需在构建中包含一个依赖项)。它可以通过服务级别详细信息和 SQL 计时告诉您在每个用例中花费的时间 - 所有这些都具有良好的可视化仪表板。

【讨论】:

  • 是的,它是连接 jconsole 并查看会发生什么的好指针;)。 Javamelody 也是一个非常好的工具 ;)
猜你喜欢
  • 2021-06-27
  • 2019-08-22
  • 2018-06-15
  • 2018-11-07
  • 2018-01-02
  • 2021-11-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多