天天看點

Spring 事務傳播行為淺析

文章目錄

    • 1 Spring中七種事務傳播行為
    • 2 定義
    • 3 定理
    • 4 推理
    • 5 證明
      • 5.1 事務傳播行為證明
      • 5.2 推理證明
    • 6 事務切面設定的幾個核心狀态
      • 6.1 newTransaction
      • 6.2 actualNewSynchronization
      • 6.3 isSynchronizationActive/actualTransactionActive
      • 6.4 connectionHolder
      • 6.5 currentTransactionStatus
    • 7 required/requires_new/nested 淺析
      • 7.1 doBegin
      • 7.2 prepareSynchronization
    • 8 support/not_support/never 淺析
    • 9 REQUIRED/SUPPORTS/ MANDATORY 淺析
    • 10 NESTED 淺析
    • 11 REQUIRES_NEW 淺析
    • 12 NOT_SUPPORTED 淺析
    • 13 資料庫操作執行時用到和設定的幾個狀态
      • 13.1 擷取 session
      • 13.2 擷取 connection
    • 14 connection auto-commit 屬性介紹
    • 15 善用 PROPAGATION_SUPPORTS
    • 參考
  • Spring 在 TransactionDefinition 接口中規定了 7 種類型的事務傳播行為
  • 事務傳播行為是Spring架構獨有的事務增強特性,不屬于資料庫行為
  • 通過這 7 種 傳播行為,可以使我們更靈活的控制哪些行為在同一個事務内、哪些行為不在同一事務内、各事務之間的關系,進而使得我們的業務邏輯更加靈活

1 Spring中七種事務傳播行為

  • 事務傳播行為規定了在一個嵌套方法内部,哪些方法屬于同一個事務,哪些方法不屬于同一個事務,以及事務之間的關系
事務傳播行為類型 說明
PROPAGATION_REQUIRED 如果存在則加入事務。如果不存在,建立新事務
PROPAGATION_SUPPORTS 如果存在則加入事務。如果不存在,以不可用事務執行
PROPAGATION_MANDATORY 如果存在則加入事務。如果不存在,抛出異常
PROPAGATION_REQUIRES_NEW 如果存在則挂起目前事務,并建立新事務。如果不存在則建立事務
PROPAGATION_NESTED 如果存在則建立嵌套事務。如果不存在,建立新事務
PROPAGATION_NOT_SUPPORTED 以不可用事務運作。如果存在事務,則先挂起目前事務
PROPAGATION_NEVER 以不可用事務運作。如果存在事務,抛出異常

2 定義

  • null:表示沒有 @Transactional 注解的方法,且不在事務内
  • NULL:表示沒有 @Transactional 注解的方法,但在事務内(其外部有事務,是以加入外部事務)
  • required:表示有 @Transactional(propagation = Propagation.REQUIRED) 的方法,但是外部沒有事務
  • REQUIRED:表示有 @Transactional(propagation = Propagation.REQUIRED) 的方法,但是外部有事務
  • 同理其它 6 個傳播行為
  • 資料庫錯誤:表示有 @Transactional 注解的方法,執行資料庫操作時失敗,但是異常最終被 catch 掉了
  • 異常:方法抛出來的、沒有被捕獲的異常
  • 方法被復原:表示方法内部的資料庫操作被復原
  • 事務切面方法:表示加入事務,且加入事務切面(被 @Transactional 注解注釋的方法才會加入事務切面)
  • 事務方法:表示加入事務的方法(包含事務切面方法)
  • 外層事務:表示先加入同一事務的方法
  • 真實事務:表示有 @Transactional 注解的方法
  • 普通方法:沒有 @Transactional 注解的方法

3 定理

  • null 方法不受事務影響,隻要執行成功就不會被復原(例:插入了一行資料後,執行其它語句立馬報錯也不會被復原,因為null 方法插入後會立馬送出,其 auto-commit 為 true)
  • 事務内發生 異常,加入這個事務的所有資料庫操作都會被復原

