前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >详解 Spring 事务传播性

详解 Spring 事务传播性

原创
作者头像
写bug的高哈哈
发布2025-02-09 15:27:29
发布2025-02-09 15:27:29
1000
举报

首先,我们来了解 Spring 事务传播性到底是什么?Spring 事务传播性是指当多个含有事务的方法嵌套调用时,这多个方法处理事务的规则。比如这个图,当事务方法 A 调用事务方法 B 时,内层事务方法 B 会合并到外层调用者 A 方法的事务中,还是会新开启自己的事务。另外如果合并到外层事务,那么当内层方法回滚后,外层方法会不会回滚。

那么,Spring 事务传播性具体是怎么处理嵌套事务这种行为的呢?总结来说,有 7 种处理方式。接下来,我会一一给你介绍,让你更好地理解 Spring 事务性的各个传播行为到底可以解决什么问题。

1.PROPAGATION_REQUIRED

这个传播行为是 Spring 默认的事务传播行为,它指的是如果外层调用方法已经开启了事务,那么当前方法就加入到外层事务。如果外层调用方没有开启事务,那么当前方法就开启一个事务。

这个传播行为可以保证多个嵌套的事务方法在同一个事务内执行,也就是可以保证多个事务方法同时提交,同时回滚。这个机制可以满足大多数业务场景。下面我们通过一个实例来说说这个传播行为是什么意思:

代码语言:java
复制
//服务类 A
@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public void methodA() {
        //1.1 做自己的入库操作
        insert();
        System.out.println("save something to db");
        //1.2 调用服务 B 的入库操作
        serviceB.methodB();
    }
}
代码语言:java
复制
//服务类 B
@Service
public class ServiceB {
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public void methodB() {
        insert();
        System.out.println("save something to db");
        //抛出异常
        //throw new RuntimeException();
    }
}

我们看这段代码中,ServiceA 类的 methodA 方法调用了 ServiceB 的 methodB 方法,其中 ServiceA 的 methodA 通过注解 Transactional 加了一个事务切面,事务的传播性设置为了 REQUIRED。

当调用者调用方法 methodA 时,如果调用者没有开启事务,那么由于 methodA 的事务传播性为 REQUIRED,所以会开启一个事务。methodA 方法内代码 1.1 向数据库插入了一条记录(但是并没有提交),代码 1.2 调用 ServiceB 的 methodB 方法,由于 methodB 事务传播性是 REQUIRED,并且它的调用者 methodA 已经开启了事务,所以 methodB 会合并到 methodA 开启的事务中执行数据库插入操作(但是没有提交)。

也就是 methodA 和 methodB 各自的数据库操作是在同一个事务内进行的,当 methodA 和 methodB 都执行成功完毕后,会提交事务。如这个图,这是嵌套调用链路的过程:

那你可能会疑问,假设 methodB 执行时抛出了 Exception 异常后,会发生什么?首先 methodB 执行的插入操作会被回滚掉,那么 methodA 执行的插入也会被回滚?答案是肯定的,因为 methodA 和 methodB 是在同一个事务内执行的,而事务是可以保证原子性操作的,这是它的嵌套调用链路:

这里你需要注意的一点,也是面试中会考到的一个问题是,如果 methodB 抛出异常后,methodA 使用 try-catch 语句 catch 了异常(如下代码),并没有向外抛出,那么这时会发生什么?methodA 会回滚吗?如这个代码就演示了 methodA 方法通过 try-catch 语句 catch 主 methodB 抛出的异常:

代码语言:java
复制
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public void methodA() {
    //1.1做自己的入库操作
     insert();
    System.out.println("save something to db");
    //1.2调用服务B的入库操作,catch主异常
    try{
        serviceB.methodB();
    }catch(Exception e){
    }
}

答案是肯定的,因为事务传播行为 REQUIRED 的语义,就是嵌套调用的多个事务方法在同一个事务内执行的。而事务本身是具有原子性的,所以只要有一个事务方法抛出而且回滚了,那么在同一个事务内的其他事务方法的执行也会回滚。所以虽然 methodA 方法 catch 主异常了,但是其插入数据库的操作还是会被回滚的。

2.PROPAGATION_REQUIRES_NEW

这个传播行为是每次都新开启一个事务。如果外层调用方已经开启了事务,就先把外层的事务挂起,然后执行当前新事务,执行完毕后再恢复上层事务的执行。

代码语言:java
复制
@Service
public class ServiceB {
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
    public void methodB() {
            insert();
        System.out.println("save something to db");
    }
}

