淺談 Spring 事務底層原理
一、@EnableTransactionManagement 工作原理
開啓 Spring 事務本質上就是增加了一個 Advisor,但我們使用@EnableTransactionManagement
註解來開啓 Spring 事務是,該註解代理的功能就是向 Spring 容器中添加了兩個 Bean:
-
AutoProxyRegistrar
-
ProxyTransactionManagementConfiguration
AutoProxyRegistrar 主要的作用是向 Spring 容器中註冊了一個InfrastructureAdvisorAutoProxyCreator
的 Bean。而InfrastructureAdvisorAutoProxyCreator
繼承了AbstractAdvisorAutoProxyCreator
,所以這個類的主要作用就是開啓自動代理的作用,也就是一個 BeanPostProcessor,會在初始化後步驟中去尋找 Advisor 類型的 Bean,並判斷當前某個 Bean 是否有匹配的 Advisor,是否需要利用動態代理產生一個代理對象。
ProxyTransactionManagementConfiguration
是一個配置類,它又定義了另外三個 bean:
-
BeanFactoryTransactionAttributeSourceAdvisor:一個 Advisor
-
AnnotationTransactionAttributeSource:相當於
BeanFactoryTransactionAttributeSourceAdvisor
中的 Pointcut -
TransactionInterceptor:相當於
BeanFactoryTransactionAttributeSourceAdvisor
中的 Advice
AnnotationTransactionAttributeSource
就是用來判斷某個類上是否存在@Transactional
註解,或者判斷某個方法上是否存在@Transactional
註解的。
TransactionInterceptor 就是代理邏輯,當某個類中存在 @Transactional 註解時,到時就產生一個代理對象作爲 Bean,代理對象在執行某個方法時,最終就會進入到 TransactionInterceptor 的 invoke() 方法。
二、Spring 事務基本執行原理
一個 Bean 在執行 Bean 的創建生命週期時,會經過InfrastructureAdvisorAutoProxyCreator
的初始化後的方法,會判斷當前 Bean 對象是否和BeanFactoryTransactionAttributeSourceAdvisor
匹配,匹配邏輯爲判斷該 Bean 的類上是否存在 @Transactional 註解,或者類中的某個方法上是否存在 @Transactional 註解,如果存在則表示該 Bean 需要進行動態代理產生一個代理對象作爲 Bean 對象。
該代理對象在執行某個方法時,會再次判斷當前執行的方法是否和BeanFactoryTransactionAttributeSourceAdvisor
匹配,如果匹配則執行該 Advisor 中的 TransactionInterceptor 的invoke()
方法,執行基本流程爲:
-
利用所配置的 PlatformTransactionManager 事務管理器新建一個數據庫連接
-
修改數據庫連接的 autocommit 爲 false
-
執行 MethodInvocation.proceed() 方法,簡單理解就是執行業務方法,其中就會執行 sql
-
如果沒有拋異常,則提交
-
如果拋了異常,則回滾
三、Spring 事務詳細執行流程
Spring 事務執行流程圖:https://www.processon.com/view/link/5fab6edf1e0853569633cc06
四、Spring 事務傳播機制
在開發過程中,經常會出現一個方法調用另外一個方法,那麼這裏就涉及到了多種場景,比如 a() 調用 b():
-
a() 和 b() 方法中的所有 sql 需要在同一個事務中嗎?
-
a() 和 b() 方法需要單獨的事務嗎?
-
a() 需要在事務中執行,b() 還需要在事務中執行嗎?
-
等等情況…
所以,這就要求 Spring 事務能支持上面各種場景,這就是 Spring 事務傳播機制的由來。那 Spring 事務傳播機制是如何實現的呢?
先來看上述幾種場景中的一種情況,a() 在一個事務中執行,調用 b() 方法時需要新開一個事務執行:
-
首先,代理對象執行
a()
方法前,先利用事務管理器新建一個數據庫連接 a -
將數據庫連接 a 的 autocommit 改爲 false
-
把數據庫連接 a 設置到 ThreadLocal 中
-
執行
a()
方法中的 sql -
執行
a()
方法過程中,調用了b()
方法(注意用代理對象調用 b() 方法) -
代理對象執行
b()
方法前,判斷出來了當前線程中已經存在一個數據庫連接 a 了,表示當前線程其實已經擁有一個 Spring 事務了,則進行掛起 -
掛起就是把 ThreadLocal 中的數據庫連接 a 從 ThreadLocal 中移除,並放入一個掛起資源對象中
-
掛起完成後,再次利用事務管理器新建一個數據庫連接 b
-
將數據庫連接 b 的 autocommit 改爲 false
-
把數據庫連接 b 設置到 ThreadLocal 中
-
執行
b()
方法中的 sql -
b()
方法正常執行完,則從 ThreadLocal 中拿到數據庫連接 b 進行提交 -
提交之後會恢復所掛起的數據庫連接 a,這裏的恢復,其實只是把在掛起資源對象中所保存的數據庫連接 a 再次設置到 ThreadLocal 中
-
a()
方法正常執行完,則從 ThreadLocal 中拿到數據庫連接 a 進行提交
這個過程中最爲核心的是:在執行某個方法時,判斷當前是否已經存在一個事務,就是判斷當前線程的 ThreadLocal 中是否存在一個數據庫連接對象,如果存在則表示已經存在一個事務了。
五、Spring 事務傳播機制分類
其中,以非事務方式運行,表示以非 Spring 事務運行,表示在執行這個方法時,Spring 事務管理器不會去建立數據庫連接,執行 sql 時,由 Mybatis 或 JdbcTemplate 自己來建立數據庫連接來執行 sql。
案例分析
情況 1
@Component
public class UserService {
@Autowired
private UserService userService;
@Transactional
public void test() {
// test方法中的sql
userService.a();
}
@Transactional
public void a() {
// a方法中的sql
}
}
默認情況下傳播機制爲 REQUIRED,表示當前如果沒有事務則新建一個事務,如果有事務則在當前事務中執行。
所以上面這種情況的執行流程如下:
-
新建一個數據庫連接 conn
-
設置 conn 的 autocommit 爲 false
-
執行 test 方法中的 sql
-
執行 a 方法中的 sql
-
執行 conn 的 commit() 方法進行提交
情況 2
假如是這種情況:
@Component
public class UserService {
@Autowired
private UserService userService;
@Transactional
public void test() {
// test方法中的sql
userService.a();
int result = 100/0;
}
@Transactional
public void a() {
// a方法中的sql
}
}
所以上面這種情況的執行流程如下:
-
新建一個數據庫連接 conn
-
設置 conn 的 autocommit 爲 false
-
執行 test 方法中的 sql
-
執行 a 方法中的 sql
-
拋出異常
-
執行 conn 的 rollback() 方法進行回滾,所以兩個方法中的 sql 都會回滾掉
情況 3
假如是這種情況:
@Component
public class UserService {
@Autowired
private UserService userService;
@Transactional
public void test() {
// test方法中的sql
userService.a();
}
@Transactional
public void a() {
// a方法中的sql
int result = 100/0;
}
}
所以上面這種情況的執行流程如下:
-
新建一個數據庫連接 conn
-
設置 conn 的 autocommit 爲 false
-
執行 test 方法中的 sql
-
執行 a 方法中的 sql
-
拋出異常
-
執行 conn 的 rollback() 方法進行回滾,所以兩個方法中的 sql 都會回滾掉
情況 4
如果是這種情況:
@Component
public class UserService {
@Autowired
private UserService userService;
@Transactional
public void test() {
// test方法中的sql
userService.a();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void a() {
// a方法中的sql
int result = 100/0;
}
}
所以上面這種情況的執行流程如下:
-
新建一個數據庫連接 conn
-
設置 conn 的 autocommit 爲 false
-
執行 test 方法中的 sql
-
又新建一個數據庫連接 conn2
-
執行 a 方法中的 sql
-
拋出異常
-
執行 conn2 的 rollback() 方法進行回滾
-
繼續拋異常,對於 test() 方法而言,它會接收到一個異常,然後拋出
-
執行 conn 的 rollback() 方法進行回滾,最終還是兩個方法中的 sql 都回滾了
六、Spring 事務強制回滾
正常情況下,a()
調用b()
方法時,如果b()
方法拋了異常,但是在a()
方法捕獲了,那麼a()
的事務還是會正常提交的,但是有的時候,我們捕獲異常可能僅僅只是不把異常信息返回給客戶端,而是爲了返回一些更友好的錯誤信息,而這個時候,我們還是希望事務能回滾的,那這個時候就得告訴 Spring 把當前事務回滾掉,做法就是:
@Transactional
public void test(){
// 執行sql
try {
b();
} catch (Exception e) {
// 構造友好的錯誤信息返回
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
public void b() throws Exception {
throw new Exception();
}
七、TransactionSynchronization
Spring 事務有可能會提交,回滾、掛起、恢復,所以 Spring 事務提供了一種機制,可以讓程序員來監聽當前 Spring 事務所處於的狀態。
@Component
public class UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private UserService userService;
@Transactional
public void test(){
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void suspend() {
System.out.println("test被掛起了");
}
@Override
public void resume() {
System.out.println("test被恢復了");
}
@Override
public void beforeCommit(boolean readOnly) {
System.out.println("test準備要提交了");
}
@Override
public void beforeCompletion() {
System.out.println("test準備要提交或回滾了");
}
@Override
public void afterCommit() {
System.out.println("test提交成功了");
}
@Override
public void afterCompletion(int status) {
System.out.println("test提交或回滾成功了");
}
});
jdbcTemplate.execute("insert into t1 values(1,1,1,1,'1')");
System.out.println("test");
userService.a();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void a(){
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void suspend() {
System.out.println("a被掛起了");
}
@Override
public void resume() {
System.out.println("a被恢復了");
}
@Override
public void beforeCommit(boolean readOnly) {
System.out.println("a準備要提交了");
}
@Override
public void beforeCompletion() {
System.out.println("a準備要提交或回滾了");
}
@Override
public void afterCommit() {
System.out.println("a提交成功了");
}
@Override
public void afterCompletion(int status) {
System.out.println("a提交或回滾成功了");
}
});
jdbcTemplate.execute("insert into t1 values(2,2,2,2,'2')");
System.out.println("a");
}
}
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/Jhoe_r5RzARgnaAj9NFL1w