4 推理

  1. NULL 方法受事務影響,外層事務被復原,NULL 方法也會被復原(…方法證明)
  2. 如果 NULL 方法發生 資料庫錯誤,不影響事務(即是 3)
  3. required 内部的 NULL 方法發生 資料庫錯誤,required 方法不會被復原(雖然在同一事務内,但是 NULL 方法的資料庫異常不會傳遞到事務切面,因為 NULL 方法雖然加入了事務,但是沒有加入事務切面)
  4. required 内部的 REQUIRED 方法發生 資料庫錯誤,required 方法被復原(通過事務切面将復原狀态傳遞到外層)
  5. required 内部的 SUPPORTS 方法發生 資料庫錯誤,required 方法被復原(同理)
  6. required 内部的 MANDATORY 方法發生 資料庫錯誤,required 方法被復原(同理)
  7. required 内部的 REQUIRES_NEW 方法發生 資料庫錯誤,required 方法不會被復原(不在同一個事務)
  8. required 内部的 NESTED 方法發生 資料庫錯誤,required 方法不會被復原(NESTED 發生資料庫錯誤會復原到儲存點,保證了外部事務的有效性)
  9. required 内部的 NOT_SUPPORTED 方法發生 資料庫錯誤,required 方法不會被復原(NOT_SUPPORTED 方法 不在事務内)
  10. requires_new、nested 情況和 required 相同,因為這兩個都隻是開啟新事務,意思和 required 一模一樣
  11. supports、not_supported、never 都是以非事務運作和 null 效果一樣(null之内的普通方法不能複用session)
  12. mandatory 直接抛異常
  13. NESTED:--------0------0---------,兩個0 之間是嵌套事務,不管是内層嵌套事務還是外層事務,其實是一個事務,隻是在這個事務線上建立了一個儲存點,嵌套事務内部事情處理完了之後,整個事務狀态會回到儲存點時的狀态,這樣保證了即使嵌套内部發生資料庫錯誤(這時仍然會設定事務為 rollback 狀态),因為方法完了之後事務狀态會回到儲存點,是以 rollback 狀态被覆寫了
  14. NESTED 和 NULL 的差別
    • NULL 内部的 事務方法 屬于 NULL 所在事務,而 NESTED 内部的 事務方法 屬于 NESTED 開啟的嵌套事務和外層事務沒關系
    1. NULL 内部的 事務切面方法 發生 資料庫錯誤 會導緻外部事務復原,而 NESTED 内部的事務切面方法 發生 資料庫錯誤 隻會導緻 NESTED 開啟的嵌套事務復原,而外層事務不會
  15. NESTED 和 REQUIRES_NEW 的差別
    • 外層事務發生 資料庫錯誤 會導緻 NESTED 方法復原,而不會導緻 REQUIRES_NEW 方法復原

5 證明

5.1 事務傳播行為證明

  • 事務傳播行為證明

5.2 推理證明

  • 推理證明

6 事務切面設定的幾個核心狀态

6.1 newTransaction

  • 是否是新開啟的事務
  • 此屬性是直接指派的,沒有判斷邏輯
  • 所有的小寫/REQUIRES_NEW 為 true
  • 所有的大寫(除了 REQUIRES_NEW) 為 false
  • 事務關閉時如果 newTransaction= true 且 actualTransactionActive=true,才會做 connection 的 commit 操作

6.2 actualNewSynchronization

  • 目前線程是否是新開啟同步的
  • 如果目前線程已經開啟同步了,傳回 false。否則,傳回 true
AbstractPlatformTransactionManager#newTransactionStatus
actualNewSynchronization = !TransactionSynchronizationManager.isSynchronizationActive()
           

6.3 isSynchronizationActive/actualTransactionActive

  • 目前線程是否同步/目前線程的事務是否可用
  • 如果 actualNewSynchronization 為 false 的話
    • 如果 status 中的 transaction 為 null,actualTransactionActive=false。否則為 true
    • isSynchronizationActive = true
protected void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) {
    if (status.isNewSynchronization()) {
        //關鍵 1:設定目前線程事務是否活躍
        setActualTransactionActive(status.hasTransaction());
        //關鍵 2: 設定事務是否已經同步
        //synchronizations.set(new LinkedHashSet<>());
        initSynchronization();
    }
}
           

6.4 connectionHolder

  • 此屬性隻有在建立新的、可用的事務時,才會設定
  • transaction.newConnectionHolder = true
  • conHolder.setSynchronizedWithTransaction(true)
  • conHolder.setTransactionActive(true)
  • connection.setAutoCommit(false)
org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin
在 doBegin 方法中做了以下操作
	1. 建立 connectionHolder
    2. 将此 connectionHolder 設定到目前事務中,并設定 newConnectionHolder = true
    3. 設定 ConnectionHolder().setSynchronizedWithTransaction(true)  ----> 1
    4. 将此 connectionHolder 中的 connection 設定 auto-commit = false -----> 2 
    5. 設定 事務的 read only 狀态(如果指定了 read only 的話)
    6. getConnectionHolder().setTransactionActive(true)             -----> 3 
           

6.5 currentTransactionStatus

  • 設定目前線程的事務狀态
