一、問題描述
場(chǎng)景描述:有這樣一個(gè)service方法,調(diào)用了兩個(gè)dao中的方法。第一個(gè)方法按照傳入的id批量更新用戶名。第二個(gè)dao方法無(wú)數(shù)據(jù)庫(kù)操作,僅僅拋出一個(gè)RuntimeException.
這個(gè)service方法通過(guò)xml配置由spring事務(wù)管理的。
兩個(gè)DAO類中分別有SqlSessionTemplate類型的屬性template,使用IOC的方式注入的。
 public void batchUpdate() {
  String username="newname59";

  List<Integer> idList=Arrays.asList(10000,10001);
  userDao.batchUpdateUsername(username, idList);
  testDao.testException();
  userDao.batchUpdateUserage(55, idList);
  testDao.testNormal(); 
 }
當(dāng)userDao及testDao中注入的是ExcutorType.Simple類型的template時(shí),批量更新用戶名的操作會(huì)回滾。
當(dāng)userDao及testDao中注入的是ExcutorType.Batch類型的template時(shí),批量更新用戶名的操作未回滾。

經(jīng)過(guò)檢查數(shù)據(jù)庫(kù)日志,發(fā)現(xiàn)第二種情況的數(shù)據(jù)庫(kù)執(zhí)行序列如下:
1 set autocommit = 0
2 rollback
3 update t_user set username="newname59" where id = '10000'
4 update t_user set username="newname59" where id = '10001'
5 set autocommit = 1
更新操作在回滾之后執(zhí)行,故回滾失敗。

調(diào)試源代碼發(fā)現(xiàn)有如下序列:
AbstractPlatformTransactionManager
 processRollback () --> triggerAfterCompletion() --> invokeAfterCompletion()
-->
TransactionSynchronizationUtils
 invokeAfterCompletion()
-->
SqlSessionUtils
    afterCompletion()
-->
DefaultSqlSession
 close()
-->
BaseExecutor
 close() --> rollback() --> flushStatement()
-->
BatchExecutor
 doFlushStatements()
這時(shí)就執(zhí)行了sql語(yǔ)句。
 
簡(jiǎn)單來(lái)說(shuō):拋出異常,spring事務(wù)回滾,清理資源關(guān)閉sqlSession.
mybatis關(guān)閉sqlsession,關(guān)閉前先f(wàn)lushStatements,執(zhí)行未執(zhí)行的sql語(yǔ)句,然后再rollback.
但是這個(gè)rollback方法里判斷connection是受事務(wù)管理的,就不執(zhí)行任何操作。
 
 public void rollback(boolean required) throws SQLException {
    if (!closed) {
      try {
        clearLocalCache();
        flushStatements();
      } finally {
        if (required) {
          transaction.rollback();
        }
      }
    }
  } 

   public void rollback() throws SQLException {
        if (!this.isConnectionTransactional) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Rolling back JDBC Connection [" + this.connection + "]");
            }
            this.connection.rollback();
        }
    }
 
二、解決辦法:
 1、在自己的應(yīng)用程序中寫個(gè)攔截器。在執(zhí)行完executor的close()之后,由這個(gè)攔截器再執(zhí)行一遍connection.rollback(),但從代碼的可讀性來(lái)看,會(huì)非常的差。
 2、修改mybatis的bug。修改BaseExecutor的rollback()
 public void rollback(boolean required) throws SQLException {
    if (!closed) {
      try {
        clearLocalCache();
  if (!required) { 
   flushStatements();
  }
      } finally {
        if (required) {
          transaction.rollback();
        }
      }
    }
  } 

不知道大家有沒有碰到過(guò)類似的問題,又是通過(guò)什么方案解決的呢?