前言
在之前的文章中,我们提及到通过Trae
的编码能力实现复刻MyBtatis
的思路,如今,我们相关的mini-mybatis
已逐渐成型,感兴趣的小伙伴可访问如下地址:mini-mybatis 进行学习 。其中,每个模块所对应知识点如下:

本次我们主要深入剖析Mybatis
中内部池化
思想的设计实践,相关代码可参考模块mini-mybatis-datasource
。
池化思想
事实上,我们平时所谈及的 池化(pool)
其底层逻辑都是一样的。即提前准备好资源,需要使用时直接获取,用完再放回,进而避免对象反复创建、销毁的资源损耗
以生活中最常见的共享单车
为例,平台会在城市各处的停靠点预先部署自行车,用户通过扫码使用,待到达目的地后再将车辆停回指定区域,进而方便其他用户继续使用。
当然,未按规则停放在服务范围内,则需缴纳额外费用。这其实是通过规则约束资源使用行为,避免资源滥用或流失。
将这一逻辑过渡到我们所熟悉的SQL
查询执行中,对于无池化
的SQL
操作,其与数据库通信逻辑如下:
- 首先,上层应用会调用
JDBC
驱动发起数据库请求 - 然后,请求与数据库通过三次握手建立
TCP
连接; - 其次,连接发送
SQL
执行指令,等待数据库返回结果; - 最终,关闭
TCP
连接,进行四次挥手操作。
可能你会想,如果这一过程中的 "等待、沟通、结算" 耗时不长,那是不是并不会产生负面影响?
如果并发量不高情况下,理论上不进行池化
操作也是可以的,但当业务高频次调用时,连接频繁的构建的 累计耗时,则会严重挤占业务逻辑时间。
比如,一次 SQL
查询本身仅需 10ms
,而建立连接的过程可能就要 50ms
,这意味着 80%
的时间都消耗在非业务相关的资源准备上, 进而降低整个业务的响应时间。
因此,池化
出现的核心是在缓解 "资源创建 / 销毁成本高" 与 "资源需求频繁" 之间的矛盾。
通常来看, 实现池化
操作通常会进行如下几步:
- 资源预创建:在系统初始化时,提前创建一批可用的资源,存到一个容器中,进而避免用的时候临时创建;
- 资源复用:业务需要资源时,直接从容器中获取,不用自己新建;待用完后不进行销毁,而是 "归还" 到池里,供其他业务复用;
- 资源管控:池会监控资源的状态(比如是否空闲、是否过期),还能配置 "最大资源数"(避免资源过多占满内存)、"最小空闲数"(保证随时有可用资源)、"超时回收"(清理长期不用的资源)。
Mybatis
中的池化设计
在介绍Mybatis
中池化思想时,我们先明确一个关键点。即 MyBatis
本身并不直接实现 "连接池",而是集成第三方连接池,并通过统一的 DataSource
接口封装池化逻辑。

MyBatis
在有关数据源实现中,核心分为无池化的UnpooledDataSource
与有池化的PooledDataSource
。对于PooledDataSource
来说,其通过对数据库连接connection
的装饰
,使得其close
方法不再直接关闭连接,而是将连接存入内存,并且用两个集合分别管理 "空闲连接" 与 "活跃连接",供不同阶段业务复用,避免重复创建损耗。
进一步来看,PooledConnection
是连接的代理类,核心通过invoke
方法反射拦截close
操作:业务调用close
时,不会真关闭 TCP
连接,而是将连接重置后归还空闲集合,同时调用notifyAll
唤醒等待连接的线程,让线程竞争复用,减少阻塞。整个逻辑如下所示:

