18個示例詳解 Spring 事務傳播機制(附測試源碼)


什么是事務傳播機制

事務的傳播機制,顧名思義就是多個事務方法之間調用,事務如何在這些方法之間傳播。

舉個例子,方法 A 是一個事務的方法,方法 A 執行的時候調用了方法 B,此時方法 B 有無事務以及是否需要事務都會對方法 A 和方法 B 產生不同的影響,而這個影響是由兩個方法的事務傳播機制決定的。

傳播屬性 Propagation 枚舉

Spring 對事務的傳播機制在 Propagation 枚舉中定義了7個分類:

  • REQUIRED 默認
  • SUPPORTS 支持
  • MANDATORY 強制
  • REQUIRES_NEW 新建
  • NOT_SUPPORTED 不支持
  • NEVER 從不
  • NESTED 嵌套

事務的傳播機制,是 spring 規定的。因為在開發中,最簡單的事務是,業務代碼都處於同一個事務下,這也是默認的傳播機制,如果出現的報錯,所有的數據回滾。
但是在處理復雜的業務邏輯時,方法之間的調用,有以下的需求:

  • 調用的方法需要新增一個事務,新事務和原來的事務各自獨立。
  • 調用的方法不支持事務
  • 調用的方法是一個嵌套的事務

7種傳播機制詳解

首先創建兩個方法 A 和 B 實現數據的插入,插入數據A:

public class AService {
    public void A(String name) {
        userService.insertName("A-" + name);
    }

}

插入數據B:

public class BService {
    public void B(String name) {
        userService.insertName("B-" + name);
    }

}

使用偽代碼創建 mainTest 方法和 childTest 方法

   public void mainTest(String name)  {
        // 存入a1
        A(a1);
        childTest(name);
    }

main 調用 test 方法,其中

   public void childTest(String name)  {
        // 存入b1
        B(b1);
       throw new RuntimeException(); 
       // 存入 b2
       B2(b2);
    }

以上偽代碼,調用 mainTest 方法,如果mainTest 和childTest 都不使用事務的話,數據存儲的結果是如何呢?

因為都沒使用事務,所以 a1 和 b1 都存到成功了,而之后拋出異常之后,b2是不會執行的。所以 a1 和 b1 都插入的數據,而 b2 沒有插入數據。

REQUIRED(默認事務)

      /**
	 * Support a current transaction, create a new one if none exists.
	 * Analogous to EJB transaction attribute of the same name.
	 * <p>This is the default setting of a transaction annotation.
	 */
	REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),

如果當前不存在事務,就新建一個事務。如果存在事務,就加入到當前事務。這是一個默認的事務

示例1:根據場景舉個例子,在 childTest 添加事務,設置傳播屬性為 REQUIRED,偽代碼如下:

   public void mainTest(String name)  {
        // 存入a1
        A(a1);
        childTest(name);
    }
   @Transactional(propagation = Propagation.REQUIRED)
   public void childTest(String name)  {
        // 存入b1
        B(b1);
       throw new RuntimeException(); 
    }

因為 mainTest 沒有事務,而 childTest 又是新建一個事務,所以 a1 添加成功。在 childTest 因為拋出了異常,不會執行 b2 添加,而 b1 添加回滾。最終 a1 添加成功,b1沒添加成功。

示例2:在 mainTest 和 childTest 都添加事務,傳播屬性都為 REQUIRED,偽代碼如下:

   @Transactional(propagation = Propagation.REQUIRED)
   public void mainTest(String name) {
        // 存入a1
        A(a1);
        childTest(name);
    }
   @Transactional(propagation = Propagation.REQUIRED)
   public void childTest(String name) {
        // 存入b1
        B(b1);
       throw new RuntimeException(); 
   }

根據 REQUIRED 傳播屬性,如果存在事務,就加入到當前事務。兩個方法都屬於同一個事務,同一個事務的話,如果有發生異常,則全部都回滾。所以 a1 和 b1 都沒添加成功。

SUPPORTS

/**
	 * Support a current transaction, execute non-transactionally if none exists.
	 * Analogous to EJB transaction attribute of the same name.
	 * <p>Note: For transaction managers with transaction synchronization,
	 */

如果當前沒有事務,則以非事務的方式運行。如果存在事務,就加入到當前事務。

示例3:childTest 添加事務,傳播屬性設置為 SUPPORTS,偽代碼如下:

   public void mainTest(String name) {
        A(a1);
        childTest(name);
    }
   @Transactional(propagation = Propagation.SUPPORTS)
   public void childTest(String name) {
        B(b1);
       throw new RuntimeException(); 
    }

