淺談 Spring 事務底層原理

一、@EnableTransactionManagement 工作原理

開啓 Spring 事務本質上就是增加了一個 Advisor,但我們使用@EnableTransactionManagement註解來開啓 Spring 事務是,該註解代理的功能就是向 Spring 容器中添加了兩個 Bean:

AutoProxyRegistrar 主要的作用是向 Spring 容器中註冊了一個InfrastructureAdvisorAutoProxyCreator的 Bean。而InfrastructureAdvisorAutoProxyCreator繼承了AbstractAdvisorAutoProxyCreator,所以這個類的主要作用就是開啓自動代理的作用,也就是一個 BeanPostProcessor,會在初始化後步驟中去尋找 Advisor 類型的 Bean,並判斷當前某個 Bean 是否有匹配的 Advisor,是否需要利用動態代理產生一個代理對象。

ProxyTransactionManagementConfiguration是一個配置類,它又定義了另外三個 bean:

AnnotationTransactionAttributeSource就是用來判斷某個類上是否存在@Transactional註解,或者判斷某個方法上是否存在@Transactional註解的。

TransactionInterceptor 就是代理邏輯,當某個類中存在 @Transactional 註解時,到時就產生一個代理對象作爲 Bean,代理對象在執行某個方法時,最終就會進入到 TransactionInterceptor 的 invoke() 方法。

二、Spring 事務基本執行原理

一個 Bean 在執行 Bean 的創建生命週期時,會經過InfrastructureAdvisorAutoProxyCreator的初始化後的方法,會判斷當前 Bean 對象是否和BeanFactoryTransactionAttributeSourceAdvisor匹配,匹配邏輯爲判斷該 Bean 的類上是否存在 @Transactional 註解,或者類中的某個方法上是否存在 @Transactional 註解,如果存在則表示該 Bean 需要進行動態代理產生一個代理對象作爲 Bean 對象。

該代理對象在執行某個方法時,會再次判斷當前執行的方法是否和BeanFactoryTransactionAttributeSourceAdvisor匹配,如果匹配則執行該 Advisor 中的 TransactionInterceptor 的invoke()方法,執行基本流程爲:

  1. 利用所配置的 PlatformTransactionManager 事務管理器新建一個數據庫連接

  2. 修改數據庫連接的 autocommit 爲 false

  3. 執行 MethodInvocation.proceed() 方法,簡單理解就是執行業務方法,其中就會執行 sql

  4. 如果沒有拋異常,則提交

  5. 如果拋了異常,則回滾

三、Spring 事務詳細執行流程

Spring 事務執行流程圖:https://www.processon.com/view/link/5fab6edf1e0853569633cc06

四、Spring 事務傳播機制

在開發過程中,經常會出現一個方法調用另外一個方法,那麼這裏就涉及到了多種場景,比如 a() 調用 b():

所以,這就要求 Spring 事務能支持上面各種場景,這就是 Spring 事務傳播機制的由來。那 Spring 事務傳播機制是如何實現的呢?

先來看上述幾種場景中的一種情況,a() 在一個事務中執行,調用 b() 方法時需要新開一個事務執行:

這個過程中最爲核心的是:在執行某個方法時,判斷當前是否已經存在一個事務,就是判斷當前線程的 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,表示當前如果沒有事務則新建一個事務,如果有事務則在當前事務中執行。

所以上面這種情況的執行流程如下:

  1. 新建一個數據庫連接 conn

  2. 設置 conn 的 autocommit 爲 false

  3. 執行 test 方法中的 sql

  4. 執行 a 方法中的 sql

  5. 執行 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
 }
}

所以上面這種情況的執行流程如下:

  1. 新建一個數據庫連接 conn

  2. 設置 conn 的 autocommit 爲 false

  3. 執行 test 方法中的 sql

  4. 執行 a 方法中的 sql

  5. 拋出異常

  6. 執行 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;
 }
}

所以上面這種情況的執行流程如下:

  1. 新建一個數據庫連接 conn

  2. 設置 conn 的 autocommit 爲 false

  3. 執行 test 方法中的 sql

  4. 執行 a 方法中的 sql

  5. 拋出異常

  6. 執行 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;
 }
}

所以上面這種情況的執行流程如下:

  1. 新建一個數據庫連接 conn

  2. 設置 conn 的 autocommit 爲 false

  3. 執行 test 方法中的 sql

  4. 又新建一個數據庫連接 conn2

  5. 執行 a 方法中的 sql

  6. 拋出異常

  7. 執行 conn2 的 rollback() 方法進行回滾

  8. 繼續拋異常,對於 test() 方法而言,它會接收到一個異常,然後拋出

  9. 執行 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