You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot加载大量数据时JPA/Hibernate性能下降原因探究

问题:Hibernate Session缓存累积导致单Rest调用内性能持续下降

在Spring Data配合Rest调用的场景中,Hibernate会将加载的实体缓存到Session中,直到整个Rest调用结束。当在单个Rest请求内批量加载大量实体时,Hibernate的持久化上下文会不断累积数据,这些实体因为被Hibernate持有引用无法被垃圾回收,最终导致Spring应用性能显著下降。

当前场景仅处理1500个元素左右,内存充足(未触发OOM),但前100个实体处理速度正常,后续执行耗时逐渐增加。示例代码未显式声明事务,调用entityManager.clear()后性能问题消失。


核心代码片段

public void someMethodCalledFromRestController() {
    List<String> allRelevantNumbers = someService.findNumbers();
    for (String number : allRelevantNumbers) {
        List<Customers> customersGroup = someService.findActiveCustomersByNumber(number); // 内部调用repository.find()
        this.workOnCustomerGroup(customersGroup);
    }
}

....

public void workOnCustomerGroup(List<Customers> customersGroup) {
    try {
        LocalDateTime startWorkTime = LocalDateTime.now();
        Result result = someService.doSomeWork(customersGroup);
        resultService.createResult(result); // 内部调用repository.save();

        // entityManager.clear();
        // Map.Entry<Object, EntityEntry>[] entries = entityManager.unwrap(SessionImplementor.class).getPersistenceContext().reentrantSafeEntityEntries();

        log.info("Work took " + ChronoUnit.MILLIS.between(startWorkTime, LocalDateTime.now()) +
                " ms. ");
    } catch (Exception e) {
        // 异常处理
    }
}

性能日志

2023-03-27 08:29:37.904  INFO 12084 --- [io-12710-exec-1] Work took 352 ms. 
2023-03-27 08:29:38.338  INFO 12084 --- [io-12710-exec-1] Work took 384 ms. 
2023-03-27 08:29:38.748  INFO 12084 --- [io-12710-exec-1] Work took 357 ms. 
2023-03-27 08:29:39.133  INFO 12084 --- [io-12710-exec-1] Work took 354 ms. 
....
2023-03-27 08:37:54.978  INFO 12084 --- [io-12710-exec-1] Work took 2685 ms.
2023-03-27 08:37:57.695  INFO 12084 --- [io-12710-exec-1] Work took 2699 ms.
2023-03-27 08:38:00.472  INFO 12084 --- [io-12710-exec-1] Work took 2745 ms.
2023-03-27 08:38:03.211  INFO 12084 --- [io-12710-exec-1] Work took 2726 ms.

原因分析

  • 持久化上下文持续累积:Spring处理Rest请求时,默认会为整个请求周期创建一个Hibernate Session(持久化上下文)。每次repository.find()加载的Customers实体都会被缓存到这个Session中,循环次数越多,Session内的实体数量就越大。
  • 性能损耗触发点:
    • Hibernate在执行实体操作(关联检查、脏数据比对等)时,需要遍历整个持久化上下文,上下文越大,遍历耗时越长。
    • 即使内存充足未触发OOM,大量存活对象会增加垃圾回收的扫描时间,间接拖慢整体性能。
  • 无显式事务的影响:即便没有手动声明事务,Spring Data的Repository方法默认会开启事务(查询用只读事务,保存用读写事务),但事务生命周期绑定当前Session,不会在每次循环后自动清理上下文。

解决方案

  1. 手动清理Session缓存:在每组Customers处理完成后,调用entityManager.clear()清空持久化上下文,释放实体引用,让GC可以回收这些对象。修改后的方法如下:
public void workOnCustomerGroup(List<Customers> customersGroup) {
    try {
        LocalDateTime startWorkTime = LocalDateTime.now();
        Result result = someService.doSomeWork(customersGroup);
        resultService.createResult(result);

        entityManager.clear(); // 清理Session缓存

        log.info("Work took " + ChronoUnit.MILLIS.between(startWorkTime, LocalDateTime.now()) +
                " ms. ");
    } catch (Exception e) {
        // 异常处理
    }
}
  1. 采用批量加载模式:如果业务允许,将查询改为分页加载或使用ScrollableResults,减少单次加载到Session中的实体数量。
  2. 调整Hibernate配置:开启JDBC批量操作(设置hibernate.jdbc.batch_size)减少数据库交互次数;设置hibernate.max_fetch_depth限制关联对象加载深度,避免不必要的实体缓存。

内容的提问来源于stack exchange,提问作者GJohannes

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 20:18:08