Spring事务传播行为Propagation及原理

1. 事务的常见传播行为

  • 事务作为一个jdbc的执行单元,要么一系列操作全部执行成功,要么都执行不成功,读过Spring源码的同学,可能会知道spring的事务时通过数据库连接来实现的。当前线程保存了一个map,key是数据源,value是数据库链接connection,具体如下。同时,下面针对常见的事务传播行为进行介绍:
private static final ThreadLocal<Map<object,object>> resources=new NamedThreadLocal<>("Transactional resources");
  • 说明:我们这里提到的加入当前事务,指的是底层使用同一个connection,但是事务的状态对象可以重新创建的,并不影响。文章提到的当前只存在一个事务,表示的是共用底层的一个connectioin,而不在乎创建了多少个事务状态(TransactionStatus)。

1.1 PROPAGATION_REQUIRED(默认级别)

  • 说明:如果当前已经存在事务,那么就加入该事务。如果不存在事务,创建一个事务。该为默认的传播属性值。
@Transactional
public void service(){
    serviceA();
    serviceB();
}

// 注意serviceA和serviceB需要在不同的class中,否则service调用的都是被代理对象的方法,serviceA和serviceB事务是不生效的。
@Transactional
public void serviceA(){
    // doSomething
}

@Transactional
public void serviceB(){
    // doSomething
}
  • serviceA和serviceB都声明了事务,默认情况下PROPAGATION_REUQIRED,整个service调用过程中,只存在一个共享的事务,当任何其中一个service发生异常时,所有操作回滚。

1.2 PROPAGATION_SUPPORTS

  • 说明:如果当前已经存在事务,那么加入该事务,否则创建一个所谓的空事务(可以理解没有事务运行)
  • 情况1:serviceA执行时当前没有事务,所以service中抛出的异常不会导致serviceA回滚
public void service(){
    serviceA();
    throw new RuntimeException();
}

@Transactional(propagation=Propagation.SUPPORTS)
public void serviceA(){
    // doSomething
}
  • 情况2:由于serviceA运行时没有实物,这个时候如果底层数据源defaultAutoCommit=true,那么sql1是生效的,如果defaultAutoCommit=false,那么sql1无效。如果service有@Transactional注解,serviceA共用service的事务(不再依赖底层defaultAutoCommit),此时serviceA全部回滚。
public void service(){
    serviceA();
}

@Transactional(propagation=Propagetion.SUPPORTS)
public void serviceA(){
    doSql1();
    1/0;// 抛异常
    doSql2();
}

1.3 PROPAGATION_MAADATORY

  • 说明:当前必须存在一个事务,否则抛出异常(强制)。
  • 情况1:这种情况执行service会抛出异常后,如果defaultAutoCommit=true,则serviceB是不会回滚的。若defaultAutoCommit=false,则serviceB执行无效。
public void service(){
    serviceB();
    serviceA();
}

public void serviceB(){
    doSql();
}

@Transactional(propagation=Propagation.MANDATORY)
public void serviceA(){
    doSql();
}

1.4 PROPAGATION_REQUIRES_NEW

  • 说明:如果当前存在事务,先把当前事务相关内容封装到一个实体,然后重新创建一个新事务,接受这个实体为参数,用于事务的恢复。即:暂停当前事务(当前无事务则不需要),创建一个新事务。针对这种情况,两个事务没有依赖关系,可以实现新事务回滚了,但外部事务继续执行。
  • 情况1:当调用service接口时,由于serviceA使用的是REUQIRES_NEW,它会创建一个新的事务,但由于serviceA抛出了运行时异常,导致整个serviceA整个被回滚了。而在service方法中捕获了异常,所以serviceB是正常提交的。注意:service中的try-catch代码是必须的,否则service也会抛出异常,导致serviceB也被回滚。
@Transactional
public void service(){
    serviceB();
    try{
        serviceA();
    }catch(Exception e){
        
    }
}

public void serviceB(){
    doSql();
}

