Spring事务传播机制的理解与代码验证

环境:

      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)
);

 请求路由

扫描二维码关注公众号,回复: 9425654 查看本文章
@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的源码上看,也确实如此。

发布了15 篇原创文章 · 获赞 14 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/master336/article/details/104521820