SpringBoot使用@Transactional注解配置事務


1、詳細介紹
事務管理是應用系統開發中必不可少的一部分。Spring 為事務管理提供了豐富的功能支持。Spring 事務管理分為編程式和聲明式的兩種方式。
編程式事務指的是通過編碼方式實現事務;聲明式事務基於 AOP,將具體業務邏輯與事務處理解耦。
聲明式事務管理使業務代碼邏輯不受污染, 因此在實際使用中聲明式事務用的比較多。

聲明式事務有兩種方式,一種是在配置文件(xml)中做相關的事務規則聲明,另一種是基於@Transactional注解的方式。

@Transactional可以作用於接口、接口方法、類以及類方法上。當作用於類上時,該類的所有public方法將都具有該類型的事務屬性,同時,我們也可以在方法級別使用該標注來覆蓋類級別的定義。

雖然@Transactional注解可以作用於接口、接口方法、類以及類方法上,但是Spring建議不要在接口或者接口方法上使用該注解,因為這只有在使用基於接口的代理時它才會生效。

另外,@Transactional注解應該只被應用到public方法上,這是由Spring AOP的本質決定的。如果你在protected、private或者默認可見性的方法上使用@Transactional注解,這將被忽略,也不會拋出任何異常。

默認情況下,只有來自外部的方法調用才會被AOP代理捕獲,也就是,類內部方法調用本類內部的其他方法並不會引起事務行為,即使被調用方法使用@Transactional注解進行修飾。

2、使用方法
注:SpringBoot項目會自動配置一個DataSourceTransactionManager,所以我們只需在方法(或者類)加上@Transactional注解,就自動納入Spring的事務管理了。如下在方法加上 @Transactional 注解:

拋出異常之后,事務會自動回滾,數據不會插入到數據庫。

3、Spring事務的默認回滾機制
Spring的事務管理默認是針對unchecked exception回滾,也就是默認對Error異常和RuntimeException異常以及其子類進行事務回滾,且必須拋出異常。若使用try-catch對其異常捕獲則不會進行回滾!(Error異常和RuntimeException異常拋出時不需要方法調用throws或try-catch語句)。而checked exception** 則必須用try語句塊進行處理或者把異常交給上級方法處理總之就是必須寫代碼處理它,所以必須在service捕獲異常,然后再次拋出,這樣事務方才起效。

4、Java 異常介紹
Java標准庫內建了一些通用的異常,這些類以Throwable為頂層父類。Throwable又派生出Error類和Exception類。錯誤:Error類以及他的子類的實例,代表了JVM本身的錯誤。錯誤不能被程序員通過代碼處理,Error很少出現。因此,程序員應該關注Exception為父類的分支下的各種異常類。異常:Exception以及他的子類,代表程序運行時發送的各種不期望發生的事件。可以被Java異常處理機制使用,是異常處理的核心。

注意:上圖中有個錯誤 - ClassNotFoundException不屬於運行時異常!總體上我們根據Javac對異常的處理要求,將異常類分為2類。非檢查異常(unckecked exception):Error和RuntimeException以及他們的子類。javac在編譯時,不會提示和發現這樣的異常,不要求在程序處理這些異常。所以如果願意,我們可以編寫代碼處理(使用try…catch…finally)這樣的異常,也可以不處理。對於這些異常,我們應該修正代碼,而不是去通過異常處理器處理。這樣的異常發生的原因多半是代碼寫的有問題。

除0錯誤ArithmeticException,錯誤的強制類型轉換錯誤ClassCastException,數組索引越界ArrayIndexOutOfBoundsException,使用了空對象NullPointerException等等。檢查異常(checked exception):除了Error 和 RuntimeException的其它異常。javac強制要求程序員為這樣的異常做預備處理工作(使用try…catch…finally或者throws)。在方法中要么用try-catch語句捕獲它並處理,要么用throws子句聲明拋出它,否則編譯不會通過。這樣的異常一般是由程序的運行環境導致的。因為程序可能被運行在各種未知的環境下,而程序員無法干預用戶如何使用他編寫的程序,於是程序員就應該為這樣的異常時刻准備着。
如SQLException , IOException,ClassNotFoundException 等。需要明確的是:檢查和非檢查是對於javac來說的,這樣就很好理解和區分了。

5、@Transactional注解屬性介紹
value和transactionManager屬性,它們兩個是一樣的意思。當配置了多個事務管理器時,可以使用該屬性指定選擇哪個事務管理器。
propagation屬性,事務的傳播行為,默認值為Propagation.REQUIRED。

