Spring @Transaction 事務注解詳解


1. Spring事務的基本原理

事務管理是應用系統開發中必不可少的一部分。Spring 為事務管理提供了豐富的功能支持。Spring 事務管理分為編碼式聲明式的兩種方式。編碼式事務指的是通過編碼方式實現事務;聲明式事務基於 AOP,將具體業務邏輯與事務處理解耦。聲明式事務管理使業務代碼邏輯不受污染, 因此在實際使用中聲明式事務用的比較多。聲明式事務有兩種方式,一種是在配置文件(xml)中做相關的事務規則聲明,另一種是基於@Transactional 注解的方式。基於注解得配置是目前流行的使用方式,因此本文將着重介紹基於@Transactional 注解的事務管理。
使用@Transactional的相比傳統的手動開啟事務然后提交事務。它有如下方便

  • 根據你的配置,設置是否自動開啟事務
  • 自動提交事務或者遇到異常自動回滾

聲明式事務(@Transactional)基本原理如下:

  1. 配置文件開啟注解驅動,在相關的類和方法上通過注解@Transactional標識。
  2. spring 在啟動的時候會去解析生成相關的bean,這時候會查看擁有相關注解的類和方法,並且為這些類和方法生成代理,並根據@Transaction的相關參數進行相關配置注入,這樣就在代理中為我們把相關的事務處理掉了(開啟正常提交事務,異常回滾事務)。
  3. 真正的數據庫層的事務提交和回滾是通過binlog或者redo log實現的。

2. @Transactional基本配置解析

1     @Transactional
2     public void saveUser(){
3 
4         User user = new User();
5         user.setAge(21);
6         user.setName("xiaoming");
7         logger.info("save the user{}",user);
8         userRepository.save(user);
9     }
如上面這個例子一樣,很輕松的就能應用事務。只需要在方法上加入@Transactional注解。當@Transactional加在方法上,表示對該方法應用事務。當加在類上,表示對該類里面所有的方法都應用相同配置的事務。接下來對@Transactional的參數解析。

 1 public @interface Transactional {
 2     @AliasFor("transactionManager")
 3     String value() default ""; //可選的限定描述符,指定使用的事務管理器
 4 
 5     @AliasFor("value")
 6     String transactionManager() default "";
 7 
 8     Propagation propagation() default Propagation.REQUIRED; //可選的事務傳播行為設置
 9 
10     Isolation isolation() default Isolation.DEFAULT; // 可選的事務隔離級別設置
11 
12     int timeout() default -1; // 事務超時時間設置
13 
14     boolean readOnly() default false; // 讀寫或只讀事務,默認讀寫
15 
16     Class<? extends Throwable>[] rollbackFor() default {}; //導致事務回滾的異常類數組
17 
18     String[] rollbackForClassName() default {}; //導致事務回滾的異常類名字數組
19 
20     Class<? extends Throwable>[] noRollbackFor() default {};//不會導致事務回滾的異常類數組
21 
22     String[] noRollbackForClassName() default {};//不會導致事務回滾的異常類名字數組
23 }

 

transactionManager()表示應用那個應用那個 TransactionManager.常用的有如下的事務管理器

 

 isolation()表示隔離級別

 

 

臟讀:一事務對數據進行了增刪改,但未提交,另一事務可以讀取到未提交的數據。如果第一個事務這時候回滾了,那么第二個事務就讀到了臟數據。
不可重復讀:一個事務中發生了兩次讀操作,第一次讀操作和第二次操作之間,另外一個事務對數據進行了修改,這時候兩次讀取的數據是不一致的。
幻讀:第一個事務對一定范圍的數據進行批量修改,第二個事務在這個范圍增加一條數據,這時候第一個事務就會丟失對新增數據的修改。

不可重復讀的重點是修改 :同樣的條件, 你讀取過的數據,再次讀取出來發現值不一樣了幻讀的重點在於新增或者刪除:同樣的條件, 第 1 次和第 2 次讀出來的記錄數不一樣