TransactionAspectSupport#prepareTransactionInfo#bindToThread
//擷取目前事務的狀态:TransactionStatus[transaction,newTransaction,newSynchronization,suspendedResources,readOnly]
//可以看出 之前很多狀态資訊 都在 TransactionStatus 中儲存了
//這個線程中儲存了 舊的 TransactionStatus 和目前的 TransactionStatus,舊的用于事務挂起等功能
private void bindToThread() {
    this.oldTransactionInfo = transactionInfoHolder.get();
    transactionInfoHolder.set(this);
}
           

7 required/requires_new/nested 淺析

  • 此三個狀态為 建立新事務
  • 事務切面後的核心狀态資訊
    • newTransaction=true(是新事務)
    • actualNewSynchronization=true(新開啟的)
    • isSynchronizationActive=true(新開啟同步)
    • actualTransactionActive = true(事務可用)
    • 建立并設定 connectionHolder(設定特定的 connectionHolder)
//AbstractPlatformTransactionManager#getTransaction
suspendedResources = suspend(null);//内部不進行任何操作,傳回 null
try {
   boolean newSynchronization = 始終為 true;
   //status 的關鍵屬性, newTransaction=true,actualNewSynchronization=true
   //說明是新事務,且新開啟事務同步(後面加入事務的就是 false) 
   DefaultTransactionStatus status = newTransactionStatus();
   //建立新的connection,并設定其狀态為和事務同步且代表一個活動的事務,并加入到線程中
   //設定事務 read only 辨別,如果需要的話 
   doBegin(transaction, definition);
   //設定目前線程的事務狀态:hasTransaction=true, isSynchronizationActive()=true
   //這樣在建立 session 後,就會将 session 加入目前線程,重複使用(registerSessionHolder()) 
   prepareSynchronization(status, definition);
   return status;
}
catch (RuntimeException | Error ex) {
   //這裡的 suspendedResources == null,内部不會做任何操作  
   resume(null, suspendedResources);
   throw ex;
}
           

7.1 doBegin

protected void doBegin(Object transaction, TransactionDefinition definition) {
   try { 
      if (!txObject.hasConnectionHolder() ||
            txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
         //因為事務中還沒有 connection 是以進入此處擷取 con(這裡就可以動态切換資料源了) 
         Connection newCon = obtainDataSource().getConnection();
         //将此 connection 設定到事務中,并标記為 新的連接配接 
         txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
      }
      //标記此 connection 為和事務同步狀态
      txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
      con = txObject.getConnectionHolder().getConnection();
      Integer previousIsolationLevel = 事務隔離級别;
      //Switch to manual commit 
	  //設定事務 read only 狀态(如果事務标記了 read only,通過注解中的屬性标記)
      prepareTransactionalConnection(con, definition);
      //标記此 connection 代表一個活動的事務
      txObject.getConnectionHolder().setTransactionActive(true);
      if (txObject.isNewConnectionHolder()) {
         //可以走到這,将上面建立的 connection 設定到目前線程中,這樣統一事務就複用 con 了
         TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
      }
   }
   catch (Throwable ex) {
      if (txObject.isNewConnectionHolder()) {
         //發生異常則關閉連接配接 
         DataSourceUtils.releaseConnection(con, obtainDataSource());
         txObject.setConnectionHolder(null, false);
      }
      throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
   }
}
           

7.2 prepareSynchronization

protected void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) {
    if (status.isNewSynchronization()) {//肯定為 true   
        setActualTransactionActive(status.hasTransaction());//肯定為 true
        setCurrentTransactionIsolationLevel(..);
        setCurrentTransactionReadOnly(..);
        setCurrentTransactionName(..);
        //synchronizations.set(new LinkedHashSet<>());
        initSynchronization();
    }
}
           

8 support/not_support/never 淺析

  • 此三個狀态為以 不可用事務 執行狀态
  • 事務切面後的核心狀态資訊
    • newTransaction=true(是新事務)
    • actualNewSynchronization=true(新開啟的)
    • isSynchronizationActive=true(新開啟同步)
    • actualTransactionActive = false(事務不可用)
    • 沒有建立 conHolder
  • 這 3 個方法表示以 非事務執行
    • 這樣雖然執行時建立了 session和connection 并加入了線程,當内部 事務方法 執行時也會重新建立事務,并覆寫 session/connection 資訊
    • 技巧: 當此3種方法内部有 普通方法 時,普通方法會複用 session/connection,減輕了建立 session/connection 的消耗,特别是在高并發的時候
    • 并且沒有執行 doBegin() 方法建立并設定 connection 的 auto-commit 為 false!,使得資料庫操作一執行完就立刻送出了
    @Override
    protected boolean isExistingTransaction(Object transaction) {
       DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
       //這裡傳回 false,因為  isTransactionActive() = false
       //傳回 false,就導緻新來的 真實事務 方法會走沒有事務存在的邏輯,這樣就覆寫了非事務綁定的 session 和 connection 資訊了 
       return (txObject.hasConnectionHolder() && txObject.getConnectionHolder().isTransactionActive());
    }
               
  • 和 6 的差別僅于
    • 6 通過 doBegin() 方法,建立 connection 相關資訊
    • 7 中的事務狀态 hasTransaction=false,actualTransactionActive=false