傳播屬性為 SUPPORTS,如果沒有事務,就以非事務的方式運行。表明兩個方法都沒有使用事務,沒有事務的話,a1、b1 都添加成功。

示例4:mainTest 添加事務,設置傳播屬性為 REQUIRED。childTest 添加事務,設置傳播屬性為 SUPPORTS,偽代碼如下:

   @Transactional(propagation = Propagation.REQUIRED)
   public void mainTest(String name) {
        A(a1);
        childTest(name);
    }
   @Transactional(propagation = Propagation.SUPPORTS)
   public void childTest(String name) {
        B(b1);
       throw new RuntimeException(); 
       B2(b2);
    }

SUPPORTS 傳播屬性,如果存在事務,就加入到當前事務。mainTest 和 childTest 都屬於同一個事務,而 childTest 拋出異常,a1 和b1 添加都回滾,最終 a1、b1 添加失敗。

MANDATORY

/**
	 * Support a current transaction, throw an exception if none exists.
	 * Analogous to EJB transaction attribute of the same name.
	 */

如果存在事務,就加入到當前事務。如果不存在事務,就報錯
這就說明如果想調用 MANDATORY 傳播屬性的方法,一定要有事務,不然就會報錯。

MANDATORY 類似功能限制,必須要被有事務的方法的調用,不然就會報錯。

示例5: 首先在 childTest 添加事務,設置傳播屬性為 MANDATORY,偽代碼如下:

  public void mainTest(String name) {
       A(a1);
       childTest(name);
   }
  @Transactional(propagation = Propagation.MANDATORY)
  public void childTest(String name) {
       B(b1);
      throw new RuntimeException(); 
      B2(b2);
   }

在控制台直接報錯:

No existing transaction found for transaction marked with propagation 'mandatory'

說明被標記為 mandatory 傳播屬性沒找到事務,直接報錯。因為 mainTest 沒有事務,a1 添加成功。而 childTest 由於報錯,b1 添加失敗。

示例6: mainTest 添加事務,設置傳播屬性為 REQUIRED。childTest 添加事務,設置傳播屬性為 MANDATOR,偽代碼如下:

  @Transactional(propagation = Propagation.REQUIRED)
  public void mainTest(String name) {
       A(a1);
       childTest(name);
   }
  @Transactional(propagation = Propagation.MANDATORY)
  public void childTest(String name) {
       B(b1);
      throw new RuntimeException(); 
   }

如果存在事務,就把事務加入到當前事務。同一個事務中 childTest 拋出異常,a1 和 b1 添加被回滾,所以a1 和 b1添加失敗。

REQUIRES_NEW

   /**
	 * Create a new transaction, and suspend the current transaction if one exists.
	 * Analogous to the EJB transaction attribute of the same name.
	 * <p><b>NOTE:</b> Actual transaction suspension will not work out-of-the-box
	 */

創建一個新的事務。如果存在事務,就將事務掛起。

無論是否有事務,都會創建一個新的事務。

示例7:childTest 添加事務,設置傳播屬性為 REQUIRES_NEW,偽代碼如下:

  public void mainTest(String name) {
       A(a1);
       childTest(name);
   }
  @Transactional(propagation = Propagation.REQUIRES_NEW)
  public void childTest(String name) {
       B(b1);
      throw new RuntimeException(); 
   }

mainTest 不存在事務,a1 添加成功,childTest 新建了一個事務,報錯,回滾 b1。所以 a1 添加成功,b1 添加失敗。

示例8:mainTest 添加事務,設置傳播屬性為 REQUIRED。childTest 添加事務,設置傳播屬性為 REQUIRES_NEW,偽代碼如下:

  @Transactional(propagation = Propagation.REQUIRED)
  public void mainTest(String name) {
       A(a1);
       childTest(name);
   }
  @Transactional(propagation = Propagation.REQUIRES_NEW)
  public void childTest(String name) {
       B(b1);
      throw new RuntimeException(); 
   }

mainTest 創建了一個事務,childTest 新建一個事務,在 childTest 事務中,拋出異常,b1 回滾,異常拋到 mainTest 方法,a1 也回滾,最終 a1 和 b1 都回滾。

