天天看點

淺談 Spring 事務底層原理,看了都說好

作者:java小悠

一、@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()方法,執行基本流程為:

  1. 利用所配置的PlatformTransactionManager事務管理器建立一個資料庫連接配接
  2. 修改資料庫連接配接的autocommit為false
  3. 執行MethodInvocation.proceed()方法,簡單了解就是執行業務方法,其中就會執行sql
  4. 如果沒有抛異常,則送出
  5. 如果抛了異常,則復原

三、Spring事務詳細執行流程

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

淺談 Spring 事務底層原理,看了都說好

四、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,表示目前如果沒有事務則建立一個事務,如果有事務則在目前事務中執行。另外,搜尋公衆号Linux中文社群背景回複“指令行”,擷取一份驚喜禮包。

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

  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;
 }
}
           
牛逼啊!接私活必備的 N 個開源項目!趕快收藏           

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

  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");
 }

}           
原文連結:https://mp.weixin.qq.com/s/_qKOogkCgCTucIwZtOBOQQ