一、概述
事务的概念,大家都不会陌生。在我们写增删改的时候,我们肯定都需要加上事务,来保证数据的一致性。MyBatis作为Java语言的数据库框架,对数据库的事务管理是其非常重要的一个方面。在Mybatis中,同样提供了事务的功能,所以我们有必要了解一下MyBatis的事务管理的实现机制。
二、Mybatis事务管理机制
Mybatis也提供了事务的机制,MyBatis将事务抽象成了Transaction接口。Transaction接口定义如下:
代码语言:javascript复制//包装数据库连接, 处理connection连接的生命周期,包括:它的创建、准备、提交/回滚和关闭。
public interface Transaction {
/**
* 获取数据库连接
*/
Connection getConnection() throws SQLException;
/**
* 提交
*/
void commit() throws SQLException;
/**
* 回滚
*/
void rollback() throws SQLException;
/**
* 关闭数据库连接
*/
void close() throws SQLException;
/**
* 获取事务超时时间
*/
Integer getTimeout() throws SQLException;
}
Transaction的继承关系图:
如上图,Transaction有两个实现子类,对应着MyBatis的事务管理的两种形式:
- JdbcTransaction:直接使用JDBC提交和回滚功能,依赖于从数据源获取到的连接【java.sql.Connection对象】来管理事务;
- ManagedTransaction:这种机制MyBatis自身不会去实现事务管理,而是让程序的容器如(JBOSS,Weblogic)来实现对事务的管理;
事务的配置,在Mybatis全局配置文件中,我们进行了事务管理器相关的配置,它的作用就是生成一个事务工厂,然后事务工厂用于创建事务对象。
代码语言:javascript复制<environments default="development">
<environment id="development">
//指定事务管理器是Jdbc类型
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/user_mybatis?serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<environment节点定义了连接某个数据库的信息,其子节点<transactionManager 的type 会决定具体使用什么类型的事务管理机制。
配置完后,在解析XML的时候,也就是在XMLConfigBuilder类的parseConfiguration方法里面调用了environmentsElement方法解析environment标签内配置的信息,包括事务管理器和数据源的解析:
代码语言:javascript复制//解析environments
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
//development
environment = context.getStringAttribute("default");
}
//循环遍历XML各个节点
for (XNode child : context.getChildren()) {
//获取ID属性,对应<environment id="development">的ID属性, 即id=development
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
//获取transactionManager事务管理器相关配置
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
//解析数据源配置,具体见下面分析
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
//将解析出来的数据源配置等信息赋值给configuration对象
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
到这里,事务相关的配置已经解析完成,下一步就要看一下事务是在哪里创建的。
如果看了前面总结的sqlSession获取流程的小伙伴一下就能想到,事务其实是在获取sqlSession的会话中创建的,我们来看看:
代码语言:javascript复制// DefaultSqlSessionFactory
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
// 获取事务工厂
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 创建事务对象
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 创建执行器
final Executor executor = configuration.newExecutor(tx, execType);
// 创建SqlSession对象
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " e, e);
} finally {
ErrorContext.instance().reset();
}
}
如上我们可以看到,在配置文件中我们将<transactionManager的type配置为"JDBC",所以在MyBatis初始化解析<environment节点时,会根据type="JDBC"创建一个JdbcTransactionFactory工厂,然后根据事务工厂创建一个Transaction事务对象,接着又根据这个事务对象,创建具体的Executor,最终创建出创建SqlSession对象。
下面我们看一下TransactionFactory的源码:
代码语言:javascript复制public interface TransactionFactory {
/**
* 设置事务工厂相关属性
*/
void setProperties(Properties props);
/**
* 从已有的连接中创建Transaction对象
*/
Transaction newTransaction(Connection conn);
/**
* 根据数据源,数据库隔离级别,自动提交创建Transaction对象
* @param dataSource 数据源信息
* @param 数据库隔离级别
* @param 是否自动提交
* @return Transaction
* @since 3.1.0
*/
Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
}
TransactionFactory默认有两个实现子类:
- JdbcTransactionFactory:创建JdbcTransaction事务对象;
- ManagedTransactionFactory:创建ManagedTransaction事务对象;
通过事务工厂TransactionFactory很容易获取到Transaction对象实例,我们以JdbcTransaction为例,看一下JdbcTransactionFactory是怎样生成JdbcTransaction的,代码如下:
代码语言:javascript复制public class JdbcTransactionFactory implements TransactionFactory {
@Override
public void setProperties(Properties props) {
}
@Override
public Transaction newTransaction(Connection conn) {
//创建JdbcTransaction事务
return new JdbcTransaction(conn);
}
@Override
public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
//创建JdbcTransaction事务,传入数据源、数据库隔离级别、是否自动提交属性
return new JdbcTransaction(ds, level, autoCommit);
}
}
代码语言:javascript复制简单理解,JdbcTransaction只是相当于对java.sql.Connection事务处理进行了一次包装,利用connection的commit()、rollback()、close()来实现对事务的管理。
//直接使用JDBC提交和回滚功能。它依赖于从数据源获取到的连接来管理事务的范围
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 autoCommmit;
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()
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()
connection.rollback();
}
}
@Override
public void close() throws SQLException {
if (connection != null) {
resetAutoCommit();
if (log.isDebugEnabled()) {
log.debug("Closing JDBC Connection [" connection "]");
}
//使用connection的close()
connection.close();
}
}
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);
}
}
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);
}
}
}
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);
}
@Override
public Integer getTimeout() throws SQLException {
return null;
}
}
三、总结
MyBatis提供的两种管理机制来管理事务:
- JdbcTransaction:直接使用JDBC提交和回滚功能,依赖于从数据源获取到的连接来管理事务的范围。设置为自动提交时会忽略提交或回滚请求。
- ManagedTransaction:MyBatis不会管理事务,这使得程序的运行容器能够管理事务的整个生命周期。这种情况下它会忽略所有提交或回滚请求。
Mybatis的事务管理这一块相对来说没有其他复杂,基本上都是封装了原生JDBC的connection连接来实现事务的管理。当然,如果我们集成到Spring以后,基本上也是使用Spring提供的事务,更加强大更加好用。
鉴于笔者水平有限,如果文章有什么错误或者需要补充的,希望小伙伴们指出来,希望这篇文章对大家有帮助。