Spring @Transactional 事務機制
原文連結
https://www.cnblogs.com/leeego-123/p/11498327.html
摘選一下精華
在service類前加上@Transactional,宣告這個service所有方法需要事務管理。每一個業務方法開始時都會開啟一個事務。error是一定會回滾的
Spring預設情況下會對執行期例外(RunTimeException)進行事務回滾。這個例外是unchecked
如果遇到checked意外就不回滾。
如何改變預設規則:
1 讓checked例外也回滾:在整個方法前加上 @Transactional(rollbackFor=Exception.class)
2 讓unchecked例外不回滾: @Transactional(notRollbackFor=RunTimeException.class)
3 不需要事務管理的(只查詢的)方法:@Transactional(propagation=Propagation.NOT_SUPPORTED)
4 如果不新增rollbackFor等屬性,Spring碰到Unchecked Exceptions都會回滾,不僅是RuntimeException,也包括Error。
注意:
如果異常被try{}catch{}了,事務就不回滾了,如果想讓事務回滾必須再往外拋try{}catch{throw Exception}。
1.檢查型異常(Checked Exception)
個人理解:所謂檢查(Checked)是指編譯器要檢查這類異常,檢查的目的一方面是因為該類異常的發生難以避免,另一方面就是讓開發者去解決掉這類異常,所以稱為必須處理(try ...catch)的異常。如果不處理這類異常,整合開發環境中的編譯器一般會給出錯誤提示。
例如:一個讀取檔案的方法程式碼邏輯沒有錯誤,但程式執行時可能會因為檔案找不到而丟擲FileNotFoundException,如果不處理這些異常,程式將來肯定會出錯。所以編譯器會提示你要去捕獲並處理這種可能發生的異常,不處理就不能通過編譯。
2.非檢查型異常(Unchecked Exception)
個人理解:所謂非檢查(Unchecked)是指編譯器不會檢查這類異常,不檢查的則開發者在程式碼的編輯編譯階段就不是必須處理,這類異常一般可以避免,因此無需處理(try ...catch)。如果不處理這類異常,整合開發環境中的編譯器也不會給出錯誤提示。
例如:你的程式邏輯本身有問題,比如陣列越界、訪問null物件,這種錯誤你自己是可以避免的。編譯器不會強制你檢查這種異常。
坑點: 在業務層捕捉異常後,發現事務不生效。
這是許多新手都會犯的一個錯誤,在業務層手工捕捉並處理了異常,你都把異常“吃”掉了,Spring自然不知道這裡有錯,更不會主動去回滾資料。例如:下面這段程式碼直接導致增加餘額的事務回滾沒有生效。
@Transactional(rollbackFor = Exception.class) public void addMoney() throws Exception { //先增加餘額 accountMapper.addMoney(); //謹慎:儘量不要在業務層捕捉異常並處理 try { throw new SQLException("發生異常了.."); } catch (Exception e) { e.printStackTrace(); } }
如果捕捉的異常是一個外部類發出的異常,就算捕捉了也會全部回滾,spring boot 預設的事物即發生異常無論是否捕獲均會回滾。
若非實際業務要求,則在業務層統一丟擲異常,然後在控制層統一處理。
上面連結寫的很好啊,推薦下,然後我這邊自己總結下一般如何標註
我下面的情況都是預設以這個方式寫的進行下面的總結,rollbackFor = Exception.class我想把所有的異常都回滾。如果不是propagation = Propagation.REQUIRED或者其他REQUIRES-NEW等的話,不在該總結範圍內
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
一 呼叫本類的方法
開啟事務和事務回滾,實際這個過程是aop代理幫忙完成的,當呼叫一個方法時,它會先檢查時候有事務,有則開啟事務,
當呼叫本類的方法是,它並沒有將其視為proxy呼叫,而是方法的直接呼叫,所以也就沒有檢查該方法是否含有事務這個過程,
那麼本地方法呼叫的事務也就無效了。
因此總結:都有事務的方法,在不同類中是有起到作用的。
但是在同一個類中事務的情況就不一樣了
1 a與b都有事務,若A.a(呼叫方)呼叫A.b(被呼叫方),則b的事務是不會開啟的,被忽略掉。
2.a無事務,b有事務,如果A.a呼叫A.b 此時b事務被忽略,代表事務都沒有開。 如果此時A .a捕獲了b的話,b所在異常語句位置沒有回滾外,其他b位置還是commit了,包括呼叫方a也是commit,就同平常無加事務的情況
3 a有事務,B無,b異常,則都回滾
二 呼叫外部類的方法(A類 B類)
1. A有事務,B也有事務
A呼叫B方法,B異常,A不捕獲,那麼AB都會回滾
A呼叫B方法,B異常,A捕獲了呼叫B的異常,那麼AB也會回滾
丟擲如下異常Transaction rolled back because it has been marked as rollback-only;資料庫中資料也沒有被修改。總結一下,spring boot 預設的事物即發生異常無論是否捕獲均會回滾。
2. A有事務,B無事務,C有事務(A類呼叫外部B類,B呼叫外部C類)
C發生異常,B進行捕獲了。但最終A B C 全部回滾了
丟擲如下異常Transaction rolled back because it has been marked as rollback-only;資料庫中資料也沒有被修改。總結一下,spring boot 預設的事物即發生異常無論是否捕獲均會回滾。
3. A.a有事務,B.a無事務,B.b有事務(A類呼叫B.a,B.a呼叫B.b)
B.b發生異常,在B.a中進行捕獲,最終都commit了,A.a無感知,正常了走完了整個流程。只有B.b異常位置沒有往下執行,在B.b方法內的前面的語句還是commit了
B.a無事務,呼叫同類的有異常的事務,會被自動忽略,因此直接認為B.a跟B.b無事務,在含有事務的A那裡是沒有感知到有異常發生,因此commit了
綜合重點:
以上都是以 @Transactional(rollbackFor = Exception.class)進行測試的。
1 同個類的被呼叫方事務預設被忽略
2 外部間的呼叫,呼叫方有事務,看被呼叫方是否是有意義的事務(同類被忽略,可看上面規則,a1有事務,a2有事務,則預設a2已有事務,不管a2有沒有寫事務,如果a1無事務,a2有,預設a1 a2都不具備事務意義),
1)任何一個當發生異常未捕獲時,全回滾。
2)當發生異常且捕獲時,類之間的呼叫方 若是沒被忽略,其具備事務意義,則全部回滾,若所在被呼叫類不具備事務意義,則不回滾
3)只要主呼叫方感知到了有異常,且沒有捕捉,一律回滾