Spring之事務傳播行爲

一、概念

首先簡單瞭解一下Spring中事務傳播行爲是什麼?聽起來很高端,但是真正用起來的時候,稍有不慎,就會讓自己陷入困境之中,所以在使用之前,我們必須要十分耐心認真的學習它。
從名字理解起來,事務傳播行爲,既然爲傳播就肯定發生在兩個實體之間,否則單個實體又如何發生行爲呢。通俗點講就是“一個巴掌拍不響”。下面進入正規話題。

  • 事務傳播行爲主要用來描述由某一個事務傳播行爲修飾的方法被嵌套進另一個方法的事務中,該事務如何傳播。這個概述可能不好理解,換句話就是當一個事務方法被另一個事務方法調用時,這個事務方法應該如何進行。

下面用代碼+文字說明解釋上面的概念。

@Transaction(Propagation=XXX)
public void methodA(){
    methodB();
    //doSomething
 }
 
 @Transaction(Propagation=XXX)
 public void methodB(){
    //doSomething
 }

methodA事務方法調用methodB事務方法時,methodB是繼續在調用者methodA的事務中運行呢,還是爲自己開啓一個新事務運行,這就是由methodB的事務傳播行爲決定的。
注意:methodA和methodB都加了事務。methodA()也可以不用開啓事務,某一個事務傳播行爲修飾的方法並不是必須要在開啓事務的外圍方法中調用

二、Spring中七種事務傳播行爲

通過上面僞代碼加文字解釋瞭解到事務傳播行爲的相關概念,下面就要學習事務傳播行爲的類型和運行機制。

事務傳播行爲類型 解釋說明
Propagation_Required 表示被修飾的方法必須運行在事務中。如果當前方法沒有事務,則就新建一個事務;如果已經存在一個事務中,就加入到這個事務中。此類型是最常見的默認選擇
Propagation_Supports 表示被修飾的方法不需要事務上下文。如果當前方法存在事務,則支持當前事務執行;如果當前沒有事務,就以非事務方式執行。
Propagation_Mandatory 表示被修飾的方法必須在事務中運行。如果當前事務不存在,則會拋出一個異常。
Propagation_Required_New 表示被修飾的方法必須運行在它自己的事務中。一個新的事務會被啓動。如果調用者存在當前事務,則在該方法執行期間,當前事務會被掛起。
Propagation_Not_Supported 表示被修飾的方法不應該運行在事務中。如果調用者存在當前事務,則該方法運行期間,當前事務將被掛起。
Propagation_Never 表示被修飾的方法不應該運行事務上下文中。如果調用者或者該方法中存在一個事務正在運行,則會拋出異常。
Propagation_Nested 表示當前方法已經存在一個事務,那麼該方法將會在嵌套事務中運行。嵌套的事務可以獨立與當前事務進行單獨地提交或者回滾。如果當前事務不存在,那麼其行爲與Propagation_Required一樣。

驗證

Propagation_Required

  • 調用者方法不存在事務傳播行爲
  1. 調用者方法內部存在異常時,被調用者方法均存在事務,那麼結果如何呢?
	@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void insertStudent(StudentDo studentDo) {
        studentMapper.insertStudent(studentDo);
        System.out.println("----------------------->Student插入成功!");
    }

	@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void insertClass(ClassDo classDo) {
        classMapper.insertClass(classDo);
        System.out.println("----------------------->Class插入成功!");
    }

單元測試
@SpringBootTest
@RunWith(SpringRunner.class)
public class PropagationTest {

    private final static StudentDo studentDo = new StudentDo();

    private final static ClassDo classDo = new ClassDo();
    static {
        studentDo.setClassId(1);
        studentDo.setStudentName("student1");
        studentDo.setAddress("測試");

        classDo.setClassName("class_1");
        classDo.setClassNo("Class01");
    }
    @Autowired
    private StudentService studentService;

    @Autowired
    private ClassService classService;

    @Test
    public void insertTest() {
        studentService.insertStudent(studentDo);
        classService.insertClass(classDo);
        
    }
}

在這裏插入圖片描述
結果:兩條數據均被插入數據庫。由於外部方法並沒有開啓事務,所以內部方法均在自己的事務提交或者回滾,因此外部方法中存在異常,內部方法事務不會回滾。

  1. 被調用者均存在事務,而在被調用者中存在異常,那麼結果如何?
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void insertStudent(StudentDo studentDo) {
        studentMapper.insertStudent(studentDo);
        System.out.println("----------------------->Student插入成功!");
    }

 //此方法中拋出異常
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void insertClassByException(ClassDo classDo) throws CustomException {
        classMapper.insertClass(classDo);
        throw new CustomException();
    }

