mybatis的事务管理器在环境配置environments中通过transactionManager来指定,mybatis自带了JDBC和MANAGED两类事务管理器。
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
<property name="..." value="..."/>
</transactionManager>
</environment>
</environments>
mybatis的事务管理器除了提供事务的提交和回滚外还为其sqlSession‘提供’了db连接。这些相关操作在Transaction接口中都有定义:
// src/main/java/org/apache/ibatis/transaction/Transaction.java
public interface Transaction {
// 返回Transaction内连接给到外部使用
Connection getConnection() throws SQLException;
// 关闭该Transaction内的bd连接
void close() throws SQLException;
// 对该Transaction内的db连接进行提交
void commit() throws SQLException;
// 对该Transaction内的db连接进行回滚
void rollback() throws SQLException;
// 返回事务的sql查询超时
Integer getTimeout() throws SQLException;
Transaction还定义了一个getTimeout方法,在生成执行语句的Statement时,若事务管理其器的该方法的返回非null,则会将statement的查询超时设置为该方法的返回值,不过mybatis自带的JDBC和MANAGED事务管理器的返回都是null。
// src/main/java/org/apache/ibatis/transaction/jdbc/JdbcTransaction.java
@Override
public Integer getTimeout() throws SQLException {
return null;
}
// src/main/java/org/apache/ibatis/transaction/jdbc/JdbcTransaction.java
@Override
public Integer getTimeout() throws SQLException {
return null;
}
一、事务管理器的使用
1、连接管理
每个sqlSession都有一个Executor来执行sql,Executor中的Transaction为Executor提供了db连接和连接的事务管理。每个sqlSession都有一个被其唯一占用的连接,而这个连接就由Transaction管理。
mybatis也是对JDBC的封装,所以对upate、delete和非缓存的select这些‘真’对db执行的语句执行前都需要一个Statement,Executor通过其transaction的getConnection来获取connection去创建Statement。
在创建事务管理器是需要给定一个DataSource,后续调用Transaction的getConnection时若Transaction的connection为null,则通过DataSource来获取连接,不为null则直接放回。所以一个事务管理器只保持一个连接,也就某个sqlSession执行时只会使用到它自己的连接。
public class JdbcTransaction implements Transaction {
protected Connection connection;
protected DataSource dataSource;
// 创建JdbcTransaction是需要指定DataSource
public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
dataSource = ds;
level = desiredLevel;
autoCommit = desiredAutoCommit;
}
@Override
public Connection getConnection() throws SQLException {
if (connection == null) {
// 从dataSource获取一个新连接
openConnection();
}
return connection;
}
protected void openConnection() throws SQLException {
// 从dataSource获取连接
connection = dataSource.getConnection();
if (level != null) {
connection.setTransactionIsolation(level.getLevel());
}
setDesiredAutoCommit(autoCommit);
}
}
Transaction中定义了close方法来关闭该事务管理器中的连接:
// src/main/java/org/apache/ibatis/transaction/jdbc/JdbcTransaction.java
public void close() throws SQLException {
if (connection != null) {
...
connection.close();
}
}
在关闭sqlSession时会关闭seqSession的executor,在executor中会调用Transaction的close方法:
// src/main/java/org/apache/ibatis/session/defaults/DefaultSqlSession.java
// sqlSession关闭executor
public class DefaultSqlSession implements SqlSession {
public void close() {
try {
executor.close(isCommitOrRollbackRequired(false));
...
} finally {
...
}
}
}
// src/main/java/org/apache/ibatis/executor/BaseExecutor.java
// Executor关闭Transaction
public void close(boolean forceRollback) {
try {
try {
...
} finally {
if (transaction != null) {
transaction.close();
}
}
} catch (SQLException e) {
...
} finally {
transaction = null;
....
}
}
可见每个sqlSession持有一个唯一被其使用的连接,所以及时关闭sqlSession释放连接是非常重要的,应该每次收到HTTP请求时打开一个SqlSession,返回一个响应后就关闭它。
2、 事务提交和回滚
sqlSession的commit和rollback方法可以对该sqlSesion的连接的事务进行提交和回滚,但并不一定会真的去commit和rollback。
因为如果连接已经是自动提交的了,那么也就不用再去主动commit了,或者该连接就没有更改过db的数据,也就是没有执行过update或delete,那么也不用提交。
在sqlSession中autoCommit和dirty两个变量记录了连接是否自动提交或执行过update或delete。对于没有自动记录和没有修改过dn的commit,是不会去触发‘真’的cimmit的。当然我们也可以在commit是指定true参数,强制进行提交。
public class DefaultSqlSession implements SqlSession {
// 是否自动提交
private final boolean autoCommit;
// 是否更改过db
private boolean dirty;
// 执行update或delete语句
public int update(String statement, Object parameter) {
...
dirty = true;
...
}
// 默认不强制提交
public void commit() {
commit(false);
}
public void commit(boolean force) {
try {
executor.commit(isCommitOrRollbackRequired(force));
// 提交后直接的更改已经已经所以将dirty重置
dirty = false;
}
...
}
// 除非强制提交,否则要判断autoCommit和dirty
private boolean isCommitOrRollbackRequired(boolean force) {
return (!autoCommit && dirty) || force;
}
}
executor最终调用其transaction的commit:
// src/main/java/org/apache/ibatis/executor/BaseExecutor.java
public abstract class BaseExecutor implements Executor {
public void commit(boolean required) throws SQLException {
...
if (required) {
transaction.commit();
}
}
}
回滚也是相同的逻辑,先判断是否有必要回滚,然后又executor最终调用其transaction的rollback。
二、JDBC事务管理器
JDBC事务管理器对应的类型为src/main/java/org/apache/ibatis/transaction/jdbc/JdbcTransaction.java
1、提交和回滚
JdbcTransaction直接使用JDBC的commit和rollback对事务进行提交和回滚:
public class JdbcTransaction implements Transaction {
// db连接
protected Connection connection;
// 提交
@Override
public void commit() throws SQLException {
if (connection != null && !connection.getAutoCommit()) {
connection.commit();
}
}
// 回滚
@Override
public void rollback() throws SQLException {
if (connection != null && !connection.getAutoCommit()) {
connection.rollback();
}
}
}
2、获取连接
JdbcTransaction持有一个Connection,但外部需要连接是就将该Connection返回,若Connection为null则从DataSource中获取并给到持有的Connection然后放回。
JdbcTransaction默认不自动提交并且默认使用db的默认隔离级别,在获取sqlSession时可以对自动提交和隔离级别进行指定,在JdbcTransaction从DataSource获取到新连接是会将连接这个两个属性设置为指定的值。
// src/main/java/org/apache/ibatis/transaction/jdbc/JdbcTransaction.java
public class JdbcTransaction implements Transaction {
// 持有的连接
protected Connection connection;
// 获取连接的DataSource
protected DataSource dataSource;
// 事务隔离级别
protected TransactionIsolationLevel level;
// 是否自动提交
protected boolean autoCommit;
public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
dataSource = ds;
level = desiredLevel;
autoCommit = desiredAutoCommit;
}
// 持有的连接不为null则直接返回
@Override
public Connection getConnection() throws SQLException {
if (connection == null) {
openConnection();
}
return connection;
}
// 从DataSource获取连接并设置连接属性
protected void openConnection() throws SQLException {
// 获取连接
connection = dataSource.getConnection();
if (level != null) {
// 设置事务隔离级别
connection.setTransactionIsolation(level.getLevel());
}
// 设置自动提交
setDesiredAutoCommit(autoCommit);
}
}
3、关闭连接
JdbcTransaction在关闭连接时,对非自动提交的连接会先将连接设置为自动提交。因为JdbcTransaction在没有修改db的时候不会进行提交和回滚,但一些db在执行select是也会开启一个事务,这时若不是自动提交的,db可能会要求对select的事务也进行提交。
public class JdbcTransaction implements Transaction {
// 关闭连接
@Override
public void close() throws SQLException {
if (connection != null) {
resetAutoCommit();
connection.close();
}
}
// 设置自动提交
protected void resetAutoCommit() {
try {
if (!connection.getAutoCommit()) {
connection.setAutoCommit(true);
}
} catch (SQLException e) {
...
}
}
三、MANAGED事务管理器
MANAGED事务管理器对应的类型为src/main/java/org/apache/ibatis/transaction/managed/ManagedTransaction.java。
ManagedTransaction对于提交和回滚不会进行任何操作,且连接只会设置其事务隔离级别。所以使用它时db应该设置自动提交,从而获取到的连接也是自动提交的,这样也就不依赖于ManagedTransaction的提交和回滚。
public class ManagedTransaction implements Transaction {
// 获取连接的DataSource
private DataSource dataSource;
// 事务的隔离级别
private TransactionIsolationLevel level;
// 连接
private Connection connection;
@Override
public void commit() throws SQLException {
// Does nothing
}
@Override
public void rollback() throws SQLException {
// Does nothing
}
// 新打开的连接只会设置事务隔离级别
protected void openConnection() throws SQLException {
this.connection = this.dataSource.getConnection();
if (this.level != null) {
this.connection.setTransactionIsolation(this.level.getLevel());
}
}
// 关闭是直接关闭
@Override
public void close() throws SQLException {
if (this.closeConnection && this.connection != null) {
this.connection.close();
}
}
}
四、事务管理的创建
在解析配置是会通过指定事务管理器类型来获取到其对应的工厂类:
// src/main/java/org/apache/ibatis/builder/xml/XMLConfigBuilder.java
public class XMLConfigBuilder extends BaseBuilder {
private TransactionFactory transactionManagerElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
TransactionFactory factory = (TransactionFactory) resolveClass(type).getDeclaredConstructor().newInstance();
return factory;
}
throw new BuilderException("Environment declaration requires a TransactionFactory.");
}
}
重SqlSessionFactory中获取SqlSession时会为SqlSession创建其Executor,在创建Executor是通过前面获取到的TransactionFactory来创建Executor的事务管理器:
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
...
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
...
}