1. Spring事務的基本原理
事務管理是應用系統開發中必不可少的一部分。Spring 為事務管理提供了豐富的功能支持。Spring 事務管理分為編碼式和聲明式的兩種方式。編程式事務指的是通過編碼方式實現事務;聲明式事務基於 AOP,將具體業務邏輯與事務處理解耦。聲明式事務管理使業務代碼邏輯不受污染, 因此在實際使用中聲明式事務用的比較多。聲明式事務有兩種方式,一種是在配置文件(xml)中做相關的事務規則聲明,另一種是基於@Transactional 注解的方式。注釋配置是目前流行的使用方式,因此本文將着重介紹基於@Transactional 注解的事務管理。
使用@Transactional的相比傳統的我們需要手動開啟事務,然后提交事務來說。它提供如下方便
- 根據你的配置,設置是否自動開啟事務
- 自動提交事務或者遇到異常自動回滾
聲明式事務(@Transactional)基本原理如下:
- 配置文件開啟注解驅動,在相關的類和方法上通過注解@Transactional標識。
- spring 在啟動的時候會去解析生成相關的bean,這時候會查看擁有相關注解的類和方法,並且為這些類和方法生成代理,並根據@Transaction的相關參數進行相關配置注入,這樣就在代理中為我們把相關的事務處理掉了(開啟正常提交事務,異常回滾事務)。
- 真正的數據庫層的事務提交和回滾是通過binlog或者redo log實現的。
2. @Transactional基本配置解析
@Transactional public void saveUser(){ User user = new User(); user.setAge(22); user.setName("maskwang"); logger.info("save the user{}",user); userRepository.save(user); }
如上面這個例子一樣,很輕松的就能應用事務。只需要在方法上加入@Transactional注解。當@Transactional加在方法上,表示對該方法應用事務。當加在類上,表示對該類里面所有的方法都應用相同配置的事務。接下來對@Transactional的參數解析。
public @interface Transactional { @AliasFor("transactionManager") String value() default ""; @AliasFor("value") String transactionManager() default ""; Propagation propagation() default Propagation.REQUIRED; Isolation isolation() default Isolation.DEFAULT; int timeout() default -1; boolean readOnly() default false; Class<? extends Throwable>[] rollbackFor() default {}; String[] rollbackForClassName() default {}; Class<? extends Throwable>[] noRollbackFor() default {}; String[] noRollbackForClassName() default {}; }
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()沒有開啟事務,則自己也不開啟事務。這種時候,內部方法的事務性完全依賴於最外層的事務。
剩下幾種就不多介紹,可以參考這篇文章。https://www.jianshu.com/p/249f2cd42692
-
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
在目標方法執行前后進行攔截之前,DynamicAdvisedInterceptor
(CglibAopProxy
的內部類)的的 intercept
方法或 JdkDynamicAopProxy 的 invoke 方法會間接調用 AbstractFallbackTransactionAttributeSource
(Spring 通過這個類獲取@Transactional
注解的事務屬性配置屬性信息)的 computeTransactionAttribute
方法。
@Nullable protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) { //這里判斷是否是public方法 if(this.allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) { return null; } //省略其他代碼
若不是 public,就不會獲取@Transactional 的屬性配置信息,最終會造成不會用 TransactionInterceptor 來攔截該目標方法進行事務管理。整個事務執行的時序圖如下。

3.3 Spring 的 AOP 的自調用問題
在 Spring 的 AOP 代理下,只有目標方法由外部調用,目標方法才由 Spring 生成的代理對象來管理,這會造成自調用問題。若同一類中的其他沒有@Transactional
注解的方法內部調用有@Transactional
注解的方法,有@Transactional
注解的方法的事務被忽略,不會發生回滾。這個問題是由於Spring AOP 代理造成的(如下面代碼所示)。之所以沒有應用事務,是因為在內部調用,而代理后的類(把目標類作為成員變量靜態代理)只是調用成員變量中的對應方法,自然也就沒有aop中的advice,造成只能調用父類的方法。另外一個問題是只能應用在public方法上。為解決這兩個問題,使用 AspectJ 取代 Spring AOP 代理。
@Transactional public void saveUser(){ User user = new User(); user.setAge(22); user.setName("mask"); logger.info("save the user{}",user); userRepository.save(user); // throw new RuntimeException("exception"); } public void saveUserBack(){ saveUser(); //自調用發生 }
3.4 自注入解決辦法
@Service public class UserService { Logger logger = LoggerFactory.getLogger(UserService.class); @Autowired UserRepository userRepository; @Autowired UserService userService; //自注入來解決 @Transactional public void saveUser(){ User user = new User(); user.setAge(22); user.setName("mask"); logger.info("save the user{}",user); userRepository.save(user); // throw new RuntimeException("exception"); } public void saveUserBack(){ saveUser(); } }
另外也可以把注解加到類上來解決。
3.4注意事項
@Transactional
注解不支持多數據源的情況- 如果存在多個數據源且未指定具體的事務管理器,那么實際上啟用的事務管理器是最先在配置文件中指定的(即先加載的)
- Spring使用聲明式事務處理,默認情況下,如果被注解的數據庫操作方法中發生了unchecked異常,所有的數據庫操作將rollback;如果發生的異常是checked異常,默認情況下數據庫操作還是會提交的。
4.spring事務回滾規則
指示spring事務管理器回滾一個事務的推薦方法是在當前事務的上下文內拋出異常。spring事務管理器會捕捉任何未處理的異常,然后依據規則決定是否回滾拋出異常的事務。
默認配置下,spring只有在拋出的異常為運行時unchecked異常時才回滾該事務,也就是拋出的異常為RuntimeException的子類(Errors也會導致事務回滾),而拋出checked異常則不會導致事務回滾。
可以明確的配置在拋出那些異常時回滾事務,包括checked異常。也可以明確定義那些異常拋出時不回滾事務。
還可以編程性的通過setRollbackOnly()方法來指示一個事務必須回滾,在調用完setRollbackOnly()后你所能執行的唯一操作就是回滾。
4. 總結
@Transactional
用起來是方便,但是我們需要明白它背后的原理,避免入坑。另外@Transactional
不建議用在處理時間過長的事務。因為,它會一直持有數據庫線程池的連接,造成不能及時返回。就是盡量是的事務的處理時間短。
參考文章:
Spring @Transactional的使用及原理
深入理解 Spring 事務原理
透徹的掌握 Spring 中@transactional 的使用
在同一個類中,一個方法調用另外一個有注解(比如@Async,@Transational)的方法,注解失效的原因和解決方法
https://www.cnblogs.com/yepei/p/4716112.html
作者:maskwang520
鏈接:https://www.jianshu.com/p/5687e2a38fbc
來源:簡書