你看这段代码中,我们设置 methodB 的传播性为 REQUIRES_NEW,methodA 的代码不变,传播性还是 REQUIRED,那么这个时候,methodA 调用 methodB 会怎么样呢?

当执行 methodA 前,如果调用方没有开启事务,那么因为 methodA 传播性为 REQUIRED,它会开启一个事务 1,然后执行 methodA 执行插入操作。在执行 methodB 前由于 methodB 事务传播性为 REQUIRES_NEW,所以会先挂起 methodA 开启的事务 1,然后开启自己的事务 2,再执行 methodB。当 methodB 执行完毕后会先提交 methodB 对应的事务 2,然后恢复 methodA 对应的事务 1 的执行,最终提交事务 1。

你可以试想一下,当 methodB 执行时抛出了异常,会发生什么呢?我们来看这张图,答案是 methodB 的插入操作会被回滚掉,但是 methodA 不受影响。如下图:

3.PROPAGATION_SUPPORTED

这个传播行为是指,如果外层调用方开启了事务,那当前方法加入到外层事务。如果外层不存在事务,那么当前方法也不会创建新事务,直接使用非事务方式执行。这是什么意思呢?

首先我们看这个代码:

代码语言:java
复制
public void methodA() {
    //1.1做自己的入库操作
            insert();
    System.out.println("save something to db");
    //1.2调用服务B的入库操作
    serviceB.methodB();
}
@Service
public class ServiceB {
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.SUPPORTED)
    public void methodB() {
            insert();
        System.out.println("save something to db");
    }
}

methodA 没有加事务注解,methodB 的传播性为 SUPPORTS。那么如果 methodA 的调用方本身没有开启事务,methodA 执行前也不会开启事务。methodA 执行代码 1.2 调用 methodB 时,由于 methodB 的传播性为 SUPPORTED,并且它的调用方 methodA 没有开启事务,所以 methodB 会以非事务方式运行:

然后我们看这个代码:

代码语言:java
复制
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public void methodA() {
    //1.1做自己的入库操作
            insert();
    System.out.println("save something to db");
    //1.2调用服务B的入库操作
    serviceB.methodB();
}
@Service
public class ServiceB {
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.SUPPORTED)
    public void methodB() {
            insert();
        System.out.println("save something to db");
    }
}

你可以看到,methodA 添加了事务注解,methodB 的传播性为 SUPPORTS。如果 methodA 的调用方本身没有开启事务,methodA 执行前会开启事务。methodA 执行代码 1.2 调用 methodB 时,由于 methodB 的传播性为 SUPPORTED,并且其调用方 methodA 开启了事务,所以 methodB 会加入到 methodA 的事务内运行:

4.PROPAGATION_NOT_SUPPORTED

这个传播行为不支持事务。也就是如果外层调用者开启了事务,就挂起外层事务,然后以非事务方式执行当前方法逻辑,等执行完毕后,再恢复外层事务的执行。下面我们看这个代码来说明这个传播行为的作用:

代码语言:java
复制
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public void methodA() {
    //1.1做自己的入库操作
            insert();
    System.out.println("save something to db");
    //1.2调用服务B的入库操作
    serviceB.methodB();
}
@Service
public class ServiceB {
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.NOT_SUPPORTED)
    public void methodB() {
            insert();
        System.out.println("save something to db");
    }
}

我们设置 methodA 的传播行为 REQUIRED,methodB 的传播行为为 NOT_SUPPORTED。如果 methodA 的调用方本身没有开启事务,methodA 执行前会开启事务。methodA 执行代码 1.2 调用 methodB 时,由于 methodB 的传播性为 NOT_SUPPORTED,所以会挂起外层 methodA 开启的事务,然后 methodB 以非事务方式运行,运行完毕后,在恢复 methodA 的事务的执行,最后提交事务,这个过程可以使用下面流程图来描述:

5.PROPAGATION_NEVER

这个传播行为不支持事务。也就是说如果外层调用者开启了事务,就执行当前方法前会抛出异常。