//AbstractPlatformTransactionManager#getTransaction
boolean newSynchronization = 這個目前始終為 true;
//設定目前線程的事務狀态:hasTransaction=true, isSynchronizationActive()=true
//ActualTransactionActive = false
//這樣在建立 session 後,就會将 session 加入目前線程,重複使用(registerSessionHolder()) 
return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);

protected final DefaultTransactionStatus prepareTransactionStatus(
    TransactionDefinition definition, @Nullable Object transaction, boolean newTransaction,
    boolean newSynchronization, boolean debug, @Nullable Object suspendedResources) {
    //transaction 為 null,hasTransaction=false, isSynchronizationActive()=true
    DefaultTransactionStatus status = newTransactionStatus(
        definition, transaction, newTransaction, newSynchronization, debug, suspendedResources);
    prepareSynchronization(status, definition);
    return status;
}
           

9 REQUIRED/SUPPORTS/ MANDATORY 淺析

  • 此三個狀态為 加入已存在事務
  • 事務切面後的核心狀态資訊
    • newTransaction=false(不是新事務)
    • actualNewSynchronization=false(不是新同步的)
    • 沒有設定 isSynchronizationActive(沿用建立事務時的狀态,一般 true)
    • 沒有設定 actualTransactionActive(沿用建立事務時的狀态,一般 true)
    • 沒有建立 conHolder(沿用建立事務時建立 conHolder)
//AbstractPlatformTransactionManager#handleExistingTransaction
boolean newSynchronization = 始終是 true;
return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);

protected final DefaultTransactionStatus prepareTransactionStatus(
    TransactionDefinition definition, @Nullable Object transaction, boolean newTransaction,
    boolean newSynchronization, boolean debug, @Nullable Object suspendedResources) {
    //hasTransaction=true,newTransaction=false,newSynchronization=false
    //以上值說明,目前的事務不是新開啟的事務,也不是新同步的事務
    DefaultTransactionStatus status = newTransactionStatus(
        definition, transaction, newTransaction, newSynchronization, debug, suspendedResources);
    //因為 newSynchronization=false,此方法内部不做任何處理
    prepareSynchronization(status, definition);
    return status;
}
           

10 NESTED 淺析

  • 此狀态為 建立嵌套事務。和 9 的唯一差別就是建立了一個 savepoint。在嵌套事務内發生的 資料庫錯誤,會復原到這個儲存點,是以并不會影響外部事務。外部事務仍可影響内部事務
  • 事務切面後的核心狀态資訊
    • newTransaction=false(不是新事務)
    • actualNewSynchronization=false(不是新同步的)
    • 沒有設定 isSynchronizationActive(沿用建立事務時的狀态,一般 true)
    • 沒有設定 actualTransactionActive(沿用建立事務時的狀态,一般 true)
    • 沒有建立 conHolder(沿用建立事務時建立 conHolder)
DefaultTransactionStatus status =
    prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
//和 9 的唯一差別就是場景 savepoint
status.createAndHoldSavepoint();
           

11 REQUIRES_NEW 淺析

  • 挂起之前的事務,重新建立新事務。完全可以當作 7 來看,隻是挂起了之前的事務
  • 事務切面後的核心狀态資訊
    • newTransaction=true(是新事務)
    • actualNewSynchronization=true(新開啟的)
    • isSynchronizationActive=true(新開啟同步)
    • actualTransactionActive = true(事務可用)
    • 建立并設定 connectionHolder(設定特定的 connectionHolder)
