第3篇-连接的借与还(getConnection全流程)
约 1418 字大约 5 分钟
2026-05-24
四、连接泄漏检测:removeAbandoned 机制
4.1 什么是连接泄漏?
连接泄漏指的是:一个线程借了连接,但忘记调用 close() 归还,导致连接一直被占用,最终连接池耗尽。
白话文解释: 这就好比你去图书馆借书,看完后忘记还了。别人想借这本书,就借不到了。如果每个人都这样,图书馆的书就越来越少,最后大家都没书可借了。
4.2 removeAbandoned 的工作原理
当开启 removeAbandoned=true 时,Druid 会:
- 记录借用信息:在
getConnection()时,记录连接的借用时间、借用线程的堆栈。 - 定期检测:
DestroyConnectionThread会定期扫描activeConnectionsMap,找出"借用时间超过removeAbandonedTimeoutMillis"的连接。 - 强制回收:如果找到了这样的连接,就强制关闭它,并回收物理连接。
// 类路径:com.alibaba.druid.pool.DruidDataSource.DestroyTask
public void destroy() {
// 【核心】扫描活跃连接,找出泄漏的连接
if (removeAbandoned) {
activeConnectionLock.lock();
try {
Iterator<DruidPooledConnection> iter = activeConnections.keySet().iterator();
while (iter.hasNext()) {
DruidPooledConnection pooledConnection = iter.next();
// 【核心】计算借用时间
long timeMillis = System.currentTimeMillis() - pooledConnection.connectTimeNano;
// 【核心】如果借用时间超过阈值,强制回收
if (timeMillis >= removeAbandonedTimeoutMillis) {
iter.remove(); // 从活跃连接Map中移除
pooledConnection.traceEnable = false;
// 【核心】关闭物理连接
JdbcUtils.close(pooledConnection.getConnection());
removeAbandonedCount++;
if (logAbandoned) {
LOG.error("abandon connection, open stackTrace : " + pooledConnection.getConnectStackTrace());
}
}
}
} finally {
activeConnectionLock.unlock();
}
}
}白话文解释:
removeAbandonedTimeoutMillis就是"借书的最长期限"。比如设置为 300 秒(5 分钟),如果超过 5 分钟还没还,图书馆就强制把书收回去。logAbandoned=true会在强制回收时打印日志,告诉你"哪个线程借了连接但没还"(通过堆栈信息)。这对于排查连接泄漏问题非常有帮助!
五、埋下伏笔:MyBatis 如何借用连接?
说到这里,你可能会问:MyBatis 是如何从 Druid 连接池中借用连接的呢?
这个问题,我们会在系列三(MyBatis 系列)中详细解答。这里先预告一下核心流程:
- MyBatis 通过
SqlSession执行 SQL。 SqlSession内部使用了Executor(执行器)。Executor在执行 SQL 前,会通过SpringManagedTransaction获取数据库连接。SpringManagedTransaction会通过DataSourceUtils.getConnection()获取连接。DataSourceUtils.getConnection()最终调用dataSource.getConnection(),也就是 Druid 的getConnection()方法。
下一篇文章的预告:《数据源系列(四):Druid 过滤链与监控统计》
六、总结与思考
今天我们一起深入了 Druid 连接池的"借与还"机制,了解了 getConnection() 和 recycle() 方法的源码实现,以及连接泄漏检测的工作原理。
让我们用几个问题来检验一下今天的学习成果:
getConnection() 的全流程是怎样的?
- 调用
init()确保数据源已初始化。 - 调用
getConnectionInternal()从连接池获取连接。 - 如果开启了
testOnBorrow或testWhileIdle,检测连接有效性。 - 如果开启了
removeAbandoned,记录连接的借用信息。 - 返回
DruidPooledConnection。
- 调用
recycle() 方法做了什么?
- 回滚未提交的事务(防止事务泄漏)。
- 重置连接状态(恢复默认配置)。
- 如果开启了
testOnReturn,检测连接有效性。 - 将连接放回连接池(
putLast())。
什么是连接泄漏?如何检测?
- 连接泄漏指的是:线程借了连接但忘记归还,导致连接池耗尽。
- Druid 通过
removeAbandoned机制检测:记录连接的借用时间和堆栈,定期扫描并强制回收超时未还的连接。
为什么从连接池的尾部获取连接?
- 因为尾部的连接是"最新被归还的",大概率还活着。
- 从头部获取连接的话,拿到的是"最早被归还的",可能已经死了。
七、扩展阅读
【扩展点】自定义连接有效性检测
Druid 允许你自定义连接有效性检测的逻辑。比如,你可以实现一个 ValidConnectionChecker,用于检测连接是否还活着。
// 【扩展点】自定义连接有效性检测器
public class MyCustomValidConnectionChecker implements ValidConnectionChecker {
@Override
public boolean isValidConnection(Connection conn, String query, int validationQueryTimeout) {
try {
// 【注意】执行一个测试 SQL
Statement stmt = conn.createStatement();
stmt.execute(query);
return true;
} catch (SQLException e) {
return false; // 连接已死
}
}
}场景:你可以根据业务需要,实现特殊的连接检测逻辑。比如,如果数据库是 MySQL,你可以发送 ping 命令,而不是执行 SELECT 1。
如何启用自定义检测器? 在 application.yml 中配置:
spring:
datasource:
druid:
# 【核心】指定自定义的连接有效性检测器
valid-connection-checker-class-name: com.example.MyCustomValidConnectionChecker好了,今天的文章就到这里。下一篇,我们将深入 Druid 的过滤链机制,看看它是如何实现监控统计、防火墙等功能的。敬请期待!
如果你觉得这篇文章对你有帮助,欢迎点赞、收藏、转发!你的支持是我继续创作的动力!
系列文章目录:
- 系列一:Spring 事务系列(已完成)
- 第1篇:@Transactional 注解的"前世今生"
- 第2篇:AOP 代理 —— 事务的"隐身衣"
- 第3篇:事务传播机制源码深度解析
- 第4篇:事务隔离级别的实现原理
- 第5篇:事务管理器的设计哲学
- 第6篇:事务资源的绑定与传递
- 第7篇:提交、回滚与异常规则
- 系列二:数据源系列(进行中)
- 第1篇:JDBC 规范与 DriverManager 的 legacy(已完成)
- 第2篇:Druid 连接池的整体架构(已完成)
- 第3篇:连接的借与还(getConnection 全流程)(本文)
- 第4篇:Druid 过滤链与监控统计
- 第5篇:MySQL 驱动的 JDBC 实现
- 第6篇:连接的生命周期与事务边界
- 系列三:MyBatis 系列(待开始)
- 系列四:总结系列(待开始)