1. 程式人生 > 其它 >Spring事務傳播行為

Spring事務傳播行為

Spring宣告事務事務傳播行為

名詞解釋

事務傳播行為
所謂事務的傳播行為是指,如果在開始當前事務之前,一個事務上下文已經存在,此時有若干選項可以指定一個事務性方法的執行行為。在TransactionDefinition定義中包括瞭如下幾個表示傳播行為的常量:

  • TransactionDefinition.PROPAGATION_REQUIRED:如果當前存在事務,則加入該事務;如果當前沒有事務,則建立一個新的事務。這是預設值。
  • TransactionDefinition.PROPAGATION_REQUIRES_NEW:建立一個新的事務,如果當前存在事務,則把當前事務掛起。
  • TransactionDefinition.PROPAGATION_SUPPORTS:如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續執行。
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事務方式執行,如果當前存在事務,則把當前事務掛起。
  • TransactionDefinition.PROPAGATION_NEVER:以非事務方式執行,如果當前存在事務,則丟擲異常。
  • TransactionDefinition.PROPAGATION_MANDATORY:如果當前存在事務,則加入該事務;如果當前沒有事務,則丟擲異常。
  • TransactionDefinition.PROPAGATION_NESTED:如果當前存在事務,則建立一個事務作為當前事務的巢狀事務來執行;如果當前沒有事務,則該取值等價於TransactionDefinition.PROPAGATION_REQUIRED。

區別解釋

上文可以看出PROPAGATION_SUPPORTS,PROPAGATION_NOT_SUPPORTED,PROPAGATION_NEVER,PROPAGATION_MANDATORY解釋比較明確,一般不會出現歧義
重點解釋下PROPAGATION_REQUIREDPROPAGATION_REQUIRES_NEWPROPAGATION_NESTED的區別.
為了解釋方便,假設一下程式碼為場景,該案例只是表示呼叫場景,因為缺乏必要的Spring註解和呼叫方式的不正確實際事務不會生效,具體使用案例參照下文案例.

public void methodA(){
  methodB();
  // do something
}

public void methodB(){
  // do something
}

方法A呼叫方,方法B被呼叫方

PROPAGATION_REQUIRED

Spring預設的事務傳播行為,如果呼叫方A有事務,被呼叫方B就加入這個事務,如果呼叫方A沒有事務,被呼叫方B就新建一個事務.如果加入的話,呼叫方A被呼叫方B是在完全相同的一個事務中,共同回滾,提交.

PROPAGATION_REQUIRES_NEW

無論當前呼叫方A是否存在事務,被呼叫方B都新建一個事務,如果呼叫方A存在事務,則掛起.呼叫方A被呼叫方B是完全不同的兩個事務.兩個事務分別回滾,提交

PROPAGATION_NESTED

如果呼叫方A存在事務,被呼叫方B建立一個巢狀事務來執行,如果呼叫方A不存在事務,被呼叫方B新建一個事務,所謂巢狀執行可以理解為,呼叫方A回滾會攜帶被呼叫方B一起回滾,被呼叫方B回滾不會影響呼叫方A.
--呼叫方A開始(設定savePointA)
----被呼叫方B開始(設定savePointB)

----被呼叫方B結束(失敗則回滾到savePointB)
--呼叫方A結束(成功則提交,失敗則回滾到savePointA)
可以看出,
B想提交成功需要兩個條件,B成功,A也成功
B想要回滾,A或者B有一個異常就可以了,
A提交只需要A中成功就可以(B丟擲的異常需要處理),A異常的時候會共同回滾
A異常會回滾到savePointA,B異常只回滾到savePointB
上文只是本人的理解,Spring真正的實現方式是怎樣沒有深入瞭解,以後有機會會補上,有懂的請不吝賜教.

程式碼驗證(舉例)

上面解釋也許只是對著名詞解釋假設的,具體是不是這樣還需要驗證一番

@Service
public class StudentServiceImpl implements StudentService {
    @Resource
    StudentMapper studentMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void methodFather() {
        int insert = studentMapper.insert(Student.builder().name("父方法先").build());
        try {
            ((StudentServiceImpl) AopContext.currentProxy()).method_NESTED();
        } catch (Exception e) {

        }
        try {
            ((StudentServiceImpl) AopContext.currentProxy()).method_REQUIRES_NEW();
        } catch (Exception e) {

        }
        try {
            ((StudentServiceImpl) AopContext.currentProxy()).method_REQUIRED();
        } catch (Exception e) {

        }
        int insert2 = studentMapper.insert(Student.builder().name("父方法後").build());
    }