@Transactional(propagation=Propagation.REUQIRES_NEW)
public void serviceA(){
    doSql1();
    1/0; // 抛异常
    doSql2();
}
  • 原理:整个REQUIRES_NEW的实现原理如下:
    • 1.创建事务状态对象,获取一个新的连接,重置连接的 autoCommit,fetchSize,timeout等属性
    • 2.把连接绑定到ThreadLocal变量
    • 3.挂起当前事务,把当前事务状态对象,连接等信息封装成一SuspendedResources对象,可用于恢复
    • 4.创建新的事务状态对象,重新获取新的连接,重置新连接的 autoCommit,fetchSize,timeout等属性,同时,保存SuspendedResources对象,用于事务的恢复,把新的连接绑定到ThreadLocal变量(覆盖操作)
    • 5.捕获到异常,回滚ThreadLocal中的连接,恢复连接参数,关闭连接,恢复SuspendedResources
    • 6.提交ThreadLocal变量中的连接(导致serviceB被提交),还原连接参数,关闭连接,连接归还数据源所以程序执行的结果就是serviceA被回滚了,serviceB成功提交了。

1.5 Propagation.NOT_SUPPORT

  • 说明:如果当前存在事务,挂起当前事务。然后新的方法在没有事物的环境下执行,没有spring事务的环境下,sql的提交完全依赖于底层defaultAutoCommit属性值。
  • 情况1:当调用时service方法时,执行到serviceA方法中的1/0处,抛出异常。由于serviceA处于无事务环境下,所以doSql1是否生效取决于defaultAutoCommit的值,如果defaultAutoCommit=true吗,doSql1是生效的。但是由于service抛出了异常,所以serviceB也会被回滚。
@Transacational
public void service(){
    serviceB();
    serviceA();
}

public void serviceB(){}

@Transacational(propagation=Propagation.NOT_SUPPORT)
public void serviceA(){
    doSql1();
    1/0;// 抛出异常
    doSql2();
}

1.6 PROPAGATION_NEVER

  • 说明:如果当前存在事务,则抛出异常,否则在无事务环境上执行。
  • 情况1:当调用service时,若defaultAutoCommit=true,则serviceB方法即serviceA中的doSql都会生效。
public void service(){
    serviceB();
    serviceA();
}

public void serviceB(){}

@Transacational(propagation=Propagation.NEVER)
public void serviceA(){
    doSql1();
    1/0;// 抛出异常
    doSql2();
}

1.7 PROPAGATION_NESTED

  • 说明:如果当前存在事务,则使用SavePoint技术把当前事务状态进行保存,然后底层共用一个连接,当NESTED内部出错时,自行回滚到SavePoint这个状态,只要外部捕获到了异常,就可以进行进行外部的事务提交,而不会收到内嵌业务干扰。但是,如果外部事务抛出了异常,则整个大事务(外部事物+内嵌事务)都会回滚。
  • 注意:spring配置事务管理器需要手动指定nestedTransactionAllowed=true。
 <bean id="dataTransactionManager"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataDataSource" />
        <property name="nestedTransactionAllowed" value="true" />
</bean>
  • 情况1:seviceA是内嵌事务,内部抛出异常时,则整个serviceA都进行了回滚。由于service捕获了异常,所以serviceB是可以正常提交的。
@Transactional
public void service(){
    serviceB();
    try{
        serviceA();
    }catch(Exception e){
        
    }
}

public void serviceB(){
    doSql();
}

@Transactional(propagation=Propagation.NESTED)
public void serviceA(){
    doSql1();
    1/0; // 抛异常
    doSql2();
}
  • 原理:
      1. 创建事务状态对象,获取一个新的连接,重置连接的 autoCommit,fetchSize,timeout等属性。
    • 2.把连接绑定到ThreadLocal变量
    • 3.标记使用当前事务状态对象,获取ThreadLocal连接对象,保存当前连接的SavePoint,用于异常恢复,此时的SavePoint就是执行完serviceB后的状态。
    • 4.捕获到异常,使用步骤3中的SavePoint进行事务回滚,也就是把状态回滚到执行serviceA后的状态,serviceB方法所有执行不生效。
    • 5.获取ThreadLocal中的连接对象,提交事务,恢复连接属性,关闭连接。
  • 情况2:运行service抛出异常,导致整个service方法都回滚(注意:这里跟PROPAGATION_REUQIRES_NEW不一样的地方,NESTED方式下的内嵌业务会受到外部事务的异常回滚而回滚,而REUQIRES_NEW则不会)。
@Transactional
public void service(){
    serviceA();
    serviceB();
    1/0;// 抛出异常
}

public void serviceB(){
    doSql();
}

@Transactional(propagation=Propagation.NESTED)
public void serviceA(){
   doSql();
}

2.参考文档

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值