Mybatis
相关池化代码剖析
在熟悉了Mybatis
中实现池化
的相关组件后,接下来,我们将Mybatis
中实现池化的关键逻辑我们拆成 个关键流程,结合代码逻辑理解来进一步理解Mybatis
内部池化逻辑实现的具体细节。
获取连接------ getConnection
从连接池中获取
当MyBatis
在执行 sqlSession.selectList(...)
时,其本质借助Environment
对象中所维护的数据源
来获取连接,即调用 dataSource.getConnection()
。此处获取连接的代码调用逻辑大致如下:
PooledDataSource
java
public Connection getConnection() throws SQLException {
//覆盖了DataSource.getConnection方法,每次都是pop一个Connection,即从池中取出一个来
return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
}
PooledDataSource # popConnection
java
private PooledConnection popConnection(String username, String password) throws SQLException {
// .... 省略无关代码
while (conn == null) {
// 资源锁住,避免并发问题
synchronized (state) {
// 1. 连接池中有空闲连接
if (!state.idleConnections.isEmpty()) {
// .... 省略无关代码
} else {
// 2. 判断活跃连接数未超出限制
if (state.activeConnections.size() < poolMaximumActiveConnections) {
// .... 省略无关代码
} else {
// 3. 最大连接数以超过限制,则获取通过LRU算法,
// 获取最老连接判断其是否超时,
// .... 省略无关代码
}
}
// .... 省略无关代码
return conn;
}
为了便于理解,我们对PooledConnection
的源码进行了精简。其中的if-else
判断逻辑如下图所示:

总的来看,此处getConnection
逻辑可简化如下两步:
-
先看 "空闲池" 有没有可用连接:如果有,直接取最前面的(FIFO 队列),移到 "活跃池"(
activeConnections
); -
如果空闲池为空,检查 "活跃池数量" 是否超过
poolMaximumActiveConnections
(最大活跃连接数):- 没超过:新建一个物理连接,加入活跃池;
- 超过了:等待(
poolMaximumWait
),直到有连接归还,或超时抛出 "连接超时" 异常。
归还连接--- close
方法的代理增强
如果我们未对connection
进行缓存,当连接用完后会自动进行关闭。通过我们之前对池化
的介绍和分析,当引入池化
操作后,我们的会对资源进行一次缓存,而不是直接将其关闭。
具体到Mybatis
内部,我们对于连接的关闭
其实是将其归还至**放回空闲池
这个队列中。 具体逻辑如下:
PooledDataSource # pushConnection
java
protected void pushConnection(PooledConnection conn) throws SQLException {
synchronized (state) {
state.activeConnections.remove(conn);
if (conn.isValid()) {
// 未达到空闲最大连接数
if (state.idleConnections.size() < poolMaximumIdleConnections) {
state.accumulatedCheckoutTime += conn.getCheckoutTime();
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
PooledConnection newConnection = new PooledConnection(conn.getRealConnection(),this);
newConnection.setCreatedTimestamp(conn.getCreatedTimestamp());
newConnection.setLastUsedTimestamp(System.currentTimeMillis());
state.notifyAll();
} else {
//反之,即空闲的连接已经足够了
state.accumulatedCheckoutTime += conn.getCheckoutTime();
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
conn.getRealConnection().close();
conn.invalidate();
}
}else {
log.info("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
state.badConnectionCount ++ ;
}
}
}
此处代码逻辑相对简单,主要考虑两种情况,如果空闲队列仍空闲空间可以使用,则将连接存在之队列的末尾,并唤醒之前已经等待的进行重新进行连接的获取;反之,如果空闲队列已经超过最大限制,则才将连接进行关闭。
总结
总的来看,MyBatis
内部连接池实现连接复用的本质,是通过 PooledConnection
代理改写 close ()
方法+PooledDataSource
双池管控流程 ,从而将 "物理连接的创建 / 关闭" 转化为 "池内连接的复用 / 流转"。
这种设计既遵循 JDBC
规范(基于 DataSource
与 Connection
接口),又通过轻量级封装实现池化能力,是 "规范兼容" 与 "性能优化" 的平衡典范,也为集成第三方连接池(如 HikariCP、Druid
)预留了扩展空间(通过实现 DataSource
接口即可替换)。