一、問題描述
場景描述:有這樣一個service方法,調用了兩個dao中的方法。第一個方法按照傳入的id批量更新用戶名。第二個dao方法無數據庫操作,僅僅拋出一個RuntimeException.
這個service方法通過xml配置由spring事務管理的。
兩個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();
}
當userDao及testDao中注入的是ExcutorType.Simple類型的template時,批量更新用戶名的操作會回滾。
當userDao及testDao中注入的是ExcutorType.Batch類型的template時,批量更新用戶名的操作未回滾。
經過檢查數據庫日志,發現第二種情況的數據庫執行序列如下:
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
更新操作在回滾之后執行,故回滾失敗。
調試源代碼發現有如下序列:
AbstractPlatformTransactionManager
processRollback () --> triggerAfterCompletion() --> invokeAfterCompletion()
-->
TransactionSynchronizationUtils
invokeAfterCompletion()
-->
SqlSessionUtils
afterCompletion()
-->
DefaultSqlSession
close()
-->
BaseExecutor
close() --> rollback() --> flushStatement()
-->
BatchExecutor
doFlushStatements()
這時就執行了sql語句。
簡單來說:拋出異常,spring事務回滾,清理資源關閉sqlSession.
mybatis關閉sqlsession,關閉前先flushStatements,執行未執行的sql語句,然后再rollback.
但是這個rollback方法里判斷connection是受事務管理的,就不執行任何操作。
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、在自己的應用程序中寫個攔截器。在執行完executor的close()之后,由這個攔截器再執行一遍connection.rollback(),但從代碼的可讀性來看,會非常的差。
2、修改mybatis的bug。修改BaseExecutor的rollback()
public void rollback(boolean required) throws SQLException {
if (!closed) {
try {
clearLocalCache();
if (!required) {
flushStatements();
}
} finally {
if (required) {
transaction.rollback();
}
}
}
}
不知道大家有沒有碰到過類似的問題,又是通過什么方案解決的呢?