9、事务补充

当事务的传播级别为SUPPORTS时,如果当前有事务,则使用当前事务,如果当前没有事务,则以无事务方式执行
这里的无事务方式执行是指,Spring不会提交事务,而且MyBatis也不会执行commit。
除非设置数据库连接的auto-commit属性为true,否则不能写入数据

spring:
    datasource:
        auto-commit: true

1、propagation = Propagation.REQUIRED
在大多数情况下,我们使用默认隔离级别Propagation.REQUIRED,当propagation = Propagation.REQUIRED时,数据库连接由Spring事务管理器在DataSourceTransactionManager#doBegin中取得
doBegin会在org.springframework.transaction.support.AbstractPlatformTransactionManager#getTransaction中被调用

org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin
protected void doBegin(Object transaction, TransactionDefinition definition) {
   DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
   Connection con = null;

   try {
      if (!txObject.hasConnectionHolder() ||
            txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
        // 由事务管理器从dataSource中获取连接
         Connection newCon = obtainDataSource().getConnection();
         if (logger.isDebugEnabled()) {
            logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
         }
         txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
      }

      txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
      con = txObject.getConnectionHolder().getConnection();

      Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
      txObject.setPreviousIsolationLevel(previousIsolationLevel);

      // Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
      // so we don't want to do it unnecessarily (for example if we've explicitly
      // configured the connection pool to set it already).
      if (con.getauto-commit()) {
         txObject.setMustRestoreauto-commit(true);
         if (logger.isDebugEnabled()) {
            logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
         }
        // 当以有事务方式执行时,Spring会将数据库连接的auto-commit属性改为false
        // 所以当开启新的事务或者说有事务方式执行时,auto-commit属性会无效
        // 因为有事务,不论是当前创建新的事务,或者是继承自上一层的事务,都会是用的Spring获取的连接,auto-commit都会被改成false
         con.setauto-commit(false);
      }

      prepareTransactionalConnection(con, definition);
      txObject.getConnectionHolder().setTransactionActive(true);

      int timeout = determineTimeout(definition);
      if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
         txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
      }

      // Bind the connection holder to the thread.
      if (txObject.isNewConnectionHolder()) {
         TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
      }
   }

   catch (Throwable ex) {
      if (txObject.isNewConnectionHolder()) {
         DataSourceUtils.releaseConnection(con, obtainDataSource());
         txObject.setConnectionHolder(null, false);
      }
      throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
   }
}

以有事务方式执行时,事务的提交,也是由Spring执行
具体是在org.springframework.transaction.support.AbstractPlatformTransactionManager#processCommit中,具体不作分析了
还有一个问题。为什么有事务方式执行时,MyBatis不会提交事务?
相关逻辑在这里
org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor

private class SqlSessionInterceptor implements InvocationHandler {
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    SqlSession sqlSession = getSqlSession(
        SqlSessionTemplate.this.sqlSessionFactory,
        SqlSessionTemplate.this.executorType,
        SqlSessionTemplate.this.exceptionTranslator);
    try {
      Object result = method.invoke(sqlSession, args);
        // isSqlSessionTransactional会判断,当前执行SQL的会话sqlSession和在TransactionSynchronizationManager中和当前线程绑定的会话是否是同一个
        // 如果是同一个,则说明事务由Spring管理,所以下面的sqlSession.commit(true);不会执行
      if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
        // force commit even on non-dirty sessions because some databases require
        // a commit/rollback before calling close()
        sqlSession.commit(true);
      }
      return result;
    } catch (Throwable t) {
      Throwable unwrapped = unwrapThrowable(t);
      if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
        // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
        closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        sqlSession = null;
        Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
        if (translated != null) {
          unwrapped = translated;
        }
      }
      throw unwrapped;
    } finally {
      if (sqlSession != null) {
        closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
      }
    }
  }
}

2、当事务的传播级别由REQUIRED改成SUPPORTS时
propagation = Propagation.SUPPORTS
在org.springframework.transaction.support.AbstractPlatformTransactionManager#getTransaction中,由于propagation = Propagation.SUPPORTS,不会创建新的事务。
DataSourceTransactionManager#doBegin也不会被调用,所以auto-commit属性不会被修改,那么连接是在在哪里取的呢?
答案是在org.apache.ibatis.executor.SimpleExecutor#prepareStatement中由MyBatis自己搞定了

org.apache.ibatis.executor.SimpleExecutor#prepareStatement
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  Statement stmt;
  Connection connection = getConnection(statementLog);
  stmt = handler.prepare(connection, transaction.getTimeout());
  handler.parameterize(stmt);
  return stmt;
}

