Mybatis源码深度解析之事务管理器

721 阅读6分钟

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);
    ...
}