單元測試代碼
private final static StudentDo studentDo = new StudentDo();
    private final static ClassDo classDo = new ClassDo();
    static {
        studentDo.setClassId(2);
        studentDo.setStudentName("student2");
        studentDo.setAddress("測試2");

        classDo.setClassName("class_2");
        classDo.setClassNo("Class02");
    }
@Test
    public void insertExceptionTest() throws CustomException {
        studentService.insertStudent(studentDo);
        classService.insertClassByException(classDo);
    }

在這裏插入圖片描述
結果 第一數據成功插入,第二條數據因異常存在,事務回滾。內部方法均在各個的事務中運行,class事務回滾,student數據不會受到影響。
在這裏插入圖片描述
結合1和2我們可以得出結論1:通過這兩個方法我們證明了在外圍方法未開啓事務的情況下Propagation_Required修飾的內部方法會新開啓自己的事務,且開啓的事務相互獨立,互不干擾

  • 2.調用者開啓事務傳播行爲
內部方法同上	
//單元測試方法
	@Test
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void insertInnerExceptionThrowsTest() throws CustomException {
        studentService.insertStudent(studentDo);
        classService.insertClassByException(classDo);
    }

結果:內部方法雖然存在事務傳播行爲,但是外部方法也存在事務且使用Propagation.REQUIRED修飾,所有內部方法不會新建事務,直接運行在當前事務中,所以student、class均會被回滾。

  • 3.調用者開啓事務傳播行爲,但是捕獲內部方法異常
 /**
     * 內部方法發生異常情況,外部方法即使捕獲處理該異常,依然數據會被回滾
     */
    @Test
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void insertInnerExceptionTest() {
        studentService.insertStudent(studentDo);
        try {
            classService.insertClassByException(classDo);
        } catch (CustomException e) {
            e.printStackTrace();
        }
    }

結果:外圍方法開啓事務,內部方法加入外圍方法事務,內部方法拋出異常回滾,即使方法被catch不被外圍方法感知,整個事務依然回滾。同2一樣,調用者方法執行操作和被調用者中的方法操作結果均被回滾。

Propagation_Supports

	@Transactional(propagation = Propagation.SUPPORTS, rollbackFor = Exception.class)
    public void insertStudent(StudentDo studentDo) {
        studentMapper.insertStudent(studentDo);
        System.out.println("----------------------->Student插入成功!");
    }

	@Test
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void insertSupportsTest() {
        studentService.insertStudent(studentDo);
    }

解釋:如果單純的調用insertStudent()方法,則以非事務執行,即使後面存在異常情況,執行操作結果不會觸發事務回滾機制。當調用insertSupportsTest()方法時,該方法以REQUIRED修飾,則會新建一個事務,內部調用insertStudent()方法,所以insertStudent()會加入到當前事務中執行。

Propagation_Mandatory

@Transactional(propagation = Propagation.MANDATORY, rollbackFor = Exception.class)
    public void insertStudent(StudentDo studentDo) {
        studentMapper.insertStudent(studentDo);
        System.out.println("----------------------->Student插入成功!");
    }

	@Test
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void insertSupportsTest() {
        studentService.insertStudent(studentDo);
    }

在這裏插入圖片描述
解釋結果:MANDATORY表示被修飾的方法必須在事務中運行。當單獨調用insertStudent時,因爲當前沒有一個活動的事務,則會拋出異常throw new IllegalTransactionStateException(“Transaction propagation ‘mandatory’ but no existing transaction found”);當調用insertSupportsTest時,insertStudent則加入到insertSupportsTest的事務中,事務地執行。

Propagation_Required_New

表示被修飾的方法必須運行在它自己的事務中。一個新的事務會被啓動。如果調用者存在當前事務,則在該方法執行期間,當前事務會被掛起。

private final static StudentDo studentDo = new StudentDo();
private final static ClassDo classDo = new ClassDo();
    static {
        studentDo.setClassId(2);
        studentDo.setStudentName("requireNew");
        studentDo.setAddress("requireNew");
    }
	@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void insertStudent(StudentDo studentDo) {
        studentMapper.insertStudent(studentDo);
        System.out.println("----------------------->Student插入成功!");
    }
    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void insertClassByException(ClassDo classDo) throws CustomException {
        classMapper.insertClass(classDo);
        throw new CustomException();
    }
    
	@Test
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void insertInnerExceptionTest() {
        studentService.insertStudent(studentDo);
        try {
            classService.insertClassByException(classDo);
        } catch (CustomException e) {
            e.printStackTrace();
        }
    }

