1. Spring事務的基本原理
事務管理是應用系統開發中必不可少的一部分。Spring 為事務管理提供了豐富的功能支持。Spring 事務管理分為編碼式和聲明式的兩種方式。編碼式事務指的是通過編碼方式實現事務;聲明式事務基於 AOP,將具體業務邏輯與事務處理解耦。聲明式事務管理使業務代碼邏輯不受污染, 因此在實際使用中聲明式事務用的比較多。聲明式事務有兩種方式,一種是在配置文件(xml)中做相關的事務規則聲明,另一種是基於@Transactional 注解的方式。基於注解得配置是目前流行的使用方式,因此本文將着重介紹基於@Transactional 注解的事務管理。
使用@Transactional的相比傳統的手動開啟事務然后提交事務。它有如下方便
- 根據你的配置,設置是否自動開啟事務
- 自動提交事務或者遇到異常自動回滾
聲明式事務(@Transactional)基本原理如下:
- 配置文件開啟注解驅動,在相關的類和方法上通過注解@Transactional標識。
- spring 在啟動的時候會去解析生成相關的bean,這時候會查看擁有相關注解的類和方法,並且為這些類和方法生成代理,並根據@Transaction的相關參數進行相關配置注入,這樣就在代理中為我們把相關的事務處理掉了(開啟正常提交事務,異常回滾事務)。
- 真正的數據庫層的事務提交和回滾是通過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 }
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
在目標方法執行前后進行攔截之前,DynamicAdvisedInterceptor
(CglibAopProxy
的內部類)的的 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
不建議用在處理時間過長的事務。因為,它會一直持有數據庫線程池的連接,造成不能及時返回。就是盡量使事務的處理時間短。
參考文章:
Spring @Transactional的使用及原理
深入理解 Spring 事務原理
透徹的掌握 Spring 中@transactional 的使用
在同一個類中,一個方法調用另外一個有注解(比如@Async,@Transational)的方法,注解失效的原因和解決方法