1.前言
数据源是持久层框架中最核心的组件之一,在实际工作中比较常见的数据源有 C3P0、Apache Common DBCP、Proxool 等。
MyBatis 自己提供了一套数据源实现,而且还能够方便地集成第三方数据源。
2.Mybatis数据源工厂
工厂方法类图
MyBatis 的数据源模块也是用到了工厂方法模式,如果需要扩展新的数据源实现时,只需要添加对应的 Factory 实现类,新的数据源就可以被 MyBatis 使用。
org.apache.ibatis.datasource.DataSourceFactory
MyBatis 数据源实现中的 Factory 接口角色,UnpooledDataSourceFactory 和 PooledDataSourceFactory 实现了 DataSourceFactory 接口,也就是 Factory 接口实现类的角色
继续:核心的方法是getDataSource() 方法,该方法用来生成一个 DataSource 对象。
在 UnpooledDataSourceFactory 这个实现类的初始化过程中,会直接创建 UnpooledDataSource 对象,其中的 dataSource 字段会指向该 UnpooledDataSource 对象。接下来调用的 setProperties() 方法会根据传入的配置信息,完成对该 UnpooledDataSource 对象相关属性的设置。在通过数据源拿到数据库连接之后,还需要开启事务,才能进行数据的修改。
后续会讲解 Transaction 接口,它可以管理事务的开启、提交和回滚。
package org.apache.ibatis.datasource.unpooled;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.ibatis.datasource.DataSourceException;
import org.apache.ibatis.datasource.DataSourceFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
/**
* @author Clinton Begin
*/
public class UnpooledDataSourceFactory implements DataSourceFactory {
private static final String DRIVER_PROPERTY_PREFIX = "driver.";
private static final int DRIVER_PROPERTY_PREFIX_LENGTH = DRIVER_PROPERTY_PREFIX.length();
protected DataSource dataSource;
//生成一个 DataSource 对象
public UnpooledDataSourceFactory() {
this.dataSource = new UnpooledDataSource();
}
//根据传入的配置信息,完成对该 UnpooledDataSource 对象相关属性的设置。
@Override
public void setProperties(Properties properties) {
Properties driverProperties = new Properties();
MetaObject metaDataSource = SystemMetaObject.forObject(dataSource);
for (Object key : properties.keySet()) {
String propertyName = (String) key;
if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) {
String value = properties.getProperty(propertyName);
driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);
} else if (metaDataSource.hasSetter(propertyName)) {
String value = (String) properties.get(propertyName);
Object convertedValue = convertValue(metaDataSource, propertyName, value);
metaDataSource.setValue(propertyName, convertedValue);
} else {
throw new DataSourceException("Unknown DataSource property: " + propertyName);
}
}
if (driverProperties.size() > 0) {
metaDataSource.setValue("driverProperties", driverProperties);
}
}
//返回了上面创建的 UnpooledDataSource 对象
@Override
public DataSource getDataSource() {
return dataSource;
}
private Object convertValue(MetaObject metaDataSource, String propertyName, String value) {
Object convertedValue = value;
Class<?> targetType = metaDataSource.getSetterType(propertyName);
if (targetType == Integer.class || targetType == int.class) {
convertedValue = Integer.valueOf(value);
} else if (targetType == Long.class || targetType == long.class) {
convertedValue = Long.valueOf(value);
} else if (targetType == Boolean.class || targetType == boolean.class) {
convertedValue = Boolean.valueOf(value);
}
return convertedValue;
}
}
3.DataSource
JDK 提供的 javax.sql.DataSource 接口在 MyBatis 数据源中扮演了 Product 接口的角色。 MyBatis 提供的数据源实现有两个,一个 UnpooledDataSource 实现,另一个 PooledDataSource 实现,它们都是 Product 具体实现类的角色。
3.1 UnpooledDataSource
driverClassLoader(ClassLoader 类型):加载 Driver 驱动类的类加载器。
driverProperties(Properties 类型):数据库连接驱动的相关配置。
registeredDrivers(Map<String, Driver> 类型):缓存所有已注册的数据库连接驱动。
defaultTransactionIsolationLevel(Integer 类型):事务隔离级别。
目前基本上所有数据源实现的底层都是依赖 JDBC 操作数据库的,而使用 JDBC 的第一步就是向 DriverManager 注册 JDBC 驱动类,之后才能创建数据库连接。
DriverManager 中定义了 registeredDrivers 字段用于记录注册的 JDBC 驱动,这是一个 ConcurrentHashMap 类型的集合,是线程安全的。
MyBatis 的 UnpooledDataSource 实现中定义了如下静态代码块,从而在 UnpooledDataSource 加载时,将已在 DriverManager 中注册的 JDBC 驱动器实例复制一份到 UnpooledDataSource.registeredDrivers 集合中。
static {
//从DriverManager中读取JDBC驱动
Enumeration<Driver> drivers = DriverManager.getDrivers();
while (drivers.hasMoreElements()) {
Driver driver = drivers.nextElement();
//将DriverManager中的全部JDBC驱动记录到registeredDrivers集合
registeredDrivers.put(driver.getClass().getName(), driver);
}
}
在 getConnection() 方法中,UnpooledDataSource 会调用 doGetConnection() 方法获取数据库连接
private Connection doGetConnection(String username, String password) throws SQLException {
Properties props = new Properties();
if (driverProperties != null) {
props.putAll(driverProperties);
}
if (username != null) {
props.setProperty("user", username);
}
if (password != null) {
props.setProperty("password", password);
}
return doGetConnection(props);
}
private Connection doGetConnection(Properties properties) throws SQLException {
// 初始化数据库驱动
//在调用的 initializeDriver() 方法中,完成了 JDBC 驱动的初始化,其中会创建配置中指定的 Driver 对象,并将其注册到 DriverManager 以及上面介绍的 UnpooledDataSource.registeredDrivers 集合中保存;
initializeDriver();
// 创建数据库连接
Connection connection = DriverManager.getConnection(url, properties);
// 配置数据库连接
//会对数据库连接进行一系列配置,例如,数据库连接超时时长、事务是否自动提交以及使用的事务隔离级别。
configureConnection(connection);
return connection;
}
3.2 PooledDataSource
1.池化技术
JDBC 连接的创建是非常耗时的,从数据库这一侧看,能够建立的连接数也是有限的,所以在绝大多数场景中,我们都需要使用数据库连接池来缓存、复用数据库连接。
使用池化技术缓存数据库连接会带来很多好处,例如:
-
在空闲时段缓存一定数量的数据库连接备用,防止被突发流量冲垮;
-
实现数据库连接的重用,从而提高系统的响应速度;
-
控制数据库连接上限,防止连接过多造成数据库假死;
-
统一管理数据库连接,避免连接泄漏。
数据库连接池在初始化时,一般会同时初始化特定数量的数据库连接,并缓存在连接池中备用。当我们需要操作数据库时,会从池中获取连接;当使用完一个连接的时候,会将其释放。在使用连接池的场景中,并不会直接将连接关闭,而是将连接返回到池中缓存,等待下次使用。
2.分析
PooledDataSource 中并没有直接维护数据库连接的集合,而是维护了一个 PooledState 类型的字段(state 字段),而这个PooledState 才是管理连接的地方。在 PooledState 中维护的数据库连接并不是真正的数据库连接(不是 java.sql.Connection 对象),而是 PooledConnection 对象。
private final PoolState state = new PoolState(this);
PooledConnection 是 MyBatis 中定义的一个 InvocationHandler 接口实现类,其中封装了真正的 java.sql.Connection 对象以及相关的代理对象,这里的代理对象就是通过JDK 动态代理产生的。
/**
* Unwraps a pooled connection to get to the 'real' connection
* 打开池连接以获取“真实”连接
* @param conn
* - the pooled connection to unwrap
* @return The 'real' connection
*/
public static Connection unwrapConnection(Connection conn) {
if (Proxy.isProxyClass(conn.getClass())) {
InvocationHandler handler = Proxy.getInvocationHandler(conn);
if (handler instanceof PooledConnection) {
return ((PooledConnection) handler).getRealConnection();
}
}
return conn;
}
3.PooledConnection 中的核心字段:
dataSource(UnpooledDataSource 类型):记录当前 PooledConnection 对象归属的 UnpooledDataSource 对象。也就是说,当前的 PooledConnection 是由该 UnpooledDataSource 对象创建的;
realConnection(Connection 类型):当前 PooledConnection 底层的真正数据库连接对象。
proxyConnection(Connection 类型):指向了 realConnection 数据库连接的代理对象。
checkoutTimestamp(long 类型):使用方从连接池中获取连接的时间戳。
createdTimestamp(long 类型):连接创建的时间戳。
lastUsedTimestamp(long 类型):连接最后一次被使用的时间戳。
connectionTypeCode(int 类型):数据库连接的标识。该标识是由数据库 URL、username 和 password 三部分组合计算出来的 hash 值,主要用于连接对象确认归属的连接池。
valid(boolean 类型):用于标识 PooledConnection 对象是否有效。该字段的主要目的是防止使用方将连接归还给连接池之后,依然保留该 PooledConnection 对象的引用并继续通过该 PooledConnection 对象操作数据库。
4.PooledConnection :
PooledConnection构造方法会初始化上述字段,这里尤其关注 proxyConnection 这个 Connection 代理对象的初始化,使用的是 JDK 动态代理的方式实现的,其中传入的 InvocationHandler 实现正是 PooledConnection 自身。
PooledConnection.invoke() 方法中只对 close() 方法进行了拦截
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if (CLOSE.equals(methodName)) {
// 如果调用close()方法,并没有直接关闭底层连接,而是将其归还给关联的连接池
dataSource.pushConnection(this);
return null;
}
if (!Object.class.equals(method.getDeclaringClass())) {
// 只要不是Object的方法,都需要检测当前PooledConnection是否可用
checkConnection();
}
// 调用realConnection的对应方法
return method.invoke(realConnection, args);
}
5.PoolState
PoolState这个类,它负责管理连接池中所有 PooledConnection 对象的状态,维护了两个 ArrayList 集合按照 PooledConnection 对象的状态分类存储,其中 idleConnections 集合用来存储空闲状态的 PooledConnection 对象,activeConnections 集合用来存储活跃状态的 PooledConnection 对象。
public class PoolState {
protected PooledDataSource dataSource;
protected final List<PooledConnection> idleConnections = new ArrayList<>();
protected final List<PooledConnection> activeConnections = new ArrayList<>();
....
定义的统计字段:
requestCount:请求数据库连接的次数。
accumulatedRequestTime:获取连接的累积耗时。
accumulatedCheckoutTime:所有连接的 checkoutTime 累加。PooledConnection 中有一个 checkoutTime 属性,
表示的是使用方从连接池中取出连接到归还连接的总时长,也就是连接被使用的时长。
claimedOverdueConnectionCount:当连接长时间未归还给连接池时,会被认为该连接超时,该字段记录了超时的连接个数。
accumulatedCheckoutTimeOfOverdueConnections:记录了累积超时时间。
accumulatedWaitTime:当连接池全部连接已经被占用之后,新的请求会阻塞等待,该字段就记录了累积的阻塞等待总时间。
hadToWaitCount:记录了阻塞等待总次数。
badConnectionCount:无效的连接数。
3.2.1 PooledDataSource 实现分析
首先是 getConnection() 方法,其中先是依赖 popConnection() 方法获取 PooledConnection 对象,然后从 PooledConnection 中获取数据库连接的代理对象
public Connection getConnection(String username, String password) throws SQLException {
return popConnection(username, password).getProxyConnection();
}
这里调用的 popConnection() 方法是从连接池中获取数据库连接的核心,具体步骤:
- 检测当前连接池中是否有空闲的有效连接,如果有,则直接返回连接;如果没有,则继续执行下一步。
- 检查连接池当前的活跃连接数是否已经达到上限值,如果未达到,则尝试创建一个新的数据库连接,并在创建成功之后,返回新建的连接;如果已达到最大上限,则往下执行。
- 检查活跃连接中是否有连接超时,如果有,则将超时的连接从活跃连接集合中移除,并重复步骤 2;如果没有,则执行下一步。
- 当前请求数据库连接的线程阻塞等待,并定期执行前面三步检测相应的分支是否可能获取连接。
1.popConnection获取连接源码:
org.apache.ibatis.datasource.pooled.PooledDataSource#popConnection
private PooledConnection popConnection(String username, String password) throws SQLException {
boolean countedWait = false;
PooledConnection conn = null;
long t = System.currentTimeMillis();
int localBadConnectionCount = 0;
while (conn == null) {
synchronized (state) { // 加锁同步
// 步骤1:检测空闲连接集合
if (!state.idleConnections.isEmpty()) {
// 获取空闲连接
conn = state.idleConnections.remove(0);
} else { // 没有空闲连接
// 步骤2:活跃连接数没有到上限值,则创建新连接
if (state.activeConnections.size() < poolMaximumActiveConnections) {
// 创建新数据库连接,并封装成PooledConnection对象
conn = new PooledConnection(dataSource.getConnection(), this);
} else {// 活跃连接数已到上限值,则无法创建新连接
// 步骤3:检测超时连接
// 获取最早的活跃连接
PooledConnection oldestActiveConnection = state.activeConnections.get(0);
long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
// 检测该连接是否超时
if (longestCheckoutTime > poolMaximumCheckoutTime) {
// 对超时连接的信息进行统计
state.claimedOverdueConnectionCount++;
state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
state.accumulatedCheckoutTime += longestCheckoutTime;
// 将超时连接移出activeConnections集合
state.activeConnections.remove(oldestActiveConnection);
// 如果超时连接上有未提交的事务,则自动回滚
if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
try {
oldestActiveConnection.getRealConnection().rollback();
} catch (SQLException e) {
}
}
// 创建新PooledConnection对象,但是真正的数据库连接
conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
// 将超时PooledConnection设置为无效
oldestActiveConnection.invalidate();
} else {
// 步骤4:无空闲连接、无法创建新连接且无超时连接,则只能阻塞等待
if (!countedWait) { // 统计阻塞等待次数
state.hadToWaitCount++;
countedWait = true;
}
long wt = System.currentTimeMillis();
state.wait(poolTimeToWait);// 阻塞等待
// 统计累积的等待时间
state.accumulatedWaitTime += System.currentTimeMillis() - wt;
}
}
}
if (conn != null) { // 对连接进行统计
if (conn.isValid()) { // 检测PooledConnection是否有效
// 配置PooledConnection的相关属性,设置connectionTypeCode、checkoutTimestamp、lastUsedTimestamp字段的值
conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));
conn.setCheckoutTimestamp(System.currentTimeMillis());
conn.setLastUsedTimestamp(System.currentTimeMillis());
state.activeConnections.add(conn); // 添加到活跃连接集合
state.requestCount++;
state.accumulatedRequestTime += System.currentTimeMillis() - t;
} else {
... ...// 统计失败的情况
}
}
}
}
return conn;
}
2.pushConnection() 释放连接
当调用 proxyConnection 对象的 close() 方法时,连接并没有真正关闭,而是通过 PooledDataSource.pushConnection() 方法将 PooledConnection 归还给了关联的 PooledDataSource。
- 从活跃连接集合(即前面提到的 activeConnections 集合)中删除传入的 PooledConnection 对象。
- 检测该 PooledConnection 对象是否可用。如果连接已不可用,则递增 badConnectionCount 字段进行统计,之后,直接丢弃 PooledConnection 对象即可。如果连接依旧可用,则执行下一步。
- 检测当前 PooledDataSource 连接池中的空闲连接是否已经达到上限值。如果达到上限值,则 PooledConnection 无法放回到池中,正常关闭其底层的数据库连接即可。如果未达到上限值,则继续执行下一步。
- 将底层连接重新封装成 PooledConnection 对象,并添加到空闲连接集合( idleConnections 集合),然后唤醒所有阻塞等待空闲连接的线程。
pushConnection() 方法的核心实现
org.apache.ibatis.datasource.pooled.PooledDataSource#pushConnection
protected void pushConnection(PooledConnection conn) throws SQLException {
synchronized (state) {
state.activeConnections.remove(conn); // 步骤1:从活跃连接集合中删除该连接
if (conn.isValid()) {// 步骤2:检测该 PooledConnection 对象是否可用
// 步骤3:检测当前PooledDataSource连接池中的空闲连接是否已经达到上限值
if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
// 累计增加 accumulatedCheckoutTime(被使用的时长)
state.accumulatedCheckoutTime += conn.getCheckoutTime();
if (!conn.getRealConnection().getAutoCommit()) {
// 回滚未提交的事务
conn.getRealConnection().rollback();
}
// 步骤4:将底层连接重新封装成PooledConnection对象,
// 并添加到空闲连接集合( idleConnections 集合)存储空闲状态的 PooledConnection 对象
PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
state.idleConnections.add(newConn);
// 设置新PooledConnection对象的创建时间戳和最后使用时间戳
newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
conn.invalidate(); // 丢弃旧PooledConnection对象
// 唤醒所有阻塞等待空闲连接的线程
state.notifyAll();
} else {
// 当前PooledDataSource连接池中的空闲连接已经达到上限值
// 当前数据库连接无法放回到池中
// 累计增加 accumulatedCheckoutTime(被使用的时长)
state.accumulatedCheckoutTime += conn.getCheckoutTime();
if (!conn.getRealConnection().getAutoCommit()) {
// 回滚未提交的事务
conn.getRealConnection().rollback();
}
// 关闭真正的数据库连接
conn.getRealConnection().close();
// 将PooledConnection对象设置为无效
conn.invalidate();
}
} else {
// 统计无效PooledConnection对象个数
state.badConnectionCount++;
}
}
}
conn.isValid()
valid 字段值为 true;
realConnection 字段值不为空;
执行 PooledDataSource.pingConnection() 方法,返回值为 true。
只有这三个条件都成立,才认为这个 PooledConnection 对象可用.
public boolean isValid() {
return valid && realConnection != null && dataSource.pingConnection(this);
}
dataSource.pingConnection(this);
PooledDataSource.pingConnection() 方法会尝试请求数据库,并执行一条测试 SQL 语句,检测是否真的能够访问到数据库
protected boolean pingConnection(PooledConnection conn) {
boolean result = true; // 记录此次ping操作是否成功完成
try {
// 检测底层数据库连接是否已经关闭
result = !conn.getRealConnection().isClosed();
} catch (SQLException e) {
result = false;
}
// 如果底层与数据库的网络连接没断开,则需要检测poolPingEnabled 字段的配置,决定
// 是否能执行ping操作。另外,ping操作不能频繁执行,只有超过一定时长
// (超过 poolPingConnectionsNotUsedFor 指定的时长)未使用的连接,才需要ping操作来检测数据库连接是否正常
if (result && poolPingEnabled && poolPingConnectionsNotUsedFor >= 0
&& conn.getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor) {
try {
// 执行poolPingQuery字段中记录的测试SQL语句
Connection realConn = conn.getRealConnection();
try (Statement statement = realConn.createStatement()) {
statement.executeQuery(poolPingQuery).close();
}
if (!realConn.getAutoCommit()) {
realConn.rollback();
}
result = true; // 不抛异常,即为成功
} catch (Exception e) {
conn.getRealConnection().close();
result = false; // 抛异常,即为失败
}
}
return result;
}
4.事务接口
当我们从数据源中得到一个可用的数据库连接之后,就可以开启一个数据库事务了,事务成功开启之后,我们才能修改数据库中的数据。在修改完成之后,我们需要提交事务,完成整个事务内的全部修改操作,如果修改过程中出现异常,我们也可以回滚事务,放弃整个事务中的全部修改操作。
MyBatis 专门抽象出来一个 Transaction 接口
org.apache.ibatis.transaction.Transaction
Transaction 接口是 MyBatis 中对数据库事务的抽象,其中定义了提交事务、回滚事务,以及获取事务底层数据库连接的方法。
JdbcTransaction、ManagedTransaction 是 MyBatis 自带的两个 Transaction 接口实现,这里也使用到了工厂方法模式
TransactionFactory 是用于创建 Transaction 的工厂接口,其中最核心的方法是 newTransaction() 方法,它会根据数据库连接或数据源创建 Transaction 对象。
JdbcTransactionFactory 和 ManagedTransactionFactory 是 TransactionFactory 的两个实现类,分别用来创建 JdbcTransaction 对象和 ManagedTransaction 对象。
4.1 JdbcTransaction 的实现
其中维护了事务关联的数据库连接以及数据源对象,同时还记录了事务自身的属性,例如,事务隔离级别和是否自动提交。
在构造函数中,JdbcTransaction 并没有立即初始化数据库连接(也就是 connection 字段),connection 字段会被延迟初始化,具体的初始化时机是在调用 getConnection() 方法时,通过dataSource.getConnection() 方法完成初始化。
org.apache.ibatis.transaction.jdbc.JdbcTransaction#openConnection
在日常使用数据库事务的时候,我们最常用的操作就是提交和回滚事务,Transaction 接口将这两个操作抽象为 commit() 方法和 rollback() 方法。在 commit() 方法和 rollback() 方法中,JdbcTransaction 都是通过 java.sql.Connection 的同名方法实现事务的提交和回滚的。
5.扩展
5.1 工厂方法模式
工厂方法模式中定义了 Factory 这个工厂接口,如下图所示,其中定义了 createProduct() 方法创建右侧继承树中的对象,不同的工厂接口实现类会创建右侧继承树中不同 Product 实现类(例如 ProductImpl 1 和 ProductImpl 2)。
我们可以看到工厂方法模式由四个核心角色构成。
-
Factory 接口:工厂方法模式的核心接口之一。使用方会依赖 Factory 接口创建 Product 对象实例。
-
Factory 实现类(图中的 FactoryImpl 1 和 FactoryImpl 2):用于创建 Product 对象。不同的 Factory 实现会根据需求创建不同的 Product 实现类。
-
Product 接口:用于定义业务类的核心功能。Factory 接口创建出来的所有对象都需要实现 Product 接口。使用方依赖 Product 接口编写其他业务实现,所以使用方关心的是 Product 接口这个抽象,而不是其中的具体实现逻辑。
-
Product 实现类(图中的 ProductImpl 1 和 ProductImpl 2):实现了 Product 接口中定义的方法,完成了具体的业务逻辑。
举个例子:
这里假设一个场景:目前我们要做一个注册中心模块,已经有了 ZookeeperImpl 和 EtcdImpl 两个业务实现类,分别支持了与 ZooKeeper 交互和与 etcd 交互,此时来了个新需求,需要支持与 Consul 交互。该怎么解决这个需求呢?那就是使用工厂方法模式,我们只需要添加新的 ConsulFactory 实现类和 ConsulImpl 实现类即可完成扩展。
工厂方法的好处:
工厂方法模式最终也是符合“开放-封闭”原则的,可以通过添加新的 Factory 接口实现和 Product 接口实现来扩展整个体系的功能。另外,工厂方法模式对使用方暴露的是 Factory 和 Product 这两个抽象的接口,而不是具体的实现,也就帮助使用方面向接口编程。
5.2 CachingExecutor与TransactionalCacheManager和TransactionalCache类之间的关系及其各自的作用是什么?
- CachingExecutor 是 MyBatis 中的一个执行器实现,它主要负责提供缓存机制。CachingExecutor 的核心功能是提高系统性能,通过缓存 SQL 语句的执行结果来减少数据库操作的次数。如果在缓存中找到了相同的 SQL 语句及其参数,则直接从缓存中获取结果,无需重新执行数据库操作。
- TransactionalCacheManager(简称 tcm)是 CachingExecutor 的一个成员变量,专门用于管理二级缓存数据。它是一个事务性缓存管理器,确保了缓存中的数据与数据库中的数据保持一致。TransactionalCacheManager 的工作原理是在事务提交后才将缓存数据真正写入到数据库,从而防止了脏读现象。
- TransactionalCache 是一个实现了 Cache 接口的类,它被 TransactionalCacheManager 用作缓存装饰器。在 MyBatis 中,TransactionalCache 实现了事务性缓存的功能,即只有在事务提交之后,对缓存的修改才会生效。如果事务回滚或不提交,则不会对缓存产生影响。
CachingExecutor 提供了缓存机制以提高性能,而 TransactionalCacheManager 作为其内部组件,负责管理这些缓存数据,并确保它们与数据库保持同步。TransactionalCache 作为装饰器,进一步增强了缓存的事务性特征,使得缓存操作仅在事务成功提交时才生效。
org.apache.ibatis.executor.CachingExecutor
// 真正用来操作数据库操作的 Executor对象
private final Executor delegate;
5.3 SqlSessionHolder扩展
SqlSessionTemplate正是利用了SqlSessionHolder,保证了数据库操作过程中的线程安全和事务完整性。
SqlSessionTemplate应用了代理的方式,所以每次操作数据库时,都会进入切面通过调用getSqlSession方法获取真正操作数据库的sqlSession,此时就依赖到了SqlSessionHolder
org.mybatis.spring.SqlSessionUtils
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
// 根据sqlSessionFactory,从TransactionSynchronizationManager定义的资源map中获取当前线程对应的SqlSessionHolder
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
// 从SqlSessionHolder中提取符合要求的SqlSession对象
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
LOGGER.debug(() -> "Creating a new SqlSession");
// 如果无法通过SqlSessionHolder获取到sqlSession,则说明当前线程没有未完成的事务
// 那就利用sessionFactory新创建一个sqlSession
session = sessionFactory.openSession(executorType);
// 新创建一个SqlSessionHolder,并且将刚创建的sqlSession与其绑定
// 然后将SqlSessionHolder对象注册到TransactionSynchronizationManager中的resources
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
//在当前存在事务的前提下,如果sqlSessionHolder中不存在符合要求的SqlSession,则新建一个SqlSession并将其赋给sqlSessionHolder,最终利用 TransactionSynchronizationManager将sqlSessionHolder与sessionFactory绑定。
private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
SqlSessionHolder holder;
//判断当前是否存在事务
if (TransactionSynchronizationManager.isSynchronizationActive()) {
Environment environment = sessionFactory.getConfiguration().getEnvironment();
if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
LOGGER.debug(() -> "Registering transaction synchronization for SqlSession [" + session + "]");
// 利用新建的sqlSession创建一个SqlSessionHolder
holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
// 将sessionFactory和SqlSessionHolder作为key和value
// 绑定到TransactionSynchronizationManager中的resources(map)属性中
TransactionSynchronizationManager.bindResource(sessionFactory, holder);
TransactionSynchronizationManager
.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
holder.setSynchronizedWithTransaction(true);
holder.requested();
} else {
if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {
LOGGER.debug(() -> "SqlSession [" + session
+ "] was not registered for synchronization because DataSource is not transactional");
} else {
throw new TransientDataAccessResourceException(
"SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
}
}
} else {
LOGGER.debug(() -> "SqlSession [" + session
+ "] was not registered for synchronization because synchronization is not active");
}
}
public static void bindResource(Object key, Object value) throws IllegalStateException {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Assert.notNull(value, "Value must not be null");
Map<Object, Object> map = resources.get();
// 获取当前线程对应的map,如果map为空,则新建一个map对象
if (map == null) {
map = new HashMap<>();
resources.set(map);
}
// 将sessionFactory和SqlSessionHolder作为key和value进行绑定,存入map
// 这样在下一次想要获取sqlSession的时候
// 就可以通过TransactionSynchronizationManager.getResource方法获取到当前事务正在使用的sqlSession了
Object oldValue = map.put(actualKey, value);
// Transparently suppress a ResourceHolder that was marked as void...
if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {
oldValue = null;
}
if (oldValue != null) {
throw new IllegalStateException(
"Already value [" + oldValue + "] for key [" + actualKey + "] bound to thread");
}
}
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
notNull(session, NO_SQL_SESSION_SPECIFIED);
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
// 根据sqlSessionFactory,从TransactionSynchronizationManager定义的资源map中
// 获取当前线程对应的SqlSessionHolder
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
// 如果存在holder,说明当前仍存在事务操作,那么执行released方法
// released方法会将referenceCount--(引用计数减一)
if ((holder != null) && (holder.getSqlSession() == session)) {
LOGGER.debug(() -> "Releasing transactional SqlSession [" + session + "]");
holder.released();
} else { // 如果不存在holder,说明当前的数据操作不存在事务,那么直接将sqlSession回收至连接池即可
LOGGER.debug(() -> "Closing non transactional SqlSession [" + session + "]");
session.close();
}
}
public void released() {
// 每当一个sqlSession执行完毕,holder持有引用的次数减一
this.referenceCount--;
}
5.4 SqlSessionTemplate 类扩展
org.mybatis.spring.SqlSessionTemplate
SqlSessionTemplate是Spring框架中提供的一个模板类,用于简化数据库操作。它基于MyBatis的SqlSession,提供了一些常用的方法,使得开发者可以更加方便地执行CRUD(创建、读取、更新、删除)操作。
5.5 SqlSessionInterceptor内部私有类
org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//通过SqlSessionUtils.getSqlSession()获得真实处理CRUD的持久层,默认为DefaultSqlSession
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
try {
//执行CRUD操作
Object result = method.invoke(sqlSession, args);
//非事务处理的数据操作需要强制commit 本次是有事务处理的
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
......
throw unwrapped;
} finally {
//关闭sqlSession
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}