示例9:在示例8中,如果不想讓 REQUIRES_NEW 傳播屬性影響到被調用事務,將異常捕獲就不會影響到被調用事務。

  @Transactional(propagation = Propagation.REQUIRED)
  public void mainTest(String name) {
       A(a1);
       try {
              childTest(name);
       } catch (Exception e) {
           e.printStackTrace();
       }   
   }
  @Transactional(propagation = Propagation.REQUIRES_NEW)
  public void childTest(String name) {
       B(b1);
      throw new RuntimeException(); 
   }

childTest 拋出了異常,在 mainTest 捕獲了,對 mainTest 沒有影響,所以 b1 被回滾,b1 添加失敗,a1 添加成功。

示例10:mainTest 設置傳播屬性為 REQUIRED,並在 mainTest 拋出異常。childTest 同樣設置 REQUIRES_NEW 傳播屬性,偽代碼如下:

  @Transactional(propagation = Propagation.REQUIRED)
  public void mainTest(String name) {
       A(a1);
       childTest(name);
       throw new RuntimeException();    
   }
  @Transactional(propagation = Propagation.REQUIRES_NEW)
  public void childTest(String name) {
       B(b1);  
       B2(b2);
   }

childTest 是一個新建的事務,只要不拋出異常是不會回滾,所以 b1 添加成功,而 mainTest 拋出了異常,a1 添加失敗。

REQUIRES_NEW 傳播屬性如果有異常,只會從被調用方影響調用方,而調用方不會影響調用方,即 childTest 拋出異常會影響 mainTest,而 mainTest 拋出異常不會到 childTest。

NOT_SUPPORTED

  /**
	 * Execute non-transactionally, suspend the current transaction if one exists.
	 * Analogous to EJB transaction attribute of the same name.
	 * <p><b>NOTE:</b> Actual transaction suspension will not work out-of-the-box
	 */

無論是否存在當前事務,都是以非事務的方式運行

示例11:childTest 添加事務,設置傳播屬性為 NOT_SUPPORTED,偽代碼如下:

  public void mainTest(String name) {
       A(a1);
       childTest(name);
          
   }
  @Transactional(propagation = Propagation.NOT_SUPPORTED)
  public void childTest(String name) {
       B(b1);  
      throw new RuntimeException();  
   }

NOT_SUPPORTED 都是以非事務的方式執行,childTest 不存在事務,b1 添加成功。而 mainTest 也是沒有事務,a1 也添加成功。

示例12:childTest 添加事務,設置傳播屬性為 NOT_SUPPORTED,mainTest 添加默認傳播屬性 REQUIRED,偽代碼如下:

  @Transactional(propagation = Propagation.REQUIRED)
  public void mainTest(String name) {
       A(a1);
       childTest(name);
          
   }
  @Transactional(propagation = Propagation.NOT_SUPPORTED)
  public void childTest(String name) {
       B(b1);  
      throw new RuntimeException();  
    }

其中 childTest 都是以非事務的方式執行,b1 添加成功。而 mainTest 存在事務,報錯后回滾,a1 添加失敗。

NEVER

   /**
	 * Execute non-transactionally, throw an exception if a transaction exists.
	 * Analogous to EJB transaction attribute of the same name.
	 */

不使用事務,如果存在事務,就拋出異常

NEVER 的方法不使用事務,調用 NEVER 方法如果有事務,就拋出異常。

示例13:childTest 添加 NEVER 傳播屬性,偽代碼如下:

  public void mainTest(String name) {
       A(a1);
       childTest(name);
          
   }
  @Transactional(propagation = Propagation.NEVER)
  public void childTest(String name) {
       B(b1);  
      throw new RuntimeException();  
      B2(b2);
   }

NEVER 不使用事務,mainTest 也不使用事務,所以 a1 和 b1 都添加成功,b2添加失敗。

示例14: mainTest 添加 REQUIRED 傳播屬性,childTest 傳播屬性設置為 NEVER,偽代碼如下:

  @Transactional(propagation = Propagation.REQUIRED)
  public void mainTest(String name) {
       A(a1);
       childTest(name);
          
   }
  @Transactional(propagation = Propagation.NEVER)
  public void childTest(String name) {
       B(b1);  
      throw new RuntimeException();  
   }

mainTest 存在事務,導致 childTest 報錯,b1添加失敗,childTest 拋錯到 mainTest,a1 添加失敗。

NESTED

/**
	 * Execute within a nested transaction if a current transaction exists,
	 * behave like PROPAGATION_REQUIRED else. There is no analogous feature in EJB.
	 * <p>Note: Actual creation of a nested transaction will only work on specific
	 */

如果當前事務存在,就運行一個嵌套事務。如果不存在事務,就和 REQUIRED 一樣新建一個事務。

