環境:
Springboot:2.2.3.RELEASE
Spring:5.2.3.RELEASE
事務傳播行爲枚舉參見Spring源碼:org.springframework.transaction.annotation.Propagation
先來了解一下Spring事務的傳播機制(由於英語不好,直接google翻譯了)
簡單終結一下:
枚舉 | 翻譯 |
REQUIRED |
支持當前事務,如果不存在則創建新事務(默認)
|
SUPPORTS | 支持當前事務,如果不存在則非事務執行 |
MANDATORY | 支持當前事務,如果不存在則拋出異常。 |
REQUIRES_NEW | 創建一個新事務,並暫停當前事務(如果存在) |
NOT_SUPPORTED | 非事務執行,如果存在當前事務,則掛起當前事務 |
NEVER | 非事務執行,如果存在事務,則引發異常 |
NESTED | 如果當前事務存在,則在嵌套事務中執行 |
接下來進行代碼驗證(以下的代碼實例會最終打包放到付件中)
準備工作:
數據庫(每次驗證前均已清空數據庫):
create table demo
(
id int auto_increment
primary key,
uid varchar(30) not null,
remark varchar(30) null,
inputdate datetime not null,
constraint demo_uid_uindex
unique (uid)
);
請求路由
@RequestMapping("/test/{trans}")
public String test(@PathVariable("trans") String trans){
if("never".equals(trans)){
transService.neverTrans();
}else if("mandatory".equals(trans)){
transService.mandatoryTrans();
}else if("required".equals(trans)){
transService.requiredTrans();
}else if("trans".equals(trans)){
transService.trans();
}else if("supports".equals(trans)){
transService.supportsTrans();
}else if("supportswithtrans".equals(trans)){
transService.supportsWithTrans();
}else if("requeirednew".equals(trans)){
transService.requiredNewTrans();
}else if("notsupported".equals(trans)){
transService.notsupportedTrans();
}else if("netsted".equals(trans)){
transService.nestedTrans();
}else if("netstedwithserviceerror".equals(trans)){
transService.nestedWithTransServiceError();
}else if("netstedwithdaoerror".equals(trans)){
transService.nestedWithTransDaoError();
}
return "ok";
}
Mapper.xml
<insert id="insertDemoWithMandatory">
insert into demo (uid, remark, inputdate) value (#{uid,jdbcType=VARCHAR},#{remark,jdbcType=VARCHAR},CURRENT_TIMESTAMP);
</insert>
<insert id="insertDemoWithNever">
insert into demo (uid, remark, inputdate) value (#{uid,jdbcType=VARCHAR},#{remark,jdbcType=VARCHAR},CURRENT_TIMESTAMP);
</insert>
<insert id="insertDemo">
insert into demo (uid, remark, inputdate) value (#{uid,jdbcType=VARCHAR},#{remark,jdbcType=VARCHAR},CURRENT_TIMESTAMP);
</insert>
<insert id="insertDemoError">
insert into demo (uid, remark, inputdate) value (#{uid1,jdbcType=VARCHAR},#{remark,jdbcType=VARCHAR},CURRENT_TIMESTAMP);
</insert>
<insert id="insertDemoWithSupportnew">
insert into demo (uid, remark, inputdate) value (#{uid,jdbcType=VARCHAR},#{remark,jdbcType=VARCHAR},CURRENT_TIMESTAMP);
</insert>
<insert id="insertDemoWithNested">
insert into demo (uid, remark, inputdate) value (#{uid,jdbcType=VARCHAR},#{remark,jdbcType=VARCHAR},CURRENT_TIMESTAMP);
</insert>
<insert id="insertDemoWithNestedOnError">
insert into demo (uid, remark, inputdate) value (#{uid1,jdbcType=VARCHAR},#{remark,jdbcType=VARCHAR},CURRENT_TIMESTAMP);
</insert>
DAO:
@Transactional(propagation = Propagation.NEVER)
int insertDemoWithNever(@Param("uid")String uid,@Param("remark") String remark);
@Transactional(propagation = Propagation.MANDATORY)
int insertDemoWithMandatory(@Param("uid")String uid, @Param("remark") String remark);
@Transactional(propagation = Propagation.REQUIRES_NEW)
int insertDemoWithSupportnew(@Param("uid")String uid, @Param("remark") String remark);
int insertDemo(@Param("uid")String uid, @Param("remark") String remark);
int insertDemoError(@Param("uid")String uid, @Param("remark") String remark);
@Transactional(propagation = Propagation.NESTED)
int insertDemoWithNested(@Param("uid")String uid, @Param("remark") String remark);
@Transactional(propagation = Propagation.NESTED)
int insertDemoWithNestedOnError(@Param("uid")String uid, @Param("remark") String remark);
-
0. NONE 沒有事務
驗證思路:準備兩條入庫的SQL,其中一條報錯,因爲沒有事務,會導致數據不一致性
路徑:/test/trans
TransService:
/**
* 不帶事務的 遇到錯誤不會回滾,會導致數據不一致(部分寫入成功,部分寫入失敗)
* @return
*/
@Override
public boolean trans() {
demoMapper.insertDemo("uid1","transService");
demoMapper.insertDemoError("uid2","transService1");
return false;
}
分析:
demoMapper.insertDemoError("uid2","transService1"); 因SQL中賦值的時候有錯誤,導致綁定參數的時候異常,但是因爲這兩個方法均沒有開啓事務,異常發生時,demoMapper.insertDemo("uid1","transService"); 已經執行完畢,導致數據不一致。
執行完畢後數據庫:
總結:沒有事務容易造成數據的不一致性。
-
1. REQUIRED 支持當前事務,如果不存在則創建新事務
驗證思路:準備兩條入庫的SQL,其中一條報錯,開啓事務,報錯會導致事物回滾
路徑:/test/required
TransService:
/**
* 該方法在事務裏執行,如果當前沒有事務開啓一個事務(默認沒有事務,見this.trans())
* @return
*/
@Override
@Transactional(propagation = Propagation.REQUIRED)
public boolean requiredTrans() {
demoMapper.insertDemo("uid1","transService");
demoMapper.insertDemoError("uid2","transService1");
return false;
}
分析:
與沒有事務相比,該方法前聲明瞭事務傳播行爲REQUIRED,執行到 demoMapper.insertDemoError("uid2","transService1");時報錯,導致事務回滾,數據庫數據庫沒有保存任何信息
總結:REQUIRED會開啓事務,且發生異常時,事務會進行回滾,數據一致性的到保證。
執行完畢後數據庫:
-
2. SUPPORTS 支持當前事務,如果不存在則非事務執行
驗證思路:分別在有/沒有事務的方法裏,調用另一個事務傳播行爲SUPPORTS的方法
路徑:
1. 當前沒有事務:/test/supports
2. 當前有事務:/test/supportswithtrans
TransService:
/**
* 當前沒有事物,demoService.supportsTrans()的也不會有事務,會插入一條數據,造成數據不一致
* @return
*/
@Override
public boolean supportsTrans() {
demoService.supportsTrans();
return false;
}
/**
* 當前有事物,demoService.supportsTrans()的也會有事務,不會造成數據不一致
* @return
*/
@Transactional(propagation = Propagation.REQUIRED)
@Override
public boolean supportsWithTrans() {
demoService.supportsTrans();
return false;
}
DemoService
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public boolean supportsTrans() {
demoMapper.insertDemo("uid1","demoService");
demoMapper.insertDemoError("uid2","demoService1");
return false;
}
分析:
supportsTrans()未聲明事務,demoService.supportsTrans();以無事務的行爲執行,Mapper執行時遇到錯誤不會回滾。
supportsWithTrans()聲明事務,demoService.supportsTrans();以有事務的行爲執行,Mapper執行時遇到錯誤會回滾。
總結:
SUPPORTS 不會主動創建事務,是否支持事務來自於其調用方是否開啓了事務。
執行完畢後數據庫:
-
3. MANDATORY 支持當前事務,如果不存在則拋出異常
驗證思路:分別在沒有事務的方法裏,調用另一個事務傳播行爲MANDATORY的方法
路徑:/test/mandatory
TransService:
/**
* 當前方法未開啓事務,調用的demoMapper.insertDemoWithMandatory(String,String) 必須在事務中執行,會報錯
* @return
*/
@Override
public boolean mandatoryTrans() {
System.out.println("service[none trans],dao[propagration=mandatory]");
demoMapper.insertDemoWithMandatory("uid","mandatorytest");
return false;
}
分析:
mandatoryTrans()未聲明事務,demoMapper.insertDemoWithMandatory();聲明瞭行爲爲MANDATORY的事務,insertDemoWithMandatory會因爲沒有事務而報錯
IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
總結:
MANDATORY不會主動創建事務,但是其要求當前必須有事務
執行完畢後數據庫:
未觸發
-
4. REQUIRES_NEW 創建一個新事務,並暫停當前事務(如果存在)
驗證思路:在有事務的方法裏,調用另一個事務傳播行爲REQUIRED_NEW的方法,且在執行完後執行一條錯誤的代碼,導致當前事務回滾
路徑:/test/requeirednew
TransService:
/**
* 當前有事務,demoMapper.insertDemoWithSupportnew(String,String)開始了自己的事務,
* 當前事務回滾不會造成demoMapper.insertDemoWithRequiresNew事務的回滾
* @return
*/
@Override
@Transactional(propagation = Propagation.REQUIRED)
public boolean requiredNewTrans() {
demoMapper.insertDemo("uid1","transService1");
demoMapper.insertDemoWithRequiresNew("uid2","transService1");
demoMapper.insertDemoError("uid3","transService1");
return false;
}
分析:
執行完畢demoMapper.insertDemoWithRequiresNew("uid2","transService1");後繼續執行requiredNewTrans()引發異常,會導致requiredNewTrans()的事務回滾,但並不影響insertDemoWithRequiresNew的事務,insertDemoWithRequiresNew會正常執行
總結:
REQUIRES_NEW開啓了的新事務並不是加入到當前的事務中來,當前事務的異常並不會引發REQUIRES_NEW方法的事務回滾。
執行完畢後數據庫:
-
5. NOT_SUPPORTED 非事務執行,如果存在當前事務,則掛起當前事務
驗證思路:在有事務的方法裏,調用另一個事務傳播行爲NOT_SUPPORTED的方法, 且NOT_SUPPORTED的方法會執行異常,但會被捕獲並處理。
路徑:/test/notsupported
TransService:
/**
* 當前有事務,demoMapper.insertDemoWithSupportnew(String,String)開始了自己的事務,
* * 當前事務回滾不會造成demoMapper.insertDemoWithSupportnew事務的回滾
* @return
*/
@Override
@Transactional(propagation = Propagation.REQUIRED)
public boolean notsupportedTrans() {
demoMapper.insertDemo("uid1","transService1");
try {
demoService.insertDemoWithnotSupported();
}catch (Exception e){
System.out.println(" demoService.insertDemoWithnotSupported執行報錯,其數據不一致");
}
demoMapper.insertDemo("uid2","transService1");
return false;
}
DemoService
@Override
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public boolean insertDemoWithnotSupported() {
demoMapper.insertDemo("uid1-1","demoService");
demoMapper.insertDemoError("uid2-1","demoService1");
return false;
}
分析:
notsupportedTrans()執行過程中未發生異常,事務正常提交,insertDemoWithnotSupported執行到demoMapper.insertDemoError("uid2-1","demoService1");會發生異常,當前DemoService沒有事務,第一條會正常插入,數據庫中應該爲三條(2+1)。
總結:
NOT_SUPPORTED對當前的事務做了一個掛起後執行自己的部分,並且自己這部分時以無事務的形式執行,其異常並不會回滾,會導致數據不一致。NOT_SUPPORTED並不會影響掛起的事務的繼續執行及提交或回滾。
執行完畢後數據庫:
-
6. NEVER 非事務執行,如果存在事務,則引發異常
驗證思路:在有事務的方法裏,調用另一個事務傳播行爲NEVER的方法
路徑:/test/never
TransService:
/**
* 當前方法開啓事務,調用的demoMapper.insertDemoWithNever(String,String) 不允許事務,會報錯
* @return
*/
@Override
@Transactional(propagation = Propagation.REQUIRED)
public boolean neverTrans() {
System.out.println("service[propagation=requeired],dao[propagration=never]");
demoMapper.insertDemoWithNever("uid","nevertest");
return false;
}
分析:
neverTrans()開啓了當前事務,調用不支持的事務方法時,觸發了異常:
IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'
總結:
NEVER並不會掛起當前事務,而是在當前存在事務時直接異常,強制無事務。
執行完畢後數據庫:
未觸發
-
7. NESTED 如果當前事務存在,則在嵌套事務中執行
驗證思路:step1. 在無事務的方法裏,調用另一個事務傳播行爲NESTED的方法,同時NESTED的方法會報錯,以此來檢查嵌套事務是否會影響無事務方法的開啓。
step2. 在有事務的方法裏,調用另一個事務傳播行爲NESTED的方法,同時NESTED的方法會報錯,以此來檢查嵌套事務是否會影響當前事務方法的提交。
step3. 在有事務的方法裏,調用另一個事務傳播行爲NESTED的方法,當前事務會報錯,以此來檢查是否會影響嵌套事務提交。
路徑:
1.當前無事務 /test/netsted
2. 當前有事務,嵌套事務異常:/test/netstedwithdaoerror
3. 當前有事務,且有異常:/test/netstedwithserviceerror
TransService:
/**
* 當前沒有事務,demoService.insertDemoWithNested會開啓事務,其失敗不會造成其本身的數據不一致,
* 但不會影響當前事務的特性(當前異常會因爲沒有事務而引發數據不一致)
* @return
*/
@Override
public boolean nestedTrans() {
demoMapper.insertDemo("uid1","transService1");
try {
demoService.insertDemoWithNested();
}catch (Exception e){
System.out.println(" demoService.insertDemoWithNested,其數據一致");
}
demoMapper.insertDemoError("uid2","transService1");
return false;
}
/**
* 當前有事務,demoMapper.insertDemoWithNested會開啓嵌套事務,其事務的提交和回滾依賴當前事務,
* 當前事務失敗,其事務不會被提交
* @return
*/
@Override
@Transactional(propagation = Propagation.REQUIRED)
public boolean nestedWithTransServiceError() {
demoMapper.insertDemo("uid1","transService1");
try {
demoMapper.insertDemoWithNested("uid-0","transService");
}catch (Exception e){
System.out.println(" demoService.insertDemoWithNested,其數據一致");
}
demoMapper.insertDemoError("uid2","transService1");
return false;
}
/**
* 當前有事務,demoMapper.insertDemoWithNested會開啓嵌套事務,其事務的提交和回滾依賴當前事務,
* 當前事務成功,其事務將被提交,但其失敗不會影響當前事務
* @return
*/
@Override
@Transactional(propagation = Propagation.REQUIRED)
public boolean nestedWithTransDaoError() {
demoMapper.insertDemo("uid1","transService1");
try {
demoMapper.insertDemoWithNestedOnError("uid-0","transService");
}catch (Exception e){
System.out.println(" demoService.insertDemoWithNestedOnError");
}
return false;
}
DemoService
@Override
@Transactional(propagation = Propagation.NESTED)
public boolean insertDemoWithNested() {
demoMapper.insertDemo("uid1-1","demoService");
demoMapper.insertDemoError("uid2-1","demoService1");
return false;
}
分析:
當前無事務的情況:嵌套事務遇到錯誤時是回滾,對當前調用方沒有影響。
當前有事務,嵌套事務異常情況:當前事務正常提交,嵌套事務因爲異常並未成功保存數據。
當前有事務有異常,嵌套事務無異常情況:嵌套事務未提交成功,當前是一回滾。
總結:
通過三組對比發現,嵌套事務的在當前事務失敗的時候不會進行提交,但當前正常提交時,嵌套事務是能正常提交和回滾的,也就是嵌套事務的提交取決於當前事務的執行情況。
特別注意:NESTED傳播行爲在有些地方解釋爲不通的廠商實現機制不一樣,用之前需要再充分了解一下,從Spring的源碼上看,也確實如此。