一、問題描述
場景描述:有這樣一個(gè)service方法,調(diào)用了兩個(gè)dao中的方法。第一個(gè)方法按照傳入的id批量更新用戶名。第二個(gè)dao方法無數(shù)據(jù)庫操作,僅僅拋出一個(gè)RuntimeException.
這個(gè)service方法通過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í),批量更新用戶名的操作會回滾。
當(dāng)userDao及testDao中注入的是ExcutorType.Batch類型的template時(shí),批量更新用戶名的操作未回滾。
經(jīng)過檢查數(shù)據(jù)庫日志,發(fā)現(xiàn)第二種情況的數(shù)據(jù)庫執(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語句。
簡單來說:拋出異常,spring事務(wù)回滾,清理資源關(guān)閉sqlSession.
mybatis關(guān)閉sqlsession,關(guān)閉前先flushStatements,執(zhí)行未執(zhí)行的sql語句,然后再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(),但從代碼的可讀性來看,會非常的差。
2、修改mybatis的bug。修改BaseExecutor的rollback()
public void rollback(boolean required) throws SQLException {
if (!closed) {
try {
clearLocalCache();
if (!required) {
flushStatements();
}
} finally {
if (required) {
transaction.rollback();
}
}
}
}
不知道大家有沒有碰到過類似的問題,又是通過什么方案解決的呢?