示例15: childTest 設置 NESTED 傳播屬性,偽代碼如下:

  public void mainTest(String name) {
       A(a1);
       childTest(name);
          
   }
  @Transactional(propagation = Propagation.NESTED)
  public void childTest(String name) {
       B(b1);  
      throw new RuntimeException();  
   }

在 childTest 設置 NESTED 傳播屬性,相當於新建一個事務,所以 b1 添加失敗, mainTest 沒有事務,a1 添加成功。

示例16:設置 mainTest 傳播屬性為 REQUIRED,新建一個事務,並在方法最后拋出異常。 childTest 設置屬性為 NESTED,偽代碼如下:

  @Transactional(propagation = Propagation.REQUIRED)
  public void mainTest(String name) {
       A(a1);
       childTest(name);
       throw new RuntimeException();     
   }
  @Transactional(propagation = Propagation.NESTED)
  public void childTest(String name) {
       B(b1);         
      B2(b2);
   }

childTest 是一個嵌套的事務,當主事務的拋出異常時,嵌套事務也受影響,即 a1、b1 和 b2 都添加失敗。和示例10不同的是,示例10不會影響 childTest 事務。

  • NESTED 和 REQUIRED_NEW 的區別:
    • REQUIRED_NEW 是開啟一個新的事務,和調用的事務無關。調用方回滾,不會影響到 REQUIRED_NEW 事務。
    • NESTED 是一個嵌套事務,是調用方的一個子事務,如果調用方事務回滾,NESTED 也會回滾。

示例17:和示例16一樣,在 mainTest 設置傳播屬性為 REQUIRED,childTest 設置傳播屬性為 NESTED,不同的是,在 mainTest 捕獲 childTest 拋出的異常,偽代碼如下:

  @Transactional(propagation = Propagation.REQUIRED)
  public void mainTest(String name) {
       A(a1);
      try {
           childTest(name);
       } catch (RuntimeException e) {
           e.printStackTrace();
       } 
   }
  @Transactional(propagation = Propagation.NESTED)
  public void childTest(String name) {
      B(b1);         
      B2(b2);
      throw new RuntimeException(); 
   }

childTest 是一個子事務,報錯回滾,b1 和 b2 都添加失敗。而 mainTest 捕獲了異常,不受異常影響,a1 添加成功。

示例18:將示例2改造一下,mainTest 捕獲 childTest 拋出的異常,偽代碼如下:

  @Transactional(propagation = Propagation.REQUIRED)
  public void mainTest(String name) {
       A(a1);
      try {
           childTest(name);
       } catch (RuntimeException e) {
           e.printStackTrace();
       } 
   }
  @Transactional(propagation = Propagation.REQUIRED)
  public void childTest(String name) {
      B(b1);         
      B2(b2);
      throw new RuntimeException(); 
   }

mainTest 和 childTest 兩個方法都處於同一個事務,如果有一個方法報錯回滾,並且被捕獲。整個事務如果還有數據添加就會拋出 Transaction rolled back because it has been marked as rollback-only 異常,同一個事務,不能出現有的回滾了,有的不回滾,要么一起回滾,要不一起執行成功。所以全部數據都添加失敗。

  • 對比示例17示例18,NESTED 和 REQUIRED 的區別:
    • REQUIRED 傳播屬性表明調用方和被調用方都是使用同一個事務,被調用方出現異常,無論異常是否被捕獲,因為屬於同一個事務,只要發生異常,事務都會回滾。
    • NESTED 被調用方出現異常,只要異常被捕獲,只有被調用方事務回滾,調用方事務不受影響。

總結

傳播屬性 總結
REQUIRED 默認屬性,所有的事務都處於同一個事務下,出現異常,不管是否捕獲所有事務回滾
SUPPORTS 如果不存事務,就以非事務的方式運行,存在事務就加入該事務
MANDATORY 強制調用方添加事務,如果不存在事務就報錯,存在事務就加入該事務
REQUIRES_NEW 無論調用方是否存在事務,都會創建新的事務,並且調用方異常不會影響 REQUIRES_NEW事務
NOT_SUPPORTED 無論是否調用方是否存在事務,都是以非事務的方式執行,出現異常也會回滾
NEVER 不用事務,存在事務就報錯,和 MANDATORY 相反
NESTED 嵌套事務,新建一個子事務,事務執行相互獨立,如果調用方出現異常,直接回滾

測試源碼

參考

如果覺得文章對你有幫助的話,請點個推薦吧!


免責聲明!

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



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