propagation()表示事務的傳播屬性
事務的傳播是指事務的嵌套的時候,它們的事務屬性。常見的傳播屬性有如下幾
  • PROPAGATION_REQUIRED(spring 默認)
    假設外層事務 Service A 的 Method A() 調用 內層Service B 的 Method B()。如果ServiceB.methodB() 的事務級別定義為 PROPAGATION_REQUIRED,那么執行 ServiceA.methodA() 的時候spring已經起了事務,這時調用 ServiceB.methodB(),ServiceB.methodB() 看到自己已經運行在 ServiceA.methodA() 的事務內部,就不再起新的事務。假如 ServiceB.methodB() 運行的時候發現自己沒有在事務中,他就會為自己分配一個事務。不管如何,ServiceB.methodB()都會在事務中。

  • PROPAGATION_REQUIRES_NEW
    比如我們設計 ServiceA.methodA() 的事務級別為 PROPAGATION_REQUIRED,ServiceB.methodB() 的事務級別為 PROPAGATION_REQUIRES_NEW。那么當執行到 ServiceB.methodB() 的時候,ServiceA.methodA() 所在的事務就會掛起,ServiceB.methodB() 會起一個新的事務,等待 ServiceB.methodB() 的事務完成以后,它才繼續執行。它與1中的區別在於ServiceB.methodB() 新起了一個事務。如過ServiceA.methodA() 發生異常,ServiceB.methodB() 已經提交的事務是不會回滾的。

  • PROPAGATION_SUPPORTS
    假設ServiceB.methodB() 的事務級別為 PROPAGATION_SUPPORTS,那么當執行到ServiceB.methodB()時,如果發現ServiceA.methodA()已經開啟了一個事務,則加入當前的事務,如果發現ServiceA.methodA()沒有開啟事務,則自己也不開啟事務。這種時候,內部方法的事務性完全依賴於最外層的事務。

 

 

  • readOnly() 事務超時設置.超過這個時間,發生回滾

  • readOnly() 只讀事務
    從這一點設置的時間點開始(時間點a)到這個事務結束的過程中,其他事務所提交的數據,該事務將看不見!(查詢中不會出現別人在時間點a之后提交的數據)。
    注意是一次執行多次查詢來統計某些信息,這時為了保證數據整體的一致性,要用只讀事務

  • rollbackFor()導致事務回滾的異常類數組.

  • rollbackForClassName() 導致事務回滾的異常類名字數組

  • noRollbackFor 不會導致事務回滾的異常類數組

  • noRollbackForClassName 不會導致事務回滾的異常類名字數組

3. @Transactional 使用應該注意的地方

3.1 默認情況下,如果在事務中拋出了未檢查異常(繼承自 RuntimeException 的異常)或者 Error,則 Spring 將回滾事務;除此之外,Spring 不會回滾事務。你如果想要在特定的異常回滾可以考慮rollbackFor()等屬性

3.2 @Transactional 只能應用到 public 方法才有效。

這是因為在使用 Spring AOP 代理時,Spring 會調用 TransactionInterceptor在目標方法執行前后進行攔截之前,DynamicAdvisedInterceptorCglibAopProxy的內部類)的的 intercept方法或 JdkDynamicAopProxy 的 invoke 方法會間接調用 AbstractFallbackTransactionAttributeSource(Spring 通過這個類獲取@Transactional 注解的事務屬性配置屬性信息)的 computeTransactionAttribute 方法。

1     @Nullable
2     protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
3        //這里判斷是否是public方法
4         if(this.allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
5             return null;
6         } 
7 //省略其他代碼

若不是 public,就不會獲取@Transactional 的屬性配置信息,最終會造成不會用 TransactionInterceptor 來攔截該目標方法進行事務管理。整個事務執行的時序圖如下。


 

 

3.3 Spring 的 AOP 的自調用問題

在 Spring 的 AOP 代理下,只有目標方法由外部調用,目標方法才由 Spring 生成的代理對象來管理,這會造成自調用問題。若同一類中的其他沒有@Transactional注解的方法內部調用有@Transactional注解的方法,有@Transactional注解的方法的事務被忽略,不會發生回滾。這個問題是由於Spring AOP 代理造成的(如下面代碼所示)。之所以沒有應用事務,是因為在內部調用,而代理后的類(把目標類作為成員變量靜態代理)只是調用成員變量中的對應方法,自然也就沒有aop中的advice,造成只能調用父類的方法。另外一個問題是只能應用在public方法上。為解決這兩個問題,使用 AspectJ 取代 Spring AOP 代理。

 1 @Transactional
 2  public void saveUser(){
 3         User user = new User();
 4         user.setAge(22);
 5         user.setName("mask");
 6         logger.info("save the user{}",user);
 7         userRepository.save(user);
 8        // throw new RuntimeException("exception");
 9     }
10  public void saveUserBack(){
11         saveUser();   //自調用發生
12     }

3.4 自注入解決辦法

 1 @Service
 2 public class UserService {
 3 
 4     Logger logger = LoggerFactory.getLogger(UserService.class);
 5 
 6     @Autowired
 7     UserRepository userRepository;
 8     
 9     @Autowired 
10     UserService userService; //自注入來解決
11 
12     @Transactional
13     public void saveUser(){
14 
15         User user = new User();
16         user.setAge(22);
17         user.setName("mask");
18         logger.info("save the user{}",user);
19         userRepository.save(user);
20        // throw new RuntimeException("exception");
21     }
22  public void saveUserBack(){
23         saveUser();
24     }
25 }

 

另外也可以把注解加到類上來解決。

 

4. 總結

@Transactional用起來是方便,但是我們需要明白它背后的原理,避免入坑。另外@Transactional不建議用在處理時間過長的事務。因為,它會一直持有數據庫線程池的連接,造成不能及時返回。就是盡量使事務的處理時間短。




免責聲明!

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



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