可選的值有:
Propagation.REQUIRED 如果當前存在事務,則加入該事務,如果當前不存在事務,則創建一個新的事務。如a方法和b方法都添加了注解,使用默認傳播模式,則a方法內部調用b方法,會把兩個方法的事務合並為一個事務。這里又會存在問題,如果b方法內部拋了異常,而a方法catch了b方法的異常,那這個事務還能正常運行嗎?答案是不行!會拋出異常:
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only,因為當ServiceB中拋出了一個異常以后,ServiceB會把當前的transaction標記為需要rollback。但是ServiceA中捕獲了這個異常,並進行了處理,認為當前transaction應該正常commit。此時就出現了前后不一致,也就是因為這樣,拋出了前面的UnexpectedRollbackException。Propagation.SUPPORTS
如果當前存在事務,則加入該事務;如果當前不存在事務,則以非事務的方式繼續運行。

Propagation.MANDATORY 如果當前存在事務,則加入該事務;如果當前不存在事務,則拋出異常。

Propagation.REQUIRES_NEW 重新創建一個新的事務,如果當前存在事務,暫停當前的事務。這個屬性可以實現:
類A中的a方法加上默認注解@Transactional(propagation = Propagation.REQUIRED),類B中的b方法加上注解@Transactional(propagation = Propagation.REQUIRES_NEW),然后在a方法中調用b方法操作數據庫,再在a方法最后拋出異常,會發現a方法中的b方法對數據庫的操作沒有回滾,因為Propagation.REQUIRES_NEW會暫停a方法的事務。

Propagation.NOT_SUPPORTED 以非事務的方式運行,如果當前存在事務,暫停當前的事務。

Propagation.NEVER 以非事務的方式運行,如果當前存在事務,則拋出異常。

Propagation.NESTED 和 Propagation.REQUIRED 效果一樣。

isolation屬性 事務的隔離級別,默認值為 Isolation.DEFAULT。

可選的值有: Isolation.DEFAULT 使用底層數據庫默認的隔離級別。 Isolation.READ_UNCOMMITTED ,Isolation.READ_COMMITTED ,Isolation.REPEATABLE_READ,Isolation.SERIALIZABLE

timeout屬性:事務的超時時間,默認值為-1。如果超過該時間限制但事務還沒有完成,則自動回滾事務。

readOnly屬性:指定事務是否為只讀事務,默認值為 false;為了忽略那些不需要事務的方法,比如讀取數據,可以設置 read-only 為 true。

rollbackFor屬性:用於指定能夠觸發事務回滾的異常類型,可以指定多個異常類型。

noRollbackFor屬性:拋出指定的異常類型,不回滾事務,也可以指定多個異常類型。

6、@Transactional事務幾點注意,這里面有幾點需要大家留意:
A. 一個功能是否要事務,必須納入設計、編碼考慮。不能僅僅完成了基本功能就ok。
B. 如果加了事務,必須做好開發環境測試(測試環境也盡量觸發異常、測試回滾),確保事務生效。
C. 以下列了事務使用過程的注意事項,請大家留意。
1.不要在接口上聲明@Transactional,而要在具體類的方法上使用@Transactional注解,否則注解可能無效。
2.不要圖省事,將@Transactional放置在類級的聲明中,放在類聲明,會使得所有方法都有事務。故@Transactional應該放在方法級別,不需要使用事務的方法,就不要放置事務,比如查詢方法。否則對性能是有影響的。
3.使用了@Transactional的方法,對同一個類里面的方法調用,@Transactional無效。比如有一個類Test,它的一個方法A,A再調用Test本類的方法B(不管B是否public還是private),但A沒有聲明注解事務,而B有。則外部調用A之后,B的事務是不會起作用的。(經常在這里出錯)
4.使用了@Transactional的方法,只能是public,@Transactional注解的方法都是被外部其他類調用才有效,故只能是public。道理和上面的有關聯。故在protected、private或者package-visible的方法上使用 @Transactional 注解,它也不會報錯,但事務無效。
5.spring的事務在拋異常的時候會回滾,如果是catch捕獲了,事務無效。可以在catch里面加throw new RuntimeException();
6.最后有個關鍵的一點:和鎖同時使用需要注意:由於Spring事務是通過AOP實現的,所以在方法執行之前會有開啟事務,之后會有提交事務邏輯。而synchronized代碼塊執行是在事務之內執行的,可以推斷在synchronized代碼塊執行完時,事務還未提交,其他線程進入synchronized代碼塊后,讀取的數據不是最新的。
所以必須使synchronized鎖的范圍大於事務控制的范圍,把synchronized加到Controller層或者大於事務邊界的調用層!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM