MyBatis源码解析之基础模块—Transaction
前文回顾
上一篇,咱们一起学习了Mybatis的DataSource模块相关源码,掌握了三种数据源工厂的逻辑,同时也掌握非池化数据源的连接创建,池化数据源如何从空闲列表获取连接并放到活跃连接列表,及连接的归还到空闲队列的逻辑。
下面跟随笔者的思路,咱们继续学习另一个重要模块——Transaction模块。
核心要点
本篇幅主要讲解Mybatis在事务管理的抽象方案,以及提供的两种简单实现:jdbc实现及外部容器的处理逻辑。
架构设计
Transaction模块所在包路径为org.apache.ibatis.transaction
,其具体划分如下:
transaction
- jdbc
- JdbcTransaction
- JdbcTransactionFactory
- managed
- ManagedTransaction
- ManagedTransactionFactory
- DataSourceException
- TransactionFactory
- Transaction
老规矩,咱们先从宏观架构图上了解设计脉络,见下图:
不想说又不得不说,Mybatis真是将Factory设计模式用户的炉火纯青。纵观整个项目源码,大量的使用工厂或工厂方法模式。
源码解读
基于面向接口编程的思路,咱们首先看下Mybatis事务管理中的两个核心接口:Transaction
和TransactionFactory
。
Transaction
正如Mybatis作者对该接口的定义:
// Handles the connection lifecycle that comprises: its creation, preparation, commit/rollback and close.
翻译过来就是:处理整个连接生命周期的各种操作,包括连接创建,事务提交、回滚及连接关闭等数据库事务相关的操作。
该接口共有五个方法。对于码畜们来说,源码加注释才是国际通用语言。各方法描述如下:
/**
* 处理整个连接生命周期的各种操作,包括连接创建,事务提交、回滚及连接关闭等数据库事务相关的操作。
*/
public interface Transaction {
/** 获取数据库连接 */
Connection getConnection() throws SQLException;
/** 数据提交 */
void commit() throws SQLException;
/** 数据回滚 */
void rollback() throws SQLException;
/** 关闭数据库连接 */
void close() throws SQLException;
/** 获取超时时间,如果设置的话,Mybatis默认的两种实现,均设置为null */
Integer getTimeout() throws SQLException;
}
以上为Transaction事务处理的核心接口方法。具体的实现逻辑等咱们稍后进行解析说明。
TransactionFactory
见名知意,该接口为事务的创建工厂,其唯一的目的就是创建事务对象。从源码中可以看出TransactionFactory
功能非常简单,就是获取事务对象。当然获取事务前会设置相关属性。具体见代码加注释:
package org.apache.ibatis.transaction;
import java.sql.Connection;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.ibatis.session.TransactionIsolationLevel;
/**
* 事务创建工厂接口
*/
public interface TransactionFactory {
*/
/** 设置相关配置信息 */
default void setProperties(Properties props) {
// NOP
}
/** 根据连接对象获取事务对象 */
Transaction newTransaction(Connection conn);
/**根据数据源、事务隔离级别、是否自动提交属性 构建事务对象*/
Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
}
一个设置配置信息的方法,两个获取事务对象的重载方法。
JdbcTransaction
接下来看下基于jdbc的事务管理方案:
JdbcTransaction类实现中有四个属性。分别为连接对象、数据源、事务隔离级别、是否自动提交等。
其构造方法有两个。一个是基于connection对象为参数的构造方法。一个则是基于数据源、事务隔离级别、autoCommit为参数的构造方法。
然后学习下JdbcTransaction实现的几个方法:
getConnection() :首选判断connection对象是否为null,为null的情况下通过调用私有方法openConnection()来获取连接对象。
commit():在connection非空且设置为手动提交的方式下,执行数据库连接提交操作。
rollback():在connection非空且设置为手动提交的方式下,执行数据库连接回滚操作。
close():在connection非空时执行数据库连接关闭操作,当然,如果数据库为手动提交,则先设置为自动提交。
具体请看源码及注释:
package org.apache.ibatis.transaction.jdbc;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.session.TransactionIsolationLevel;
import org.apache.ibatis.transaction.Transaction;
import org.apache.ibatis.transaction.TransactionException;
public class JdbcTransaction implements Transaction {
private static final Log log = LogFactory.getLog(JdbcTransaction.class);
protected Connection connection; //连接对象
protected DataSource dataSource; //数据源
protected TransactionIsolationLevel level; //事务隔离级别
protected boolean autoCommit; //是否自动提交
public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
dataSource = ds;
level = desiredLevel;
autoCommit = desiredAutoCommit;
}
public JdbcTransaction(Connection connection) {
this.connection = connection;
}
@Override
public Connection getConnection() throws SQLException {
if (connection == null) {
openConnection();
}
return connection;
}
@Override
public void commit() throws SQLException {
if (connection != null && !connection.getAutoCommit()) {
if (log.isDebugEnabled()) {
log.debug("Committing JDBC Connection [" + connection + "]");
}
connection.commit();
}
}
@Override
public void rollback() throws SQLException {
if (connection != null && !connection.getAutoCommit()) {
if (log.isDebugEnabled()) {
log.debug("Rolling back JDBC Connection [" + connection + "]");
}
connection.rollback();
}
}
@Override
public void close() throws SQLException {
if (connection != null) {
// 非自动提交情况下,设置为自动提交
resetAutoCommit();
if (log.isDebugEnabled()) {
log.debug("Closing JDBC Connection [" + connection + "]");
}
// 关闭连接
connection.close();
}
}
protected void resetAutoCommit() {
try {
if (!connection.getAutoCommit()) {
// MyBatis does not call commit/rollback on a connection if just selects were performed.
// Some databases start transactions with select statements
// and they mandate a commit/rollback before closing the connection.
// A workaround is setting the autocommit to true before closing the connection.
// Sybase throws an exception here.
if (log.isDebugEnabled()) {
log.debug("Resetting autocommit to true on JDBC Connection [" + connection + "]");
}
connection.setAutoCommit(true);
}
} catch (SQLException e) {
if (log.isDebugEnabled()) {
log.debug("Error resetting autocommit to true "
+ "before closing the connection. Cause: " + e);
}
}
}
/**通过datasource获取数据库连接,然后属性level、autoCommit设置对应的事务隔离级别和是否自动提交*/
protected void openConnection() throws SQLException {
if (log.isDebugEnabled()) {
log.debug("Opening JDBC Connection");
}
connection = dataSource.getConnection();
if (level != null) {
connection.setTransactionIsolation(level.getLevel());
}
setDesiredAutoCommit(autoCommit);
}
/** 设置是否自动提交 */
protected void setDesiredAutoCommit(boolean desiredAutoCommit) {
try {
if (connection.getAutoCommit() != desiredAutoCommit) {
if (log.isDebugEnabled()) {
log.debug("Setting autocommit to " + desiredAutoCommit + " on JDBC Connection [" + connection + "]");
}
connection.setAutoCommit(desiredAutoCommit);
}
} catch (SQLException e) {
// Only a very poorly implemented driver would fail here,
// and there's not much we can do about that.
throw new TransactionException("Error configuring AutoCommit. "
+ "Your driver may not support getAutoCommit() or setAutoCommit(). "
+ "Requested setting: " + desiredAutoCommit + ". Cause: " + e, e);
}
}
@Override
public Integer getTimeout() throws SQLException {
return null;
}
}
JdbcTransactionFactory
JdbcTransactionFactory非常简单,只是实现了TransactionFactory两个获取实例的方法:
public class JdbcTransactionFactory implements TransactionFactory {
/** 通过数据库连接获取事务 */
@Override
public Transaction newTransaction(Connection conn) {
return new JdbcTransaction(conn);
}
/** 通过数据源、事务隔离级别、提交方式 获取事务 */
@Override
public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
return new JdbcTransaction(ds, level, autoCommit);
}
}
ManagedTransaction
通过其名字可知,该事务管理方式是有外部容器托管的。ManagedTransaction 与JdbcTransaction一样也有四个属性,不过区别的一个属性是boolean类型的closeConnection属性,与之对应的构造方法也有所区别。
对应ManagedTransaction的接口实现,其数据库连接对象获取也是通过datasource,而commit、rollback则是空实现,相对应的提交回滚则由外部容器来完成。
对于事务的关闭,则根据属性字段closeConnection值来决定。具体请查看代码及注释:
public class ManagedTransaction implements Transaction {
private static final Log log = LogFactory.getLog(ManagedTransaction.class);
private DataSource dataSource; //数据源
private TransactionIsolationLevel level; //事务隔离级别
private Connection connection; //数据库连接
private final boolean closeConnection; //是否关闭连接
public ManagedTransaction(Connection connection, boolean closeConnection) {
this.connection = connection;
this.closeConnection = closeConnection;
}
public ManagedTransaction(DataSource ds, TransactionIsolationLevel level, boolean closeConnection) {
this.dataSource = ds;
this.level = level;
this.closeConnection = closeConnection;
}
@Override
public Connection getConnection() throws SQLException {
if (this.connection == null) {
openConnection();
}
return this.connection;
}
@Override
public void commit() throws SQLException {
// Does nothing
}
@Override
public void rollback() throws SQLException {
// Does nothing
}
@Override
public void close() throws SQLException {
// closeConnection 为true 且connection不为空时执行关闭
if (this.closeConnection && this.connection != null) {
if (log.isDebugEnabled()) {
log.debug("Closing JDBC Connection [" + this.connection + "]");
}
this.connection.close();
}
}
protected void openConnection() throws SQLException {
if (log.isDebugEnabled()) {
log.debug("Opening JDBC Connection");
}
this.connection = this.dataSource.getConnection();
if (this.level != null) {
this.connection.setTransactionIsolation(this.level.getLevel());
}
}
}
总结
MyBatis关于事务的实现比较简单。抽象了事务管理对象并提供了一个简单的jdbc类型实现。而Managed基本是空实现,最终会有外部容器进行托管。具体会在MyBatis集成Spring中进行说明。
关于MyBatis的Transaction模块介绍至此告一段落。感谢垂阅,如有不妥之处请多多指教~
微观世界,达观人生。
做一名踏实的coder !
欢迎关注我的个人微信公众号 : todobugs ~