<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
在我們工作中,經常會用到 @Transactional 宣告事務,不正確的使用姿勢會導致註解失效,下面就來分析四種最常見的@Transactional事務不生效的 Case:
類內部存取:A 類的 a1 方法沒有標註 @Transactional,a2 方法標註 @Transactional,在 a1 裡面呼叫 a2;
私有方法:將 @Transactional 註解標註在非 public 方法上;
異常不匹配:@Transactional 未設定 rollbackFor 屬性,方法返回 Exception 等異常;
多執行緒:主執行緒和子執行緒的呼叫,執行緒丟擲異常。
UserDao 介面,運算元據庫;UserController 實現業務邏輯,宣告事務,呼叫 UserController.testSuccess 方法,事務宣告生效
// 提供的介面 public interface UserDao { // select * from user_test where uid = "#{uid}" public MyUser selectUserById(Integer uid); // update user_test set uname =#{uname},usex = #{usex} where uid = #{uid} public int updateUser(MyUser user); }
@Service public class UserController { @Autowired private UserDao userDao; public void update(Integer id) { MyUser user = new MyUser(); user.setUid(id); user.setUname("張三-testing"); user.setUsex("女"); userDao.updateUser(user); } public MyUser query(Integer id) { MyUser user = userDao.selectUserById(id); return user; } // 正常情況 @Transactional(rollbackFor = Exception.class) public void testSuccess() throws Exception { Integer id = 1; MyUser user = query(id); System.out.println("原記錄:" + user); update(id); throw new Exception("事務生效"); } }
為了快速說明問題,直接在controller中實現了業務邏輯和事務宣告,不代表生產環境中的程式碼分層
在類 UserController 中新增一個方法 testInteralCall():
public void testInteralCall() throws Exception { testSuccess(); throw new Exception("事務不生效:類內部存取"); }
這裡 testInteralCall() 沒有標註 @Transactional,我們再看一下測試用例:
public static void main(String[] args) throws Exception { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); UserController uc = (UserController) applicationContext.getBean("userController"); try { uc.testSuccess(); } finally { MyUser user = uc.query(1); System.out.println("修改後的記錄:" + user); } } // 輸出: // 原記錄:MyUser(uid=1, uname=張三, usex=女) // 修改後的記錄:MyUser(uid=1, uname=張三-testing, usex=女)
從上面的輸出可以看到,事務並沒有回滾,這個是什麼原因呢?
因為 @Transactional 的工作機制是基於 AOP 實現,AOP 是使用動態代理實現的,如果通過代理直接呼叫 testSuccess(),通過 AOP 會前後進行增強,增強的邏輯其實就是在 testSuccess() 的前後分別加上開啟、提交事務的邏輯。
現在是通過 testInteralCall() 去呼叫 testSuccess(),testSuccess() 前後不會進行任何增強操作,也就是類內部呼叫,不會通過代理方式存取。
在私有方法上,新增 @Transactional 註解也不會生效:
@Transactional(rollbackFor = Exception.class) private void testPirvateMethod() throws Exception { Integer id = 1; MyUser user = query(id); System.out.println("原記錄:" + user); update(id); throw new Exception("測試事務生效"); }
直接使用時,下面這種場景不太容易出現,因為 IDEA 會有提醒,文案為: Methods annotated with '@Transactional' must be overridable,至於深層次的原理,原始碼部分會給你解讀。
這裡的 @Transactional 沒有設定 rollbackFor = Exception.class 屬性:
@Transactional public void testExceptionNotMatch() throws Exception { Integer id = 1; MyUser user = query(id); System.out.println("原記錄:" + user); update(id); throw new Exception("事務不生效:異常不匹配"); }
@Transactional 註解預設處理執行時異常,即只有丟擲執行時異常時,才會觸發事務回滾,否則並不會回滾,至於深層次的原理,原始碼部分會給你解讀。
父執行緒丟擲異常,子執行緒不丟擲異常:
public void testSuccess() throws Exception { Integer id = 1; MyUser user = query(id); System.out.println("原記錄:" + user); update(id); } @Transactional(rollbackFor = Exception.class) public void testMultThread() throws Exception { new Thread(new Runnable() { @SneakyThrows @Override public void run() { testSuccess(); } }).start(); throw new Exception("測試事務不生效"); }
父執行緒丟擲執行緒,事務回滾,因為子執行緒是獨立存在,和父執行緒不在同一個事務中,所以子執行緒的修改並不會被回滾
父執行緒不丟擲異常,子執行緒丟擲異常:
public void testSuccess() throws Exception { Integer id = 1; MyUser user = query(id); System.out.println("原記錄:" + user); update(id); throw new Exception("測試事務不生效"); } @Transactional(rollbackFor = Exception.class) public void testMultThread() throws Exception { new Thread(new Runnable() { @SneakyThrows @Override public void run() { testSuccess(); } }).start(); }
由於子執行緒的異常不會被外部的執行緒捕獲,所以父執行緒不拋異常,事務回滾沒有生效。
我們只看最核心的邏輯,程式碼中的 interceptorOrInterceptionAdvice 就是 TransactionInterceptor 的範例,入參是 this 物件。
紅色方框有一段註釋,大致翻譯為 “它是一個攔截器,所以我們只需呼叫即可:在構造此物件之前,將靜態地計算切入點。”
this 是 ReflectiveMethodInvocation 物件,成員物件包含 UserController 類、testSuccess() 方法、入參和代理物件等。
進入 invoke() 方法後:
前方高能!!!這裡就是事務的核心邏輯,包括判斷事務是否開啟、目標方法執行、事務回滾、事務提交。
在上面這幅圖中,第一個紅框區域呼叫了方法 getTransactionAttribute(),主要是為了獲取 txAttr 變數,它是用於讀取 @Transactional 的設定,如果這個 txAttr = null,後面就不會走事務邏輯,我們看一下這個變數的含義:
我們直接進入 getTransactionAttribute(),重點關注獲取事務設定的方法。
前方高能!!!這裡就是 private 導致事務不生效的原因所在,allowPublicMethodsOnly() 一直返回 false,所以重點只關注 isPublic() 方法。
我們繼續回到事務的核心邏輯,因為主方法丟擲 Exception() 異常,進入事務回滾的邏輯:
進入 rollbackOn() 方法,判斷該異常是否能進行回滾,這個需要判斷主方法丟擲的 Exception() 異常,是否在 @Transactional 的設定中:
我們進入 getDepth() 看一下異常規則匹配邏輯,因為我們對 @Transactional 設定了 rollbackFor = Exception.class,所以能匹配成功:
範例中的 winner 不為 null,所以會跳過下面的環節。但是當 winner = null 時,也就是沒有設定 rollbackFor 屬性時,會走預設的異常捕獲方式。
前方高能!!!這裡就是異常不匹配原因的原因所在,我們看一下預設的異常捕獲方式:
是不是豁然開朗,當沒有設定 rollbackFor 屬性時,預設只對 RuntimeException 和 Error 的異常執行回滾。
以上就是Spring事務@Transactional註解的四種不生效案例場景分析的詳細內容,更多關於Spring事務@Transactional不生效的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45