Transactional 注解與 AOP


轉載 為防止消失 如有侵權請聯系 原鏈接 https://www.dazhuanlan.com/2019/11/30/5de14b06398b8/

當我們在一個 private 方法上打上 @Transactional 注解,IDEA 會提示 Methods annotated with '@Transactional' must be overridable。比如下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class UserService {

public void method1(){
method2();
}

@Transactional
private void method2(){

}
}

有時候我們想讓事務的范圍盡可能的小,可能就會寫出這樣的代碼,在 method1 先做一些非事務的事情,然后在 method2 中做事務相關的事情。此時,如果我們用 IDEA 的話,應該就會出現上面的提示信息,eclipse 我沒用不清楚會不會有這樣的其實。為什么被 @Transactional 注解的方法必須是可重寫的呢?

如果我們忽略掉這個信息,你會發現編譯運行是沒有問題的。在外部調用 UserService.method1() 時,如果 method2 沒有發生異常的話,你會發現一切都是正常的。為什么 IDEA 要這樣提示呢,真是怪了!

但是,當 method2 發生異常時,你會驚奇的發現 method2 的事務並沒有回滾!

AOP

要理解事務為什么沒有回滾,我們就要回顧一下 AOP 的知識。在 Spring 中默認是通過 JDK 動態代理的方式來實現 AOP 的,對於打了 @Transactional 注解的類,Spring 動態代理會生成一個代理 bean 和一個真實的目標 bean。

我們回頭看看 method1 並沒有打上注解,所以 method1 並不會被事務切面環繞。而 method2 是通過 method1 調用的,隱藏的調用對象是真實的目標 bean,真實的目標 bean 是沒有切面邏輯的,切面邏輯都在代理 bean 上。這就是為什么事務沒有回滾的原因,如下圖所示:

transactional-aop

按照這樣的理解,只要我們在 method1 方法上打上 @Transactional 注解,事務就能生效了,method2 的注解是多余的。此時,我們應該就能理解 IDEA 的善意提示了。因為 UserService 沒有接口,所以只能通過 CGLIB 的方式來實現動態代理,而 CGLIB 是通過繼承的方式來進行代理,需要對目標 bean 的方法進行重寫,但是 private 修飾的方法是不能重寫的,所以就會出現這樣的提示。

但是,在 method1 上打注解,method2 上不打,那不是違背了我們當初讓事務范圍最小化的出發點了嗎?一切又回到了原地。還有沒有其他解決方法?

  1. 把 method1 方法搬到 controller 層,嘿嘿,這個方法看起來很雞賊,但是好像是更加合理的?
  2. 如果代理類和目標類合為一個,有注解的方法有切面環繞,沒有注解的方法沒有切面環繞,這樣是不是就行了?AspectJ 靜態代理就是這樣的一門技術,在編譯成 class 字節碼的時候在方法周圍織入切面邏輯。

總結

不僅僅是 @Transactional 會這樣,其他的注解也是同理的,所以我們要深入理解 AOP 的原理,面對這些問題才能游刃有余。


免責聲明!

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



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