Spring+Hibernate+HikariCP:长耗时REST调用下的数据库连接处理问询
这个问题我之前在类似的Spring Boot+Hibernate项目里碰到过,核心原因很明确:你的doStuff()方法应该是被Spring的@Transactional注解管理着(默认服务层方法会自动开启事务),从调用dao.findByWhatever()开始,Hikari的数据库连接就被绑定到当前线程的事务上下文里了。而后续的restService.doRemoteRequestWithRetries()耗时超过2秒,这段时间连接一直被占用没有释放,正好触发了你配置的leakDetectionThreshold=2000告警。
下面给你几个按推荐程度排序的解决方案:
1. 拆分事务,释放连接后再执行远程调用(最优方案)
最合理的做法是把数据库操作和远程调用拆分开,让数据库操作在独立的事务中完成,释放连接后再去处理慢远程服务。这样远程调用期间完全不会占用数据库连接,从根源上解决问题。
代码示例(注意处理内部事务调用的问题):
@Service public class YourService { // 注入所需的DAO、restService等依赖 // 只读事务获取A,事务结束后自动释放连接 @Transactional(readOnly = true) public A fetchA() { return dao.findByWhatever(); } // 单独的事务保存修改后的A @Transactional public void updateAWithBProperty(A a, B b) { a.setProp(b.getSomething()); dao.save(a); // 原代码里的save(b)应该是笔误,这里应该保存修改后的a } // 主方法,无事务,只负责流程编排 public void doStuff() { A a = fetchA(); if (a.hasProperty()) { // 这里已经没有数据库连接占用了,放心调用慢服务 B b = restService.doRemoteRequestWithRetries(); // 调用带事务的方法保存数据 updateAWithBProperty(a, b); } } }
注意:如果你的方法是内部私有调用,Spring的动态代理不会拦截这些调用,事务不会生效。所以最好把这些方法设为public,或者把数据库操作逻辑拆分到单独的DAO/Repository类里,或者开启
exposeProxy=true后用AopContext.currentProxy()获取代理对象调用。
2. 手动控制连接释放(应急方案,不推荐长期使用)
如果暂时没办法拆分事务,也可以手动在远程调用前断开数据库连接,调用完成后再重新连接。这种方式代码复杂度高,容易引入事务一致性问题,只建议作为临时应急手段。
代码示例:
@Service @Transactional public class YourService { @PersistenceContext private EntityManager entityManager; public void doStuff() { A a = dao.findByWhatever(); if (a.hasProperty()) { // 手动断开当前会话的连接 Session session = entityManager.unwrap(Session.class); session.disconnect(); // 执行慢远程调用 B b = restService.doRemoteRequestWithRetries(); // 重新连接并执行保存操作 session.reconnect(); a.setProp(b.getSomething()); dao.save(a); } } }
3. 调整泄漏检测阈值(临时掩盖问题,不推荐)
如果你只是想临时消除告警,不想修改代码,可以调大leakDetectionThreshold的值,比如设置为10000(10秒)。但这只是掩盖问题,并没有解决连接被长期占用的本质——连接池里的连接会被长时间占用,降低系统的并发处理能力,所以只适合临时应急。
配置示例(application.properties):
spring.datasource.leakDetectionThreshold=10000
额外提醒
- 检查原代码里的
dao.save(b),看起来应该是笔误,你修改的是a的属性,应该保存的是a对象; - 给远程调用设置合理的超时时间,即使调大了泄漏阈值,也避免连接被无限期占用。
内容的提问来源于stack exchange,提问作者Anton Kushch