org.apache.ibatis.executor.BaseExecutor#getConnection
protected Connection getConnection(Log statementLog) throws SQLException {
  Connection connection = transaction.getConnection();
  if (statementLog.isDebugEnabled()) {
    return ConnectionLogger.newInstance(connection, statementLog, queryStack);
  } else {
    return connection;
  }
}

org.apache.ibatis.transaction.jdbc.JdbcTransaction#getConnection
@Override
public Connection getConnection() throws SQLException {
  if (connection == null) {
    openConnection();
  }
  return connection;
}

传播级别为SUPPORTS时事务的提交有两种可能
a、auto-commit属性为false,事务不会提交,或者说connection不会commit(SqlSessionInterceptor的isSqlSessionTransactional此时仍然返回true)
b、auto-commit属性为true,事务由java.sql.Connection各个实现类自己在执行完SQL后自动提交了

3、不使用Spring事务,去掉@Transactionl注解
取数据库连接和使用propagation = Propagation.SUPPORTS时相同
事务的提交也有两种可能
a、auto-commit属性为false,事务由MyBatis提交(SqlSessionInterceptor的isSqlSessionTransactional此时返回false)
b、auto-commit属性为true,事务由java.sql.Connection各个实现类自己在执行完SQL后自动提交了
a和b两种情况都是执行一条SQL做一次commit。
在情况b中,SqlSessionInterceptor的isSqlSessionTransactional同样返回false,MyBatis为什么没有提交事务?

SqlSessionInterceptor中sqlSession.commit(true),最终调用的是org.apache.ibatis.transaction.jdbc.JdbcTransaction#commit

org.apache.ibatis.transaction.jdbc.JdbcTransaction#commit
@Override
public void commit() throws SQLException {
    // 当connection的auto-commit属性为true时,MyBatis不会自动commit
  if (connection != null && !connection.getauto-commit()) {
    if (log.isDebugEnabled()) {
      log.debug("Committing JDBC Connection [" + connection + "]");
    }
    connection.commit();
  }
}

最后一个问题。propagation = Propagation.REQUIRED时,如果设置auto-commit属性为true,Spring会自动改成false,那么事务提交之后会将连接的自动提交属性改回来吗

答案是会

DataSourceTransactionManager.doBegin片段      
if (con.getauto-commit()) {
            // 这里将DataSourceTransactionObject的mustRestoreAutoCommit属性改成true,表示事务提交之后要重置connection的auto-commit属性为true
         txObject.setMustRestoreauto-commit(true);
         if (logger.isDebugEnabled()) {
            logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
         }```
        // 当以有事务方式执行时,Spring会将数据库连接的auto-commit属性改为false
        // 所以当开启新的事务或者说有事务方式执行时,auto-commit属性会无效
        // 因为有事务,不论是当前创建新的事务,或者是继承自上一层的事务,都会是用的Spring获取的连接,auto-commit都会被改成false
         con.setauto-commit(false);
      }

具体调用路径是

org.springframework.transaction.support.AbstractPlatformTransactionManager#processCommit
org.springframework.transaction.support.AbstractPlatformTransactionManager#cleanupAfterCompletion
org.springframework.jdbc.datasource.DataSourceTransactionManager#doCleanupAfterCompletion

protected void doCleanupAfterCompletion(Object transaction) {
   DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;

   // Remove the connection holder from the thread, if exposed.
   if (txObject.isNewConnectionHolder()) {
      TransactionSynchronizationManager.unbindResource(obtainDataSource());
   }

   // Reset connection.
   Connection con = txObject.getConnectionHolder().getConnection();
   try {
        // 这里DataSourceTransactionObject的mustRestoreAutoCommit属性为true,修改连接auto-commit属性为true
      if (txObject.isMustRestoreAutoCommit()) {
         con.setAutoCommit(true);
      }
      DataSourceUtils.resetConnectionAfterTransaction(con, txObject.getPreviousIsolationLevel());
   }
   catch (Throwable ex) {
      logger.debug("Could not reset JDBC Connection after transaction", ex);
   }

   if (txObject.isNewConnectionHolder()) {
      if (logger.isDebugEnabled()) {
         logger.debug("Releasing JDBC Connection [" + con + "] after transaction");
      }
      DataSourceUtils.releaseConnection(con, this.dataSource);
   }

   txObject.getConnectionHolder().clear();
}

你可能感兴趣的:(9、事务补充)