結果解析:insertStudent(),insertClassByException()方法執行時,外部方法事務被掛起,內部方法會新建事務,直至該方法執行結束,恢復外部方法事務執行。兩者之間事務存在隔離性,insertClassByException()方法遇到異常,觸發事務回滾機制,但insertStudent()執行結果並受到影響。
如圖所示:
在這裏插入圖片描述
在這裏插入圖片描述

Propagation_Not_Supported

表示被修飾的方法不應該運行在事務中。如果調用者存在當前事務,則該方法運行期間,當前事務將被掛起。

 private final static ClassDo classDo = new ClassDo();
    static {

        classDo.setClassName("notSupport");
        classDo.setClassNo("notSupport");
    }
	@Transactional(propagation = Propagation.NOT_SUPPORTED, rollbackFor = Exception.class)
    public void insertClassByException(ClassDo classDo) throws CustomException {
        classMapper.insertClass(classDo);
        throw new CustomException();
    }
	@Test
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void insertInnerExceptionTest() {
        try {
            classService.insertClassByException(classDo);
        } catch (CustomException e) {
            e.printStackTrace();
        }
    }

結果解釋:即使外部方法開啓事務,但是insertClassByException()執行,當前事務會掛起,not_support以非事務方式運行,所以即使遇到異常情況,執行結果也不會觸發回滾。
在這裏插入圖片描述

Propagation_Never

表示被修飾的方法不應該運行事務上下文中。如果調用者或者該方法中存在一個事務正在運行,則會拋出異常。

	@Transactional(propagation = Propagation.NEVER, rollbackFor = Exception.class)
    public void insertStudent(StudentDo studentDo) {
        studentMapper.insertStudent(studentDo);
        System.out.println("----------------------->Student插入成功!");
    }

 @Test
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void insertInnerExceptionTest() {
        studentService.insertStudent(studentDo);
    }

結果如圖:
在這裏插入圖片描述

Propagation_Nested

表示當前方法已經存在一個事務,那麼該方法將會在嵌套事務中運行。
嵌套的事務可以獨立與當前事務進行單獨地提交或者回滾。
如果當前事務不存在,那麼其行爲與Propagation_Required一樣。
嵌套事務的概念就是內層事務依賴於外層事務。外層事務失敗時,會回滾內層事務所做的動作。而內層事務操作失敗並不會引起外層事務的回滾。

  • 1.外部未開啓事務時,內部方法則新建事務執行
	private final static StudentDo studentDo = new StudentDo();
    private final static ClassDo classDo = new ClassDo();
    static {
        studentDo.setClassId(2);
        studentDo.setStudentName("NESTED");
        studentDo.setAddress("NESTED");

        classDo.setClassName("NESTED");
        classDo.setClassNo("NESTED");
    }
	@Test
    public void insertTest() {
        studentService.insertStudent(studentDo);
        classService.insertClass(classDo);
        throw new RuntimeException();

    }
@Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)
    public void insertStudent(StudentDo studentDo) {
        studentMapper.insertStudent(studentDo);
        System.out.println("----------------------->Student插入成功!");
    }
@Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)
    public void insertClass(ClassDo classDo) {
        classMapper.insertClass(classDo);
        System.out.println("----------------------->Class插入成功!");
    }

結果
在這裏插入圖片描述
在這裏插入圖片描述

  • 2.外部方法開啓事務:
  • 如果外部方法發生異常,則內部事務一起發生回滾操作;
  • 如果外部無異常情況,內部被調用方法存在異常情況,則內部方法獨立回滾(疑問點???我用以下實例驗證,但是外部方法也一樣被回滾了,請各位大佬給與解答);
//單測代碼
	private final static StudentDo studentDo = new StudentDo();
    private final static ClassDo classDo = new ClassDo();
    static {
        studentDo.setClassId(2);
        studentDo.setStudentName("NESTED_InnerException");
        studentDo.setAddress("NESTED_InnerException");

        classDo.setClassName("NESTED_InnerException");
        classDo.setClassNo("NESTED_InnerException");
    }
	@Test
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void insertInnerExceptionThrowsTest() throws CustomException {
        studentMapper.insertStudent(studentDo);
        classService.insertClassByException(classDo);
    }
//NESTED事務傳播行爲
	@Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)
    public void insertClassByException(ClassDo classDo) throws CustomException {
        classMapper.insertClass(classDo);
        throw new RuntimeException();
    }

源代碼傳送門:gitHub倉庫

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章