环境:
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的源码上看,也确实如此。