代码语言:java
复制
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public void methodA() {
    //1.1做自己的入库操作
            insert();
    System.out.println("save something to db");
    //1.2调用服务B的入库操作
    serviceB.methodB();
}
@Service
public class ServiceB {
@Transactional(rollbackFor = Exception.class, propagation = Propagation.NEVER)
    public void methodB() {
            insert();
        System.out.println("save something to db");
    }

我们看这段代码,methodA 添加了事务注解,methodB 的传播性为 NEVER。如果 methodA 的调用方本身没有开启事务,methodA 执行前会开启事务。methodA 执行代码 1.2 调用 methodB 时,由于 methodB 的传播性为 NEVER,所以会抛出异常,就像这样:

代码语言:java
复制
IllegalTransactionStateException(
                    "Existing transaction found for transaction marked with propagation 'never'")

6.PROPAGATION_MANDATORY

这个传播行为是说,配置了这个传播性的方法只能在已经存在事务的方法中被调用。如果在不存在事务的方法中被调用,会抛出异常。

代码语言:java
复制
public void methodA() {
    //1.1做自己的入库操作
    insert();
    System.out.println("save something to db");
    //1.2调用服务B的入库操作
    serviceB.methodB();
}
@Service
public class ServiceB {
@Transactional(rollbackFor = Exception.class, propagation = Propagation.MANDATORY)
    public void methodB() {
        insert();
        System.out.println("save something to db");

如上方法 你看这里的代码,methodA 没有加事务注解,methodB 的传播性为 PROPAGATION_MANDATORY。如果 methodA 的调用方本身没有开启事务,methodA 执行前也不会开启事务。methodA 执行代码 1.2 调用 methodB 时,由于 methodB 的传播性为 PROPAGATION_MANDATORY,所以会抛出这样的异常:

代码语言:java
复制
IllegalTransactionStateException(
                    "No existing transaction found for transaction marked with propagation 'mandatory'")

7.PROPAGATION_NESTED

当外层调用方存在事务时,当前方法合并到外层事务,如果外层不存在事务,就当前开启事务,这点和 PROPAGATION_REQUIRED 传播性一致,不同的是,PROPAGATION_NESTED 传播行为的特点是可以保存状态保存点,当事务回滚时,可以回滚到某一个保存点上,从而避免所有嵌套事务都回滚。

代码语言:java
复制
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public void methodA() {
    //1.1做自己的入库操作
    insert()
    System.out.println("save something to db");
    //1.2调用服务B的入库操作
    try{
    serviceB.methodB();
    }catch(Exception e){
    }
    //1.3 执行更新操作
    update();
}
@Service
public class ServiceB {
@Transactional(rollbackFor = Exception.class, propagation = Propagation.NESTED)
    public void methodB() {
            insert();
        System.out.println("save something to db");
    }

在这段代码中,methodA 执行时,如果调用方没开启事务,它就会开启一个事务,然后执行插入操作;执行 1.2 时由于 methodB 传播性为 NESTED,所以会新建一个 savepoint,用于标记代码 1.1 执行的插入操作。

然后 methodB 的执行是使用了 methodA 开启事务的,当 methodB 执行抛出异常时,methodB 的插入操作会被回滚掉。那么 methodA 中的代码 1.1 的 insert 操作会回滚?答案是不会,这是因为执行 methodB 前建立了 savepoint 点,methodB 的回滚只会回滚到这个 savepoint 点之前。

并且这里当 methodB 回滚后,代码 1.1 和代码 1.3 的执行都会被提交到数据库。简单来说,Spring 事务传播性是为了解决事务嵌套问题的,它定义了一系列的传播行为,用来规定当出现事务嵌套时,应该采用哪种策略来进行处理。

在日常开发环境中,我们最常用到的事务传播行为只有两种,一是 REQUIRED,二是 REQUIRES_NEW。REQUIRED 可以保证多个事务方法在同一个事务内执行,而事务本身是具有原子性的,所以这个传播性可以保证多个事务方法执行的原子性。比如当应用层需要编排不用业务域的服务实现一个功能时,就需要每个业务域的方法都配置为 REQUIRED 传播性,从而保证不同业务域的方法同时提交变动或者同时回滚变动。

而 REQUIRES_NEW 可以保证内层方法开启独立于外层方法的事务,这样当内层方法抛出异常回滚自己的事务时,不会影响外层事务方法的执行,这种传播性更适用于内层方法的执行独立于外层方法执行的场景,比如执行业务逻辑的服务方法调用写日志表服务的方法时,如果打日志抛异常了,就不该让业务逻辑服务方法回滚。

至于其他五个传播行为,在日常开发中基本不会用到,我们只需要了解它们的特点以及工作机制,扩充下自己的知识面即可。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.PROPAGATION_REQUIRED
  • 2.PROPAGATION_REQUIRES_NEW
  • 3.PROPAGATION_SUPPORTED
  • 4.PROPAGATION_NOT_SUPPORTED
  • 5.PROPAGATION_NEVER
  • 6.PROPAGATION_MANDATORY
  • 7.PROPAGATION_NESTED
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档