最近我对一个常见的事务方法做了实验,并通过分析日志来理解连接的生命周期,以及如何避免连接泄漏的问题。
在一个典型的用户创建服务中,我使用了 @Transactional 注解对数据库写入操作进行事务控制。整个流程中还包括一次对外部 API 的调用。为了进一步分析连接的使用情况,我开启了调试日志,观察连接的创建、使用、提交与释放过程。
在一次正常的请求中,我观察到以下日志片段:
Obtaining new connection from the pool
Acquired Connection [H2Connection@30602a52]
...
Initiating transaction commit
Committing R2DBC transaction on Connection [H2Connection@30602a52]
Releasing R2DBC Connection [H2Connection@30602a52] after transaction
Releasing connection
从日志可以看出,R2DBC 成功从连接池中获取了一个连接,完成了事务提交,并将连接正确归还给连接池。这说明在这种情况下,事务和连接管理是正常的,没有发生连接泄漏。
在另一个实验中,我在事务方法内部使用了 .publishOn(Schedulers.boundedElastic()),手动将反应式流切换到另一个线程池。结果发现连接被获取之后,迟迟没有释放,也没有触发事务提交的日志。
这是因为 R2DBC 的事务管理依赖于 Reactor 的上下文(Context),而这种上下文不会在线程切换时自动传播。一旦线程发生切换,事务上下文就会丢失,导致连接无法正常提交或释放,从而造成连接泄漏。
另一个容易被忽略的问题是“自调用”。当一个类内部的方法通过 this.someMethod() 的形式调用另一个带有 @Transactional 注解的方法时,事务实际上并不会生效。Spring 的事务是基于代理实现的,只有通过 Spring 管理的代理对象调用方法时,事务才会被正确识别。
根据以上trytry,我总结出以下几点:
@Transactional 方法内部做与事务特性无关的事@Transactional 方法内部进行线程切换@Transactional 注解的方法提取到另一个 Spring 管理的 Bean 中,避免自调用的问题。r2dbc-pool)配合 Spring Boot Actuator 暴露连接指标,便于监控连接使用情况。