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_REQUIRED
和PROPAGATION_REQUIRES_NEW
及PROPAGATION_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_NEW
和method_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作為巢狀事務自己回滾了,不影響父事務提交