引言
上一部分我们实现了解析XML文件中的数据源配置信息,这篇文章我们来实现数据源。
开始
MyBatis源码中自带的数据源类型
MyBatis 自带了三种内置的数据源(DataSource
)类型,通过配置文件中的 <dataSource>
标签的 type
属性指定。以下是它们的详细说明及使用场景:
1. **UNPOOLED**
(非池化数据源)
特点
每次请求新建连接:每次执行 SQL 时创建新的数据库连接,用完后直接关闭。
无连接池管理:适用于轻量级或测试场景,不适合高并发生产环境。
简单高效:没有连接池的开销,但频繁创建/销毁连接性能较差。
配置示例
<dataSource type="UNPOOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
<!-- 可选参数 -->
<property name="autoCommit" value="false"/> <!-- 是否自动提交事务 -->
<property name="defaultTransactionIsolationLevel" value="READ_COMMITTED"/> <!-- 事务隔离级别 -->
</dataSource>
适用场景
本地开发或单元测试。
对性能要求不高的简单应用。
2. **POOLED**
(池化数据源)
特点
连接池管理:复用数据库连接,减少频繁创建/销毁连接的开销。
性能优化:适合生产环境,支持并发请求。
自动回收:空闲连接超时或活动连接超过最大数量时,自动关闭冗余连接。
配置示例
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
<!-- 连接池参数 -->
<property name="poolMaximumActiveConnections" value="10"/> <!-- 最大活动连接数 -->
<property name="poolMaximumIdleConnections" value="5"/> <!-- 最大空闲连接数 -->
<property name="poolMaximumCheckoutTime" value="20000"/> <!-- 连接最大借用时间(毫秒) -->
<property name="poolTimeToWait" value="20000"/> <!-- 获取连接超时时间(毫秒) -->
</dataSource>
核心参数说明
适用场景
生产环境中的中小型应用。
需要平衡性能和资源消耗的场景。
3. **JNDI**
(容器管理的数据源)
特点
从容器获取连接:数据源由 Java EE 容器(如 Tomcat、WebLogic)管理,MyBatis 通过 JNDI 名称查找。
集中配置:数据源的连接池参数在容器中统一配置,与 MyBatis 解耦。
适合企业级应用:通常用于需要与容器集成的场景。
配置示例
<dataSource type="JNDI">
<property name="initial_context" value="java:comp/env"/> <!-- JNDI 初始上下文 -->
<property name="data_source" value="jdbc/MyDB"/> <!-- 数据源的 JNDI 名称 -->
</dataSource>
容器配置示例(Tomcat)
在 Tomcat 的 context.xml
中配置数据源:
<Context>
<Resource
name="jdbc/MyDB"
auth="Container"
type="javax.sql.DataSource"
driverClassName="com.mysql.cj.jdbc.Driver"
url="jdbc:mysql://localhost:3306/test"
username="root"
password="123456"
maxTotal="20" <!-- 最大连接数 -->
maxIdle="10" <!-- 最大空闲连接数 -->
maxWaitMillis="10000" <!-- 获取连接超时时间 -->
/>
</Context>
适用场景
Java EE 环境(如 Tomcat、JBoss 等容器)。
需要与容器共享数据源的分布式应用。
4. 内置数据源的实现类
MyBatis 内置数据源的底层实现类如下:
UNPOOLED
:org.apache.ibatis.datasource.unpooled.UnpooledDataSource
POOLED
:org.apache.ibatis.datasource.pooled.PooledDataSource
JNDI
:org.apache.ibatis.datasource.jndi.JndiDataSource
5. 如何选择数据源?
6. 对比第三方连接池(如 HikariCP、Druid)
非池化数据源实现
MyBatis 的无池化数据源(Unpooled DataSource)实现机制相对简单,核心在于每次请求时直接创建新的数据库连接,使用后立即关闭。
无池化的数据源链接实现比较简单,核心在于 initializerDriver 初始化驱动中使用了 Class.forName 和 newInstance 的方式创建了数据源链接操作。
在创建完成连接以后,把连接存放到驱动注册器中,方便后续使用中可以直接获取连接,避免重复创建所带来的资源消耗。
池化数据源实现
池化的数据源连接,核心在于对无池化连接的包装,同时提供了相应的池化技术实现,包括:pushConnection、popConnection、forceCloseAll、pingConnection 的操作处理。
这样当用户想要获取连接的时候,则会从连接池中进行获取,同时判断是否有空闲连接、最大活跃连接多少,以及是否需要等待处理或是最终抛出异常。
池化连接的代理
由于我们需要对连接进行池化处理,所以当链接调用一些 CLOSE 方法的时候,也需要把链接从池中关闭和恢复可用,允许其他用户获取到链接。那么这里就需要对连接类进行代理包装,处理 CLOSE 方法。
通过 PooledConnection 实现 InvocationHandler#invoke 方法,包装代理链接,这样就可以对具体的调用方法进行控制了。
在 invoke 方法中处理对 CLOSE 方法控制以外,排除 toString 等Object 的方法后,则是其他真正需要被 DB 链接处理的方法了。
那么这里有一个对于 CLOSE 方法的数据源回收操作 dataSource.pushConnection(this); 有一个具体的实现方法,在池化实现类 PooledDataSource 中进行处理。
方法实现
1. pushConnection
作用:将使用完毕的连接归还到空闲连接池(idleConnections
),供后续复用。 流程:
重置连接状态(如回滚未提交的事务、恢复自动提交模式)。
检查空闲池是否已满:
若未满,将连接加入空闲池,并记录
lastUsedTime
(最后使用时间)。若已满,直接关闭物理连接。
从活跃池(
activeConnections
)中移除该连接。通知等待线程(通过
notifyAll
),唤醒可能阻塞在popConnection
中的其他线程。
2. popConnection
作用:从连接池中获取一个可用连接。 流程:
循环尝试获取连接:
检查空闲池是否有可用连接:
若有,取出第一个空闲连接,验证有效性(通过
pingConnection
)。若无效,关闭该连接并重试。
若空闲池无连接,检查活跃池是否已满:
若未满,创建新连接并加入活跃池。
若已满,线程等待(
wait(poolTimeToWait)
)直到超时或有连接被归还。
将连接标记为活跃状态,记录
checkoutTime
(取出时间)。返回代理连接(
ProxyConnection
),拦截close()
方法以实现归还逻辑。
3. forceCloseAll
作用:强制关闭所有空闲和活跃连接,通常用于连接池销毁或紧急重置。 流程:
遍历空闲池和活跃池,逐个调用
realClose()
关闭物理连接。清空空闲池和活跃池。
重置连接池状态(如计数器)。
4. pingConnection
作用:验证连接是否有效,防止返回已失效的连接(如数据库超时断开)。 流程:
发送测试查询(如
SELECT 1
)或调用Connection.isValid(timeout)
。若测试成功,返回
true
;否则返回false
。若连接无效,调用
realClose()
关闭物理连接。
池状态
线程安全控制 通过 synchronized(state)
对连接池的关键操作(如获取连接、归还连接)加锁,防止多线程竞争导致数据不一致。
资源状态管理 维护连接池的实时状态,包括:
空闲连接列表(
idleConnections
)活跃连接列表(
activeConnections
)当前等待连接的线程数
统计信息(如总连接数、超时次数等)
线程间通信 使用 state.wait()
和 state.notifyAll()
实现阻塞唤醒机制:
当线程无法获取连接时,调用
state.wait()
进入等待。当连接被归还时,调用
state.notifyAll()
唤醒等待线程。
流程图
无池化工厂
简单包装 getDataSource 获取数据源处理,把必要的参数进行传递过去。
池化工厂
池化的数据源工厂实现的也比较简单,只是继承 UnpooledDataSourceFactory 共用获取属性的能力,以及实例化出池化数据源即可。