    @Transactional(rollbackFor = Exception.class,propagation = Propagation.NESTED)
    public void method_NESTED() {
        int insert = studentMapper.insert(Student.builder().name("子方法NESTED").build());
    }

    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
    public void method_REQUIRES_NEW() {
        int insert = studentMapper.insert(Student.builder().name("子方法REQUIRES_NEW").build());
    }

    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    public void method_REQUIRED() {
        int insert = studentMapper.insert(Student.builder().name("子方法REQUIRED").build());
    }
}

上面的程式碼可以看出,呼叫方methodFather分別呼叫了method_NESTED,method_REQUIRES_NEWmethod_REQUIRED三個唄呼叫方,呼叫方法的地方都分別使用了trycatch語句,保證子方法的異常不會影響到父方法.

正常執行

Creating a new SqlSession
Registering transaction synchronization for SqlSession 
**註冊父事務a1b7bb8**
[org.apache.ibatis.session.defaults.DefaultSqlSession@a1b7bb8]
JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@192cd917] will be managed by Spring
==>  Preparing: INSERT INTO student ( name ) VALUES ( ? ) 
==> Parameters: 父方法先(String)
<==    Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a1b7bb8]
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a1b7bb8] from current transaction
==>  Preparing: INSERT INTO student ( name ) VALUES ( ? ) 
==> Parameters: 子方法NESTED(String)
<==    Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a1b7bb8]
Transaction synchronization suspending SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a1b7bb8]
Creating a new SqlSession
**註冊子事務REQUIRES_NEW a1b7bb8**
Registering transaction synchronization for SqlSession 
[org.apache.ibatis.session.defaults.DefaultSqlSession@6d69a313]
JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@16831f94] will be managed by Spring
==>  Preparing: INSERT INTO student ( name ) VALUES ( ? ) 
==> Parameters: 子方法REQUIRES_NEW(String)
<==    Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6d69a313]
Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6d69a313]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6d69a313]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6d69a313]
Transaction synchronization resuming SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a1b7bb8]
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a1b7bb8] from current transaction
==>  Preparing: INSERT INTO student ( name ) VALUES ( ? ) 
==> Parameters: 子方法REQUIRED(String)
<==    Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a1b7bb8]
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a1b7bb8] from current transaction
==>  Preparing: INSERT INTO student ( name ) VALUES ( ? ) 
==> Parameters: 父方法後(String)
<==    Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a1b7bb8]
Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a1b7bb8]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a1b7bb8]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a1b7bb8]

可以看到只有REQUIRES_NEW新建了一個事務,其他都是使用的父事務

父事務異常

將父方法methodFather修改為如下

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void methodFather() {
        int insert = studentMapper.insert(Student.builder().name("父方法先").build());
        try {
            ((StudentServiceImpl) AopContext.currentProxy()).method_NESTED();
        } catch (Exception e) {

        }
        try {
            ((StudentServiceImpl) AopContext.currentProxy()).method_REQUIRES_NEW();
        } catch (Exception e) {

        }
        try {
            ((StudentServiceImpl) AopContext.currentProxy()).method_REQUIRED();
        } catch (Exception e) {

        }
        int insert2 = studentMapper.insert(Student.builder().name("父方法後").build());
        // 父事務異常
        int i = 1 / 0;
    }

執行結果

父事務異常,回滾,加入父事務的REQUIRED和嵌入的NESTED都回滾了,只有開啟新事務的REQUIRES_NEW不受影響,成功提交了

REQUIRED子方法異常

去除父方法異常程式碼
修改method_REQUIRED程式碼如下

    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    public void method_REQUIRED() {
        int insert = studentMapper.insert(Student.builder().name("子方法REQUIRED").build());
        //REQUIRED異常測試
        int i = 1 / 0;
    }


結果與父方法異常相同,原因是REQUIRED是加入父事務,它回滾等於父事務回滾

REQUIRES_NEW異常

相同方法,去除REQUIRED中的異常
REQUIRES_NEW子方法加入異常

    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
    public void method_REQUIRES_NEW() {
        int insert = studentMapper.insert(Student.builder().name("子方法REQUIRES_NEW").build());
        //REQUIRES_NEW異常測試
        int i = 1 / 0;
    }


除了REQUIRES_NEW異常自己回滾了,其他成功提交

NESTED異常測試

相同方法,去除其他異常
method_NESTED中加入異常

    @Transactional(rollbackFor = Exception.class,propagation = Propagation.NESTED)
    public void method_NESTED() {
        int insert = studentMapper.insert(Student.builder().name("子方法NESTED").build());
        //NESTED異常測試
        int i = 1 / 0;
    }

因為測試使用的是同一張表,該異常出現的時候會進行鎖表,造成REQUIRES_NEW的提交超時,此處將method_NESTED方法呼叫移到最後執行

可以看到NESTED作為巢狀事務自己回滾了,不影響父事務提交