當 Propagation.NESTED 遇到 TransactionTimedOutException 時,會發現不僅內層嵌套事務回滾,外層事務也被一起回滾了。這是爲什麼?
首先明確幾個概念:
- TransactionTimedOutException 什麼時候會拋出來?答:事務中執行 SQL 前,會檢查當前事務是否超時,超時則會拋出該異常;
- Propagation.NESTED 傳播級別的使用場景?答:外層事務已經寫了一部分數據,但是有一部分數據並不關心是否執行成功,則可以給這部分邏輯的事務傳播級別設置爲 NESTED;
當拋出 TransactionTimedOutException 時,當前事務持有的 DataSource 會被標記爲 rollbackOnly(藉助 ResourceHolderSupport 實現),當整個大的事務提交時,會檢查 rollbackOnly 標記,若爲 true,即使內部並沒有對外拋出異常,事務也會被回滾。
那如何避免 rollbackOnly 回滾整個事務呢?重寫 DataSourceTransactionManager 的 shouldCommitOnGlobalRollbackOnly 方法即可:
@Bean
@Primary
public DataSourceTransactionManager transactionManager(@Autowired DataSource dataSource) {
return new DataSourceTransactionManager(dataSource) {
@Override
protected boolean shouldCommitOnGlobalRollbackOnly() {
return true;
}
};
}
但是這樣做是有風險的,如下面這段代碼:
class serviceA {
@Autowired ServiceB serviceB;
@Transaction
void methodA() {
serviceB.methodB();
}
}
class serviceB {
@Transaction
void methodB() {
someDao.insert("inserted data");
throw new RuntimeException("maked runtime exception");
}
}
methodA() 執行後,正常情況 “inserted data” 是不應該被插入數據庫的,但是如果 shouldCommitOnGlobalRollbackOnly 被重寫爲返回 true,"insert data"將被插入。其實 rollbackOnly 屬性就是爲了確保這種傳播級別爲 Propagation.REQUIRED 場景服務的。