本文通過分析Spring 事務(wù)的源碼,說明@Transactional Timeout 參數(shù)設(shè)置的一些問題。
從問題開始誓琼,下面兩段代碼油湖,事務(wù)是否都能正常的回滾巍扛?
timeout 參數(shù)大于3
代碼片段1
@Transactional(rollbackFor = Exception.class , propagation = Propagation.REQUIRED,timeout = 3)
public int timeout(int timeout,int blogId){
jdbcTemplate.execute("DELETE FROM blog where id = "+blogId);
Sleep.second(timeout);
jdbcTemplate.execute("SELECT 1");
return timeout;
}
代碼片段2
@Transactional(rollbackFor = Exception.class , propagation = Propagation.REQUIRED,timeout = 3)
public int timeout(int timeout , int blogId){
jdbcTemplate.execute("DELETE FROM blog where id = "+blogId);
Sleep.second(timeout);
return timeout;
}
按照我們的認(rèn)知,上面的代碼都應(yīng)該因?yàn)槌瑫r(shí)拋出異常從而觸發(fā) rollback ,但是實(shí)際運(yùn)行結(jié)果是 代碼片段2 不會回滾乏德,如果在生產(chǎn)環(huán)境意味著數(shù)據(jù)會真實(shí)的被刪除撤奸。
帶著問題來看源碼
- 首先我們從Timeout的異常堆棧追蹤到異常拋出的代碼
- 定位到ResourceHolderSupport#checkTransactionTimeout 方法,查看源碼
通過源碼可以看到喊括,源碼通過一個(gè) dealline (Date) 與當(dāng)前時(shí)間比較來判斷是否超時(shí)胧瓜,那么這會就產(chǎn)生了兩個(gè)疑問:
- dealline 在哪里設(shè)置的?
- 什么時(shí)候調(diào)用checkTransactionTimeout?
- 跟蹤 dealline 屬性,找到設(shè)置的方法 ResourceHolderSupport#setTimeoutInMillis,通過Debug面板郑什,追蹤調(diào)用堆棧府喳,可以定位到DataSourceTransactionManager#doBegin方法
-
查看 DataSourceTransactionManager#doBegin 方法,定位到設(shè)置timeout的代碼端蘑拯,到這里我們第一個(gè)問題得到了答案
-
通過ResourceHolderSupport#checkTransactionTimeout堆棧找到調(diào)用方法
- DataSourceUtils#applyTimeout
- JdbcTemplate#applyStatementSettings
- JdbcTemplate#execute
從這里就能清楚的知道钝满, checkTransactionTimeout 是在每次執(zhí)行SQL的時(shí)候被調(diào)用的,這也就能說明為什么代碼片段1可以正城恳龋回滾了舱沧,因?yàn)榇a片段1在最后執(zhí)行了一條無意義的SQL “SELECT 1” 觸發(fā)了 checkTransactionTimeout 的檢查。