一、为什么会有传播机制

spring 对事务的控制,是使用 aop 切面实现的,我们不用关心事务的开始,提交 ,回滚,只需要在方法上加 @Transactional 注解,这时候就有问题了。

  • 场景一: serviceA 方法调用了 serviceB 方法,但两个方法都有事务,这个时候如果 serviceB 方法异常,是让 serviceB 方法提交,还是两个一起回滚。
  • 场景二:serviceA 方法调用了 serviceB 方法,但是只有 serviceA 方法加了事务,是否把 serviceB 也加入 serviceA 的事务,如果 serviceB 异常,是否回滚 serviceA 。
  • 场景三:serviceA 方法调用了 serviceB 方法,两者都有事务,serviceB 已经正常执行完,但 serviceA 异常,是否需要回滚 serviceB 的数据。

    二、什么是事务传播行为?

    事务传播行为(propagation behavior)指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何运行。
    例如:methodA方法调用methodB方法时,methodB是继续在调用者methodA的事务中运行呢,还是为自己开启一个新事务运行,这就是由methodB的事务传播行为决定的。
    methodA与methodB不在同一个类中。如果在同一个类,这样的写法,注解是不生效的
    因为spring事务只是对代理类调用的方法才有效,本方法内部直接调用相当于是使用的被代理对象进行调用,所以事务注解是不生效的。
    不生效的原因是AOP导致的,重新组建字节码的时候,没有办法再次判断嵌套里面的那个方法是否有注解,这是AOP相关问题

    1. public void methodA(){
    2. methodB();
    3. //doSomething
    4. }
    5. @Transaction(Propagation=XXX)
    6. public void methodB(){
    7. //doSomething
    8. }

    三、事务的7种传播行为

    Spring在TransactionDefinition接口中规定了7种类型的事务传播行为。
    事务传播行为是Spring框架独有的事务增强特性。
    7种:(required / supports / mandatory / requires_new / not supported / never / nested)

对事务的要求程度可以从大到小排序:mandatory / supports / required / requires_new / nested / not supported / never

1、PROPAGATION_REQUIRED

  • (required需要,没有新建,有加入)

如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。
可以把事务想像成一个胶囊,在这个场景下方法B用的是方法A产生的胶囊(事务)。
Spring的事务的传播行为 - 图1
举例有两个方法:

  1. @Transactional(propagation = Propagation.REQUIRED)
  2. public void methodA() {
  3. methodB();// do something
  4. }
  1. @Transactional(propagation = Propagation.REQUIRED)
  2. public void methodB() {
  3. // do something
  4. }

单独调用methodB方法时,因为当前上下文不存在事务,所以会开启一个新的事务。
调用methodA方法时,因为当前上下文不存在事务,所以会开启一个新的事务。当执行到methodB时,methodB发现当前上下文有事务,因此就加入到当前事务中来。

2、PROPAGATION_SUPPORTS

  • (supports支持,有则加入,没有就不管了,非事务运行)

如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。但是对于事务同步的事务管理器,PROPAGATION_SUPPORTS与不使用事务有少许不同。
举例有两个方法:

  1. @Transactional(propagation = Propagation.REQUIRED)
  2. public void methodA() {
  3. methodB();// do something
  4. }
  1. // 事务属性为SUPPORTS
  2. @Transactional(propagation = Propagation.SUPPORTS)
  3. public void methodB() {
  4. // do something
  5. }

单纯的调用methodB时,methodB方法是非事务的执行的。当调用methdA时,methodB则加入了methodA的事务中,事务地执行。

3、PROPAGATION_MANDATORY

  • (mandatory强制性,有则加入,没有异常)

如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。

  1. @Transactional(propagation = Propagation.REQUIRED)
  2. public void methodA() {
  3. methodB();// do something
  4. }
  1. // 事务属性为MANDATORY
  2. @Transactional(propagation = Propagation.MANDATORY)
  3. public void methodB() {
  4. // do something
  5. }

当单独调用methodB时,因为当前没有一个活动的事务,则会抛出异常throw new IllegalTransactionStateException(“Transaction propagation ‘mandatory’ but no existing transaction found”);当调用methodA时,methodB则加入到methodA的事务中,事务地执行。

4、PROPAGATION_REQUIRES_NEW

  • (requires_new需要新的,不管有没有,直接创建新事务)

