ORM - MyBatis事务管理机制详解
事务这个词我们多次遇到,在Executor执行器里面,在SqlSession里面,在二级缓存里面。和事务打交道避免不了事务的管理,那么MyBatis是如何进行事务管理的,这篇文章将带领大家一解疑惑。
初识事务
数据库事务的定义就是将多个执行单元看作一个整体,要么全部执行成功,要么执行失败。事务具有ACID属性,事务的隔离级别有读未提交,读提交,可重复读,串行等等,这些涉及到数据库的知识我们在此不做展开,希望同学们有这方面的基础。
MyBatis 事务管理的接口是Transaction,它其实包装的就是一个数据库连接,处理这个连接的生命周期,它的方法如下
/**
* 包装一个数据库连接,处理一个数据库连接的生命周期,包括创建,准备,提交,回滚,关闭
* @author Clinton Begin
*/
public interface Transaction {
/**
* 获取内部的数据库连接
*/
Connection getConnection() throws SQLException;
/**
* 提交
*/
void commit() throws SQLException;
/**
* 回滚
*/
void rollback() throws SQLException;
/**
* 关闭连接
*/
void close() throws SQLException;
/**
* 获取超时时间
*/
Integer getTimeout() throws SQLException;
}
Transaction有两个实现JdbcTransaction和ManagedTransaction,提供的这两种管理机制来管理事务
- JdbcTransaction:使用Jdbc中的java.sql.Connection来管理事务,包括提交回滚
- ManagedTransaction:在这种机制下,MyBatis不会管理事务,而是交由程序的运行容器(weblogic,tomcat)来进行管理。
创建事务
在mybatis- config.xml中可以配置事务管理的类型,其中transactionManager的类型配置为JDBC,将会以JdbcTransaction管理机制来管理事务。
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://xxxx:3306/xxxx?useUnicode=true"/>
<property name="username" value="xxxx"/>
<property name="password" value="xxxx"/>
</dataSource>
</environment>
</environments>
XMLConfigBuilder 中的environmentsElement会解析transactionManager类型,并且会创建一个TransactionFactory类型的事务工厂,这个工厂的作用就是来创建Transaction事务对象。
// XMLConfigBuilder
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
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.setEnvironment(environmentBuilder.build());
}
}
}
}
TransactionFactory 作为对象工厂,其功能就是创建一个Transaction对象。创建方式有两种
- 从已有的连接中创建Transaction对象
- 根据数据源,数据库隔离级别,自动提交创建Transaction对象
public interface TransactionFactory {
/**
* 设置事务工厂私有属性
*/
default void setProperties(Properties props) {
// NOP
}
/**
* 从已有的连接中创建Transaction对象
*/
Transaction newTransaction(Connection conn);
/**
* 根据数据源,数据库隔离级别,自动提交创建Transaction对象
*/
Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
}
TransactionFactory 有两个实现,一个是JdbcTransactionFactory,另一个是ManagedTransactionFactory,他们分别创建出来的就是JdbcTransaction和ManagedTransaction对象。以JdbcTransactionFactory为例,其实现也是很简单的,没有什么逻辑。
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);
}
}
上面创建完TransactionFactory后,就会被Environment引用。接下来在获取SqlSession的时候,会根据事务工厂创建一个Transaction事务对象,根据这个对象,再创建具体的Executor,最终创建出创建SqlSession对象。
// 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();
}
}
JdbcTransaction 内容如下,也是没有什么复杂的逻辑,基本都是对JDBC connection的包装
/**
* {@link Transaction} that makes use of the JDBC commit and rollback facilities directly.
* It relies on the connection retrieved from the dataSource to manage the scope of the transaction.
* Delays connection retrieval until getConnection() is called.
* Ignores commit or rollback requests when autocommit is on.
*
* @author Clinton Begin
*
* @see JdbcTransactionFactory
*
*
* Transaction 完全直接利用JDBC的提交和回滚管理机制。它依赖于从数据源获取到的连接connection来管理事务transaction的生命周期。
* 如果开启了自动提交,那么提交和回滚将会被忽略。
*/
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 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中的java.sql.Connection来管理事务,包括提交回滚
- ManagedTransaction:在这种机制下,MyBatis不会管理事务,而是交由程序的运行容器(weblogic,tomcat)来进行管理。