REQUIRES_NEW会挂起当前事务并获取全新连接,若方法执行异常、超时或提前返回,该连接无法归还连接池,导致隐性泄漏;NESTED依赖savepoint,不支持时可能退化为REQUIRES_NEW;自调用和线程切换均会使连接脱离Spring事务管控。

Spring事务传播行为配置不当,确实会引发数据库连接不释放的隐性泄漏——这不是连接没关,而是连接被“卡”在事务上下文中无法归还池中,表面无异常,实则资源持续占用。
Propagation.REQUIRES_NEW 引发的连接独占
当方法A(已开启事务)调用标注 @Transactional(propagation = Propagation.REQUIRES_NEW) 的方法B时,Spring会挂起A的事务,新建一个独立事务并获取**全新连接**。若B执行时间长、抛出未被捕获的异常、或提前return未触发事务完成逻辑,该新连接就可能滞留在活跃状态,且不会随A的事务结束而释放。
- 尤其常见于异步任务、日志记录、审计写入等辅助操作中滥用 REQUIRES_NEW
- HikariCP 或 C3P0 日志中可见
numBusyConnections持续增长,numIdleConnections趋近于0 - 即使B方法内使用 JdbcTemplate,只要事务未正常提交/回滚,连接就不会归还
Propagation.NESTED 与 Savepoint 的陷阱
NESTED 依赖数据库 savepoint 实现嵌套回滚,不新开事务,但要求底层连接支持 savepoint(如 MySQL 5.6+、PostgreSQL)。若数据库不支持或驱动版本过低,Spring 可能退化为 REQUIRES_NEW 行为,意外获取新连接;更隐蔽的是,若嵌套逻辑中发生未声明 rollbackFor 的异常,外层事务继续提交,而 savepoint 释放失败,连接可能因状态混乱延迟释放。
- 检查日志是否出现
Cannot release savepoint或Connection is closed类似警告 - 避免在 NESTED 方法中执行耗时IO(如HTTP调用),否则连接持有时间远超预期
- 确认数据库方言和 JDBC 驱动版本兼容 savepoint 语义
自调用导致事务失效,连接脱离管控
同一类内非 public 方法调用 @Transactional 方法,因 Spring AOP 代理失效,事务注解不生效。此时若方法内部又手动调用 dataSource.getConnection(),连接完全游离于 Spring 事务上下文之外,JDBC 模板无法接管其生命周期。
立即学习“Java免费学习笔记(深入)”;
- 典型场景:Service 内部 private 方法里做数据库类型判断(如
select @@version),直接拿 Connection 却忘了 close - 这种连接不会绑定到 TransactionSynchronizationManager,也不会被 DataSourceUtils 自动回收
- 必须搭配
DataSourceUtils.releaseConnection(conn, dataSource)手动释放
事务边界与线程切换的断裂
在事务方法中启动新线程(如 new Thread(...).start() 或 CompletableFuture.runAsync()),新线程无法继承原事务上下文,其内部的 JdbcTemplate 操作会从连接池重新获取连接,且该连接不受外层事务管理——若线程异常退出或未显式关闭,即成泄漏点。
- WebFlux + 虚拟线程混用 JDBC 更危险:虚拟线程不可取消,连接无法响应 Reactor 生命周期钩子
- 正确做法是将数据操作移至主线程完成,或改用响应式数据库驱动(如 R2DBC)
- 若必须异步,确保使用
TransactionTemplate在新线程内显式开启/结束事务,并配好资源释放逻辑


















