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

网络恢复后无法打开JPA EntityManager进行事务的问题解决

问题解决:网络恢复后JPA Repository持续抛出连接关闭异常

问题背景

存在一个包含5000次迭代的长循环,每次循环都会调用以下代码执行数据库查询:

if(!runnedPedidosToBulkOrCronjobClientesService.checkIsRunned(pedido.getId())){
    return;
}

其中checkIsRunned方法实现如下:

public Boolean checkIsRunned(Integer id_pedido){
    return runnedPedidosToBulkOrCronjobClientesRepository.findById(id_pedido).isEmpty();
}

所用Repository为依赖注入的JPA Repository实例:

@Repository
public interface RunnedPedidosToBulkOrCronjobClientesRepository extends JpaRepository<RunnedPedidosToBulkOrCronjobClientesEntidade,Integer> {
}

该循环由HTTP请求触发,正常执行无异常,但网络中断恢复后,每次循环执行到数据库查询步骤都会抛出:

Could not open JPA EntityManager for transaction

异常详情为:

java.sql.SQLException: Connection is closed

必须重启循环才能恢复正常,现需在保留依赖注入的前提下,实现无需重启循环即可处理网络中断的方案。当前Hikari连接池配置如下:

spring.datasource.url=scrt
spring.datasource.username=scrt
spring.datasource.password=scrt
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
api.security.token.secret=scrt
api.paginacao-size=50
server.port=9999

app.varejo-token=scrt

spring.datasource.hikari.validation-timeout=5000
spring.datasource.hikari.connection-test-query=SELECT 1
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.idle-timeout=600000
spring.datasource.hikari.max-lifetime=1800000

根本原因

  1. 网络中断时,Hikari连接池内部分连接已失效,但因循环持续时间过长,失效连接未被及时检测和剔除,后续请求获取到了无效连接。
  2. 长循环若复用同一EntityManager实例,该实例持有的连接在网络中断后关闭,网络恢复后无法自动重建连接,导致后续查询持续失败。

解决方案

1. 优化Hikari连接池配置,自动剔除失效连接

在现有配置基础上补充以下参数,增强连接有效性检测:

# 获取连接前强制验证连接状态
spring.datasource.hikari.test-on-borrow=true
# 空闲连接定期验证
spring.datasource.hikari.test-while-idle=true
# 缩短空闲连接检测间隔,加快失效连接清理(默认60秒)
spring.datasource.hikari.time-between-eviction-runs-millis=30000

这些配置会让Hikari在获取连接前、连接空闲时自动验证状态,及时剔除失效连接,确保后续请求拿到可用连接。

2. 强制每次查询使用独立事务上下文

若长循环所在方法标记了@Transactional,会导致整个循环复用同一EntityManager和连接。可将checkIsRunned方法单独配置事务传播属性,强制每次调用创建新事务和EntityManager:

@Transactional(propagation = Propagation.REQUIRES_NEW)
public Boolean checkIsRunned(Integer id_pedido){
    return runnedPedidosToBulkOrCronjobClientesRepository.findById(id_pedido).isEmpty();
}

每次调用该方法都会从连接池获取新连接,避免复用已失效的连接实例。

3. 添加异常捕获与重试机制

在循环中捕获数据库连接异常,设置重试逻辑,避免单次异常导致整个循环终止:

for (Pedido pedido : pedidosList) {
    boolean isRunned = false;
    int retryCount = 3; // 自定义重试次数
    while (retryCount > 0) {
        try {
            isRunned = runnedPedidosToBulkOrCronjobClientesService.checkIsRunned(pedido.getId());
            break;
        } catch (SQLException | TransactionSystemException e) {
            retryCount--;
            if (retryCount == 0) {
                log.error("查询订单{}状态失败,已重试3次", pedido.getId(), e);
                throw e; // 或根据业务逻辑跳过该订单
            }
            // 重试前短暂等待,给连接池恢复时间
            Thread.sleep(1000);
        }
    }
    if (!isRunned) {
        return;
    }
    // 后续业务逻辑
}

重试机制可在网络恢复后重新尝试获取可用连接,无需重启循环。

4. 避免长循环占用连接过长

若循环持续时间极长,可拆分循环为批量处理,或在迭代中手动清理EntityManager以释放连接:

@Autowired
private EntityManager entityManager;

// 每N次迭代后清理EntityManager,释放连接回池
if (iteration % 100 == 0) {
    entityManager.clear();
}

这会让连接及时回到连接池,后续操作重新获取可用连接。

验证步骤

  1. 优先调整Hikari连接池配置,添加test-on-borrow=true和time-between-eviction-runs-millis=30000,测试网络恢复后是否自动恢复。
  2. 若问题仍存在,为checkIsRunned方法添加REQUIRES_NEW事务传播属性。
  3. 最后补充重试机制作为兜底方案。

内容的提问来源于stack exchange,提问作者Davi Américo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 05:30:13