//挂起之前的事務(把之前的事務資訊儲存到目前事務,目前事務完成後,在設定回去)
SuspendedResourcesHolder suspendedResources = suspend(transaction);
try {
    boolean newSynchronization = true;
    DefaultTransactionStatus status = newTransactionStatus(
        definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
    doBegin(transaction, definition);
    prepareSynchronization(status, definition);
    return status;
}
catch (RuntimeException | Error beginEx) {
    //發生異常也重置挂起的事務
    resumeAfterBeginException(transaction, suspendedResources, beginEx);
    throw beginEx;
}
           

12 NOT_SUPPORTED 淺析

  • 挂起之前事務 (之前的 connection/session 資訊沒有了),以不可用事務執行。和 8 的唯一差別是儲存了之前事務。該事務執行完之後會重新設定之前的事務
  • 事務切面後的核心狀态資訊
    • newTransaction=true(是新事務)
    • actualNewSynchronization=true(新開啟的)
    • isSynchronizationActive=true(新開啟同步)
    • actualTransactionActive = false(事務不可用)
    • 沒有建立 conHolder
Object suspendedResources = suspend(transaction);
boolean newSynchronization = true;
return prepareTransactionStatus(
      definition, null, false, newSynchronization, debugEnabled, suspendedResources);
           

13 資料庫操作執行時用到和設定的幾個狀态

13.1 擷取 session

  • session 隻要有任何事務,都會加入到線程中,不可用事務也是
//org.mybatis.spring.SqlSessionUtils#getSqlSession#sessionHolder
//如果目前線程中的 sessionHolder 不為 null 且 此 session 和 事務處于同步狀态,就從事務中得到 session,否則傳回 null   
//目前隻要有 holder 則 isSynchronizedWithTransaction 肯定為 true
if (holder != null && holder.isSynchronizedWithTransaction()) {}

//org.mybatis.spring.SqlSessionUtils#getSqlSession#registerSessionHolder
//如果目前線程事務處于同步狀态,則将建立的 session 綁定到目前線程中
//隻要方法或方法外層有 @Transactional 任意類型注解,此處都為 true
if (TransactionSynchronizationManager.isSynchronizationActive()) {}
           

13.2 擷取 connection

  • 在此處建立的 connection 都不具備手動 commit/rollback 能力
  • 隻有在 doBegin() 方法中建立并加入事務的才正真具備事務能力
//org.springframework.jdbc.datasource.DataSourceUtils#doGetConnection
//如果目前線程存在 conHolder 且 conHolder 中存在 connection 或者 conholder 已經和事務同步,則直接傳回 conHolder 中的 connection
//隻要 conHolder 不為 null,後面一般都為 true
if (conHolder != null && (conHolder.hasConnection() || 	conHolder.isSynchronizedWithTransaction())) {
}
//org.springframework.jdbc.datasource.DataSourceUtils#doGetConnection
//隻要方法或方法外層有 @Transactional 任意類型注解,此處一般都為 true
if (TransactionSynchronizationManager.isSynchronizationActive()) {}
           

14 connection auto-commit 屬性介紹

  • 總的來說如果 auto-commit 為 true,則資料庫操作全部完成時就自動送出
将此連接配接的自動送出模式設定為給定狀态。
如果連接配接處于自動送出模式,則其所有SQL語句都将作為單個事務執行和送出。否則,隻會在事務完成或失敗後統一進行 commit 或 rollback。
預設情況下,新連接配接處于自動送出模式。自動送出發生在 statement 完成後。
statement 完成的時間取決于SQL語句的類型:
	1. 對于DML語句,如Insert、Update或Delete和DDL語句,該語句在完成執行後即完成。
	2. 對于Select語句,當關聯的結果集關閉時,該語句即完成。
	3. 對于CallableStatement對象或傳回多個結果的語句,當所有關聯的結果集都已關閉,并且已檢索到所有更新計數和輸出參數時,該語句即完成。
- 總的來說就是資料庫操作全部完成時就自動送出	

注意:如果在事務期間調用此方法,并且更改了自動送出模式,則将送出事務。如果調用setAutoCommit并且自動送出模式沒有更改,則調用是no-op。
void setAutoCommit(boolean autoCommit) throws SQLException;
           

15 善用 PROPAGATION_SUPPORTS

  • 如果存在則加入事務。如果不存在,以非事務執行
  • 此 傳播類型其實和沒有 @Transactional 标記的作用基本一緻
    • 如果外部有事務,則 這兩種情況都加入事務
    • 如果外部沒有事務,則這兩種情況都會自動送出
  • 差別: SUPPORTS 方法在外圍沒有事務時,也會建立 session/connection 并加入到線程,這樣在此方法内的其它 普通方法 都會重用線程中的 session/connection,避免了頻繁建立!

參考

spring-boot-starter:2.1.13.RELEASE

mybatis-spring-boot-starter:2.1.1

繼續閱讀