Spring事务的传播行为

之前有简单介绍过 Spring事务的四个特性(ACID)Spring事务的隔离级别数据库悲观锁和乐观锁, 不了解的可以先看看这些

今天继续了解一下Spring事务的传播行为, 一共有七种

在这里插入图片描述

1.PROPAGATION_REQUIRED

如果当前上下文中没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的默认设置

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

@Transactional(propagation= Propagation.REQUIRED)
public void methodB(){
    // do something
}

单独调用methodB方法时,因为当前上下文不存在事务,所以会开启一个新的事务, methodB中发生错误会回滚

调用methodA方法时,因为当前上下文不存在事务,所以会开启一个新的事务。当执行到methodB时,methodB发现当前上下文有事务,因此就加入到当前事务中来。其中如果methodB发生异常,触发事务回滚,methodA中的也会回滚。

2.PROPAGATION_SUPPORTS

支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。

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

@Transactional(propagation= Propagation.SUPPORTS)
public void methodB(){
    // do something
}

如果调用methodA,A中调用methodB,mehtodB会加入到methodA的开启的当前事务中。
如果直接调用methodB,当前没有事务,就以非事务执行。B中遇到错误也不会发生回滚

3.PROPAGATION_MANDATORY

支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。

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

@Transactional(propagation= Propagation.MANDATORY)
public void methodB(){
    // do something
}

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

如果调用methodA,再调用methodB,MehtodB会加入到MethodA的开启的当前事务中。

4.PROPAGATION_REQUIRES_NEW

使用PROPAGATION_REQUIRES_NEW,需要使用 JtaTransactionManager作为事务管理器。

它会开启一个新的事务。如果一个事务已经存在,则先将这个存在的事务挂起。

@Transactional(propagation= Propagation.REQUIRED)
public void methodA(){
    // do something pre
	methodB();
    // do something post
}

@Transactional(propagation= Propagation.REQUIRES_NEW)
public void methodB(){
    // do something
}

当调用

main{  
methodA();
} 

相当于

main(){
    TransactionManager tm = null;
    try{
        //获得一个JTA事务管理器
        tm = getTransactionManager();
        tm.begin();//开启一个新的事务, 也就是methodA的事务
        Transaction ts1 = tm.getTransaction();
        doSomeThing();
        tm.suspend();//挂起当前事务, 也就是methodA的事务
        try{
            tm.begin();//重新开启第二个事务, 也就是methodB的事务
            Transaction ts2 = tm.getTransaction();
            methodB();
            ts2.commit();//提交第二个事务, 也就是methodB的事务
        } Catch(RunTimeException ex) {
            ts2.rollback();//回滚第二个事务, 也就是methodB的事务
        } finally {
            //释放资源
        }
        //methodB执行完后,恢复第一个事务, 也就是methodA的事务
        tm.resume(ts1);
        doSomeThingB();
        ts1.commit();//提交第一个事务, 也就是methodA的事务
    } catch(RunTimeException ex) {
        ts1.rollback();//回滚第一个事务, 也就是methodA的事务
    } finally {
        //释放资源
    }
}

分析上面代码可以发现,调用methodA,会先开启事务ts1,执行A的something pre的代码。再调用methodB,methdoB会开启一个事务ts2,再执行methodB自身的代码。最后在执行methodA的something post。如果methodB发生异常回滚,只是methodB中的代码回滚,不影响methodA中的代码。如果methodA发生异常回滚,只回滚methodA中的代码,不影响methodB中的代码。

可以把ts1称为外层事务,ts2称为内层事务。从上面的代码可以看出,ts2与ts1是两个独立的事务,互不相干。Ts2是否成功并不依赖于 ts1。

5.PROPAGATION_NOT_SUPPORTED

总是以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

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

@Transactional(propagation= Propagation.NOT_SUPPORTED)
public void methodB(){
    // do something
}

调用methodA,再调用methodB,methodA开启的事务会被挂起,即在methodB中不起作用,相当于没有事务,methodB内部抛出异常不会回滚(A和B都不会回滚, 因为B没有加入到A事务中)。methodA内的代码发生异常会回滚。

直接调用methodB,不会开启事务。

6.PROPAGATION_NEVER

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

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

@Transactional(propagation= Propagation.NEVER)
public void methodB(){
    // do something
}

调用methodA,再调用methodB,此时会报错,因为methodB发现上下文中存在事务

7.PROPAGATION_NESTED

如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按PROPAGATION_REQUIRED属性执行。

@Transactional(propagation= Propagation.REQUIRED)
public void methodA(){
    // do something pre
	methodB();
    // do something post
}

@Transactional(propagation= Propagation.NESTED)
public void methodB(){
    // do something
}

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

main(){
    Connection con = null;
    Savepoint savepoint = null;
    try{
        con = getConnection();
        con.setAutoCommit(false);
        doSomeThingA();
        savepoint = con2.setSavepoint();
        try{
            methodB();
        } catch(RuntimeException ex) {
            con.rollback(savepoint);
        } finally {
            //释放资源
        }
        doSomeThingB();
        con.commit();
    } catch(RuntimeException ex) {
        con.rollback();
    } finally {
        //释放资源
    }
}

调用methodA,开启一个事务,执行something pre的代码,设置回滚点savepoint,再调用methodB的代码,如果methodB里抛出异常,此时回滚到之前的saveponint(这时的事务并没有进行提交,如果后续的代码(doSomeThingB()方法)调用失败,则回滚包括methodB方法的所有操作。嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚),然后再执行methodA里的something post的代码,最后提交或者回滚事务。

嵌套事务,外层的事务如果回滚,会导致内层的事务也回滚;但是内层的事务如果回滚,仅仅是回滚自己的代码,不影响外层的事务的代码。

PROPAGATION_NESTED 与PROPAGATION_REQUIRES_NEW的区别:

它们非常类似,都像一个嵌套事务,如果不存在一个活动的事务,都会开启一个新的事务。

使用 PROPAGATION_REQUIRES_NEW时,内层事务与外层事务就像两个独立的事务一样,一旦内层事务进行了提交后,外层事务不能对其进行回滚。两个事务互不影响。两个事务不是一个真正的嵌套事务。同时它需要JTA事务管理器的支持。
PROPAGATION_REQUIRES_NEW会挂起已经存在的事务, 然后自己开启一个事务, 两个事务是相互独立不影响的。

使用PROPAGATION_NESTED时,外层事务的回滚可以引起内层事务的回滚。而内层事务的异常并不会导致外层事务的回滚,它是一个真正的嵌套事务。DataSourceTransactionManager使用savepoint支持PROPAGATION_NESTED时,需要JDBC 3.0以上驱动及1.4以上的JDK版本支持。其它的JTATrasactionManager实现可能有不同的支持方式。

PROPAGATION_REQUIRES_NEW 启动一个新的, 不依赖于环境的 “内部” 事务. 这个事务将被完全 commited 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行。

另一方面, PROPAGATION_NESTED 开始一个 “嵌套的” 事务, 它是已经存在事务的一个真正的子事务. 潜套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 潜套事务是外部事务的一部分, 只有外部事务结束后它才会被提交。

由此可见, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于, PROPAGATION_REQUIRES_NEW 完全是一个新的事务, 而 PROPAGATION_NESTED 则是外部事务的子事务, 如果外部事务 commit, 嵌套事务也会被 commit, 这个规则同样适用于 roll back.

posted @ 2022-08-24 09:16  KILLNPE  阅读(167)  评论(0编辑  收藏  举报