Spring的事务的传播行为 - 图2
使用PROPAGATION_REQUIRES_NEW,需要使用 JtaTransactionManager作为事务管理器。
它会开启一个新的事务。如果一个事务已经存在,则先将这个存在的事务挂起。

  1. @Transactional(propagation = Propagation.REQUIRED)
  2. public void methodA() {
  3. doSomeThingA();
  4. methodB();
  5. doSomeThingB();
  6. // do something else
  7. }
  1. // 事务属性为REQUIRES_NEW
  2. @Transactional(propagation = Propagation.REQUIRES_NEW)
  3. public void methodB() {
  4. // do something
  5. }

当调用

  1. main{
  2. methodA();
  3. }

相当于调用

  1. main(){
  2. TransactionManager tm = null;
  3. try{ //获得一个JTA事务管理器
  4. tm = getTransactionManager();
  5. tm.begin();//开启一个新的事务---doSomeThingA();
  6. Transaction ts1 = tm.getTransaction();
  7. doSomeThing();
  8. tm.suspend();//挂起当前事务
  9. try{
  10. tm.begin();//重新开启第二个事务
  11. Transaction ts2 = tm.getTransaction();
  12. methodB();
  13. ts2.commit();//提交第二个事务
  14. } Catch(RunTimeException ex) {
  15. ts2.rollback();//回滚第二个事务
  16. } finally {
  17. //释放资源
  18. }
  19. //methodB执行完后,恢复第一个事务
  20. tm.resume(ts1);
  21. doSomeThingB();
  22. ts1.commit();//提交第一个事务
  23. } catch(RunTimeException ex) {
  24. ts1.rollback();//回滚第一个事务
  25. } finally {
  26. //释放资源
  27. }
  28. }

在这里,我把ts1称为外层事务,ts2称为内层事务。从上面的代码可以看出,ts2与ts1是两个独立的事务,互不相干。Ts2是否成功并不依赖于 ts1。如果methodA方法在调用methodB方法后的doSomeThingB方法失败了,而methodB方法所做的结果依然被提交。而除了methodB之外的其它代码导致的结果却被回滚了

5、PROPAGATION_NOT_SUPPORTED

  • (not supported不支持事务,存在就挂起)

PROPAGATION_NOT_SUPPORTED 总是非事务地执行,并挂起任何存在的事务。使用PROPAGATION_NOT_SUPPORTED,也需要使用JtaTransactionManager作为事务管理器。
Spring的事务的传播行为 - 图3

6、PROPAGATION_NEVER

  • (never不支持事务,存在就异常)

总是非事务地执行,如果存在一个活动事务,则抛出异常。

7、PROPAGATION_NESTED

  • (nested存在就在嵌套的执行,没有就找是否存在外面的事务,有则加入,没有则新建)

Spring的事务的传播行为 - 图4
如果一个活动的事务存在,则运行在一个嵌套的事务中。 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行。
这是一个嵌套事务,使用JDBC 3.0驱动时,仅仅支持DataSourceTransactionManager作为事务管理器。
需要JDBC 驱动的java.sql.Savepoint类。使用PROPAGATION_NESTED,还需要把PlatformTransactionManager的nestedTransactionAllowed属性设为true(属性值默认为false)。
这里关键是嵌套执行。

  1. @Transactional(propagation = Propagation.REQUIRED)
  2. methodA(){
  3. doSomeThingA();
  4. methodB();
  5. doSomeThingB();
  6. }
  1. @Transactional(propagation = Propagation.NEWSTED)
  2. methodB(){
  3. ……
  4. }

如果单独调用methodB方法,则按REQUIRED属性执行。如果调用methodA方法,相当于下面的效果:

  1. main(){
  2. Connection con = null;
  3. Savepoint savepoint = null;
  4. try{
  5. con = getConnection();
  6. con.setAutoCommit(false);
  7. doSomeThingA();
  8. savepoint = con2.setSavepoint();//保存当前的状态到savepoint
  9. try{
  10. methodB();
  11. } catch(RuntimeException ex) {
  12. con.rollback(savepoint);//恢复到之前保存的状态
  13. } finally {
  14. //释放资源
  15. }
  16. doSomeThingB();
  17. con.commit();
  18. } catch(RuntimeException ex) {
  19. con.rollback();//回滚包括methodB方法的所有操作
  20. } finally {
  21. //释放资源
  22. }
  23. }

当methodB方法调用之前,调用setSavepoint方法,保存当前的状态到savepoint。如果methodB方法调用失败,则恢复到之前保存的状态。但是需要注意的是,这时的事务并没有进行提交,如果后续的代码(doSomeThingB()方法)调用失败,则回滚包括methodB方法的所有操作。嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚。