Spring第45篇:带你吃透Spring事务7种传播行为

本文详解 Spring 事务中的 7 种传播行为,特别重要。

环境

1. jdk1.8

2. Spring 5.2.3.RELEASE

3. mysql5.7

什么是事务传播行为?

事务的传播行为用来描述:系统中的一些方法交由 spring 来管理事务,当这些方法之间出现嵌套调用的时候,事务所表现出来的行为是什么样的?

比如下面 2 个类,Service1 中的 m1 方法和 Service2 中的 m2 方法上面都有@Transactional 注解,说明这 2 个方法由 spring 来控制事务。

但是注意 m1 中 2 行代码,先执行了一个 insert,然后调用 service2 中的 m2 方法,service2 中的 m2 方法也执行了一个 insert。

那么大家觉得这 2 个 insert 会在一个事务中运行么?也就是说此时事务的表现行为是什么样的呢?这个就是 spring 事务的传播行为来控制的事情,不同的传播行为,表现会不一样,可能他们会在一个事务中执行,也可能不会在一个事务中执行,这就需要看传播行为的配置了。

@Component
public class Service1 {
    @Autowired
    private Service2 service2;

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Transactional
    public void m1() {
        this.jdbcTemplate.update("INSERT into t1 values ('m1')");
        this.service2.m2();
    }
}

@Component
public class Service2 {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Transactional
    public void m2() {
        this.jdbcTemplate.update("INSERT into t1 values ('m2')");
    }
}

如何配置事务传播行为?

通过@Transactional 注解中的 propagation 属性来指定事务的传播行为:

Propagation propagation() default Propagation.REQUIRED;

7 种传播行为

Propagation 是个枚举,有 7 种值,如下:

事务传播行为类型说明
REQUIRED如果当前事务管理器中没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择,是默认的传播行为。
SUPPORTS支持当前事务,如果当前事务管理器中没有事务,就以非事务方式执行。
MANDATORY使用当前的事务,如果当前事务管理器中没有事务,就抛出异常。
REQUIRES_NEW新建事务,如果当前事务管理器中存在事务,把当前事务挂起,然后会新建一个事务。
NOT_SUPPORTED以非事务方式执行操作,如果当前事务管理器中存在事务,就把当前事务挂起。
NEVER以非事务方式执行,如果当前事务管理器中存在事务,则抛出异常。
NESTED如果当前事务管理器中存在事务,则在嵌套事务内执行;如果当前事务管理器中没有事务,则执行与 PROPAGATION_REQUIRED 类似的操作。

注意:这 7 种传播行为有个前提,他们的事务管理器是同一个的时候,才会有上面描述中的表现行为。

下面通过案例对 7 中表现行为来做说明,在看案例之前,先来回顾几个知识点

1、Spring 声明式事务处理事务的过程

spring 声明式事务是通过事务拦截器 TransactionInterceptor 拦截目标方法,来实现事务管理的功能的,事务管理器处理过程大致如下:

1、获取事务管理器
2、通过事务管理器开启事务
try{
 3、调用业务方法执行db操作
 4、提交事务
}catch(RuntimeException | Error){
 5、回滚事务
}

2、何时事务会回滚?

默认情况下,目标方法抛出 RuntimeException 或者 Error 的时候,事务会被回滚。

3、Spring 事务管理器中的 Connection 和业务中操作 db 的 Connection 如何使用同一个的?

以 DataSourceTransactionManager 为事务管理器,操作 db 使用 JdbcTemplate 来说明一下。

创建 DataSourceTransactionManager 和 JdbcTemplate 的时候都需要指定 dataSource,需要将他俩的 dataSource 指定为同一个对象。

当事务管理器开启事务的时候,会通过 dataSource.getConnection()方法获取一个 db 连接 connection,然后会将 dataSource->connection 丢到一个 Map 中,然后将 map 放到 ThreadLocal 中。

当 JdbcTemplate 执行 sql 的时候,以 JdbcTemplate.dataSource 去上面的 ThreadLocal 中查找,是否有可用的连接,如果有,就直接拿来用了,否则调用 JdbcTemplate.dataSource.getConnection()方法获取一个连接来用。

所以 spring 中可以确保事务管理器中的 Connection 和 JdbcTemplate 中操作 db 的 Connection 是同一个,这样才能确保 spring 可以控制事务。

代码验证

准备 db

DROP DATABASE IF EXISTS javacode2018;
CREATE DATABASE if NOT EXISTS javacode2018;

USE javacode2018;
DROP TABLE IF EXISTS user1;
CREATE TABLE user1(
  id int PRIMARY KEY AUTO_INCREMENT,
  name varchar(64) NOT NULL DEFAULT '' COMMENT '姓名'
);

DROP TABLE IF EXISTS user2;
CREATE TABLE user2(
  id int PRIMARY KEY AUTO_INCREMENT,
  name varchar(64) NOT NULL DEFAULT '' COMMENT '姓名'
);

spring 配置类 MainConfig6

准备 JdbcTemplate 和事务管理器。

package com.javacode2018.tx.demo6;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

@EnableTransactionManagement //开启spring事务管理功能
@Configuration //指定当前类是一个spring配置类
@ComponentScan //开启bean扫描注册
public class MainConfig6 {
    //定义一个数据源
    @Bean
    public DataSource dataSource() {
        org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8");
        dataSource.setUsername("root");
        dataSource.setPassword("root123");
        dataSource.setInitialSize(5);
        return dataSource;
    }

    //定义一个JdbcTemplate,用来执行db操作
    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }

    //定义我一个事务管理器
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

来 3 个 service

后面的案例中会在这 3 个 service 中使用 spring 的事务来演示效果。

User1Service

package com.javacode2018.tx.demo6;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

@Component
public class User1Service {
    @Autowired
    private JdbcTemplate jdbcTemplate;
}

User2Service

package com.javacode2018.tx.demo6;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

@Component
public class User2Service {
    @Autowired
    private JdbcTemplate jdbcTemplate;
}

TxService

package com.javacode2018.tx.demo6;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class TxService {
    @Autowired
    private User1Service user1Service;
    @Autowired
    private User2Service user2Service;
}

测试用例 Demo6Test

before 方法会在每个@Test 标注的方法之前执行一次,这个方法主要用来做一些准备工作:启动 spring 容器、清理 2 个表中的数据;after 方法会在每个@Test 标注的方法执行完毕之后执行一次,我们在这个里面输出 2 个表的数据;方便查看的测试用例效果。

package com.javacode2018.tx.demo6;

import org.junit.Before;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Demo6Test {

    private TxService txService;
    private JdbcTemplate jdbcTemplate;

    //每个@Test用例执行之前先启动一下spring容器,并清理一下user1、user2中的数据
    @Before
    public void before() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig6.class);
        txService = context.getBean(TxService.class);
        jdbcTemplate = context.getBean(JdbcTemplate.class);
        jdbcTemplate.update("truncate table user1");
        jdbcTemplate.update("truncate table user2");
    }

    @After
    public void after() {
        System.out.println("user1表数据:" + jdbcTemplate.queryForList("SELECT * from user1"));
        System.out.println("user2表数据:" + jdbcTemplate.queryForList("SELECT * from user2"));
    }

}

1、REQUIRED

User1Service

添加 1 个方法,事务传播行为:REQUIRED

@Transactional(propagation = Propagation.REQUIRED)
public void required(String name) {
    this.jdbcTemplate.update("insert into user1(name) VALUES (?)", name);
}

User2Service

添加 2 个方法,事务传播行为:REQUIRED,注意第 2 个方法内部最后一行会抛出一个异常。

@Transactional(propagation = Propagation.REQUIRED)
public void required(String name) {
    this.jdbcTemplate.update("insert into user1(name) VALUES (?)", name);
}

@Transactional(propagation = Propagation.REQUIRED)
public void required_exception(String name) {
    this.jdbcTemplate.update("insert into user1(name) VALUES (?)", name);
    throw new RuntimeException();
}

场景 1(1-1)

外围方法没有事务,外围方法内部调用 2 个 REQUIRED 级别的事务方法。

案例中都是在 TxService 的方法中去调用另外 2 个 service,所以 TxService 中的方法统称外围方法,另外 2 个 service 中的方法称内部方法。

验证方法 1

TxService 添加

public void notransaction_exception_required_required() {
    this.user1Service.required("张三");
    this.user2Service.required("李四");
    throw new RuntimeException();
}

测试用例,Demo6Test 中添加

@Test
public void notransaction_exception_required_required() {
    txService.notransaction_exception_required_required();
}

运行输出

user1表数据:[{id=1, name=张三}]
user2表数据:[{id=1, name=李四}]

验证方法 2

TxService 添加

public void notransaction_required_required_exception() {
    this.user1Service.required("张三");
    this.user2Service.required_exception("李四");
}

测试用例,Demo6Test 中添加

@Test
public void notransaction_required_required_exception() {
    txService.notransaction_required_required_exception();
}

运行输出

user1表数据:[{id=1, name=张三}]
user2表数据:[]

结果分析

验证方法序号数据库结果结果分析
1“张三”、“李四”均插入。外围方法未开启事务,插入“张三”、“李四”方法在自己的事务中独立运行,外围方法异常不影响内部插入“张三”、“李四”方法独立的事务。
2“张三”插入,“李四”未插入。外围方法没有事务,插入“张三”、“李四”方法都在自己的事务中独立运行,所以插入“李四”方法抛出异常只会回滚插入“李四”方法,插入“张三”方法不受影响。

结论

通过这两个方法我们证明了在外围方法未开启事务的情况下Propagation.REQUIRED修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。

场景 2(1-2)

外围方法开启事务(Propagation.REQUIRED),这个使用频率特别高。

验证方法 1

TxService 添加

@Transactional(propagation = Propagation.REQUIRED)
public void transaction_exception_required_required() {
    user1Service.required("张三");
    user2Service.required("李四");
    throw new RuntimeException();
}

测试用例,Demo6Test 中添加

@Test
public void transaction_exception_required_required() {
    txService.transaction_exception_required_required();
}

运行输出

user1表数据:[]
user2表数据:[]

验证方法 2

TxService 添加

@Transactional(propagation = Propagation.REQUIRED)
public void transaction_required_required_exception() {
    user1Service.required("张三");
    user2Service.required_exception("李四");
}

测试用例,Demo6Test 中添加

@Test
public void transaction_required_required_exception() {
    txService.transaction_required_required_exception();
}

运行输出

user1表数据:[]
user2表数据:[]

验证方法 3

TxService 添加

@Transactional(propagation = Propagation.REQUIRED)
public void transaction_required_required_exception_try() {
    user1Service.required("张三");
    try {
        user2Service.required_exception("李四");
    } catch (Exception e) {
        System.out.println("方法回滚");
    }
}

测试用例,Demo6Test 中添加

@Test
public void transaction_required_required_exception_try() {
    txService.transaction_required_required_exception_try();
}

运行输出

方法回滚
user1表数据:[]
user2表数据:[]

结果分析

验证方法序号数据库结果结果分析
1“张三”、“李四”均未插入。外围方法开启事务,内部方法加入外围方法事务,外围方法回滚,内部方法也要回滚。
2“张三”、“李四”均未插入。外围方法开启事务,内部方法加入外围方法事务,内部方法抛出异常回滚,外围方法感知异常致使整体事务回滚。
3“张三”、“李四”均未插入。外围方法开启事务,内部方法加入外围方法事务,内部方法抛出异常回滚,即使方法被 catch 不被外围方法感知,整个事务依然回滚。

结论

以上试验结果我们证明在外围方法开启事务的情况下Propagation.REQUIRED修饰的内部方法会加入到外围方法的事务中,所有Propagation.REQUIRED修饰的内部方法和外围方法均属于同一事务,只要一个方法回滚,整个事务均回滚。

2、PROPAGATION_REQUIRES_NEW

User1Service

添加 1 个方法,事务传播行为:REQUIRES_NEW

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void requires_new(String name) {
    this.jdbcTemplate.update("insert into user1(name) VALUES (?)", name);
}

User2Service

添加 2 个方法,事务传播行为:REQUIRES_NEW,注意第 2 个方法内部最后一行会抛出一个异常。

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void requires_new(String name) {
    this.jdbcTemplate.update("insert into user2(name) VALUES (?)", name);
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void requires_new_exception(String name) {
    this.jdbcTemplate.update("insert into user2(name) VALUES (?)", name);
    throw new RuntimeException();
}

场景 1(2-1)

外围方法没有事务。

验证方法 1

TxService 添加

public void notransaction_exception_requiresNew_requiresNew(){
    user1Service.requires_new("张三");
    user2Service.requires_new("李四");
    throw new RuntimeException();
}

Demo6Test 中添加

@Test
public void notransaction_exception_requiresNew_requiresNew() {
    txService.notransaction_exception_requiresNew_requiresNew();
}

运行输出

user1表数据:[{id=1, name=张三}]
user2表数据:[{id=1, name=李四}]

验证方法 2

TxService 添加

public void notransaction_requiresNew_requiresNew_exception(){
    user1Service.requires_new("张三");
    user2Service.requires_new_exception("李四");
}

测试用例,Demo6Test 中添加

@Test
public void notransaction_requiresNew_requiresNew_exception() {
    txService.notransaction_requiresNew_requiresNew_exception();
}

运行输出

user1表数据:[{id=1, name=张三}]
user2表数据:[]

结果分析

验证方法序号数据库结果结果分析
1“张三”插入,“李四”插入。外围方法没有事务,插入“张三”、“李四”方法都在自己的事务中独立运行,外围方法抛出异常回滚不会影响内部方法。
2“张三”插入,“李四”未插入外围方法没有开启事务,插入“张三”方法和插入“李四”方法分别开启自己的事务,插入“李四”方法抛出异常回滚,其他事务不受影响。

结论

通过这两个方法我们证明了在外围方法未开启事务的情况下Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。

场景 2(2-2)

外围方法开启事务。

验证方法 1

TxService 添加

@Transactional(propagation = Propagation.REQUIRED)
public void transaction_exception_required_requiresNew_requiresNew() {
    user1Service.required("张三");
    user2Service.requires_new("李四");
    user2Service.requires_new("王五");
    throw new RuntimeException();
}

测试用例,Demo6Test 中添加

@Test
public void transaction_exception_required_requiresNew_requiresNew() {
    txService.transaction_exception_required_requiresNew_requiresNew();
}

运行输出

user1表数据:[]
user2表数据:[{id=1, name=李四}, {id=2, name=王五}]

验证方法 2

TxService 添加

@Transactional(propagation = Propagation.REQUIRED)
public void transaction_required_requiresNew_requiresNew_exception() {
    user1Service.required("张三");
    user2Service.requires_new("李四");
    user2Service.requires_new_exception("王五");
}

Demo6Test 中添加

@Test
public void transaction_required_requiresNew_requiresNew_exception() {
    txService.transaction_required_requiresNew_requiresNew_exception();
}

运行输出

user1表数据:[]
user2表数据:[{id=1, name=李四}]

验证方法 3

TxService 添加

@Transactional(propagation = Propagation.REQUIRED)
public void transaction_required_requiresNew_requiresNew_exception_try(){
    user1Service.required("张三");
    user2Service.requires_new("李四");
    try {
        user2Service.requires_new_exception("王五");
    } catch (Exception e) {
        System.out.println("回滚");
    }
}

Demo6Test 中添加

@Test
public void transaction_required_requiresNew_requiresNew_exception_try() {
    txService.transaction_required_requiresNew_requiresNew_exception_try();
}

运行输出

回滚
user1表数据:[{id=1, name=张三}]
user2表数据:[{id=1, name=李四}]

结果分析

验证方法序号数据库结果结果分析
1“张三”未插入,“李四”插入,“王五”插入。外围方法开启事务,插入“张三”方法和外围方法一个事务,插入“李四”方法、插入“王五”方法分别在独立的新建事务中,外围方法抛出异常只回滚和外围方法同一事务的方法,故插入“张三”的方法回滚。
2“张三”未插入,“李四”插入,“王五”未插入。外围方法开启事务,插入“张三”方法和外围方法一个事务,插入“李四”方法、插入“王五”方法分别在独立的新建事务中。插入“王五”方法抛出异常,首先插入 “王五”方法的事务被回滚,异常继续抛出被外围方法感知,外围方法事务亦被回滚,故插入“张三”方法也被回滚。
3“张三”插入,“李四”插入,“王五”未插入。外围方法开启事务,插入“张三”方法和外围方法一个事务,插入“李四”方法、插入“王五”方法分别在独立的新建事务中。插入“王五”方法抛出异常,首先插入“王五”方法的事务被回滚,异常被 catch 不会被外围方法感知,外围方法事务不回滚,故插入“张三”方法插入成功。

结论

在外围方法开启事务的情况下Propagation.REQUIRES_NEW修饰的内部方法依然会单独开启独立事务,且与外部方法事务也独立,内部方法之间、内部方法和外部方法事务均相互独立,互不干扰。

3、PROPAGATION_NESTED

User1Service

添加 1 个方法,事务传播行为:NESTED

@Transactional(propagation = Propagation.NESTED)
public void nested(String name) {
    this.jdbcTemplate.update("insert into user1(name) VALUES (?)", name);
}

User2Service

添加 2 个方法,事务传播行为:NESTED,注意第 2 个方法内部最后一行会抛出一个异常。

@Transactional(propagation = Propagation.NESTED)
public void nested(String name) {
    this.jdbcTemplate.update("insert into user2(name) VALUES (?)", name);
}

@Transactional(propagation = Propagation.NESTED)
public void nested_exception(String name) {
    this.jdbcTemplate.update("insert into user2(name) VALUES (?)", name);
    throw new RuntimeException();
}

场景 1(3-1)

外围方法没有事务。

验证方法 1

TxService 添加

public void notransaction_exception_nested_nested(){
    user1Service.nested("张三");
    user2Service.nested("李四");
    throw new RuntimeException();
}

Demo6Test 中添加

@Test
public void notransaction_exception_nested_nested() {
    txService.notransaction_exception_nested_nested();
}

运行输出

user1表数据:[{id=1, name=张三}]
user2表数据:[{id=1, name=李四}]

验证方法 2

TxService 添加

public void notransaction_nested_nested_exception(){
    user1Service.nested("张三");
    user2Service.nested_exception("李四");
}

测试用例,Demo6Test 中添加

@Test
public void notransaction_nested_nested_exception() {
    txService.notransaction_nested_nested_exception();
}

运行输出

user1表数据:[{id=1, name=张三}]
user2表数据:[]

结果分析

验证方法序号数据库结果结果分析
1“张三”、“李四”均插入。外围方法未开启事务,插入“张三”、“李四”方法在自己的事务中独立运行,外围方法异常不影响内部插入“张三”、“李四”方法独立的事务。
2“张三”插入,“李四”未插入。外围方法没有事务,插入“张三”、“李四”方法都在自己的事务中独立运行,所以插入“李四”方法抛出异常只会回滚插入“李四”方法,插入“张三”方法不受影响。

结论

通过这两个方法我们证明了在外围方法未开启事务的情况下Propagation.NESTEDPropagation.REQUIRED作用相同,修饰的内部方法都会新开启自己的事务,且开启的事务相互独立,互不干扰。

场景 2(3-1)

外围方法开启事务。

验证方法 1

TxService 添加

@Transactional
public void transaction_exception_nested_nested(){
    user1Service.nested("张三");
    user2Service.nested("李四");
    throw new RuntimeException();
}

测试用例,Demo6Test 中添加

@Test
public void transaction_exception_nested_nested() {
    txService.transaction_exception_nested_nested();
}

运行输出

user1表数据:[]
user2表数据:[]

验证方法 2

TxService 添加

@Transactional
public void transaction_nested_nested_exception(){
    user1Service.nested("张三");
    user2Service.nested_exception("李四");
}

Demo6Test 中添加

@Test
public void transaction_nested_nested_exception() {
    txService.transaction_nested_nested_exception();
}

运行输出

user1表数据:[]
user2表数据:[]

验证方法 3

TxService 添加

@Transactional
public void transaction_nested_nested_exception_try(){
    user1Service.nested("张三");
    try {
        user2Service.nested_exception("李四");
    } catch (Exception e) {
        System.out.println("方法回滚");
    }
}

Demo6Test 中添加

@Test
public void transaction_nested_nested_exception_try() {
    txService.transaction_nested_nested_exception_try();
}

运行输出

方法回滚
user1表数据:[{id=1, name=张三}]
user2表数据:[]

结果分析

验证方法序号数据库结果结果分析
1“张三”、“李四”均未插入。外围方法开启事务,内部事务为外围事务的子事务,外围方法回滚,内部方法也要回滚。
2“张三”、“李四”均未插入。外围方法开启事务,内部事务为外围事务的子事务,内部方法抛出异常回滚,且外围方法感知异常致使整体事务回滚。
3“张三”插入、“李四”未插入。外围方法开启事务,内部事务为外围事务的子事务,插入“李四”内部方法抛出异常,可以单独对子事务回滚。

结论

以上试验结果我们证明在外围方法开启事务的情况下Propagation.NESTED修饰的内部方法属于外部事务的子事务,外围主事务回滚,子事务一定回滚,而内部子事务可以单独回滚而不影响外围主事务和其他子事务。

内部事务原理

以 mysql 为例,mysql 中有个savepoint 的功能,NESTED 内部事务就是通过这个实现的。

REQUIRED,REQUIRES_NEW,NESTED 比较

由“场景 2(1-2)”和“场景 2(3-2)”对比,我们可知:

REQUIRED 和 NESTED 修饰的内部方法都属于外围方法事务,如果外围方法抛出异常,这两种方法的事务都会被回滚。但是 REQUIRED 是加入外围方法事务,所以和外围事务同属于一个事务,一旦 REQUIRED 事务抛出异常被回滚,外围方法事务也将被回滚。而 NESTED 是外围方法的子事务,有单独的保存点,所以 NESTED 方法抛出异常被回滚,不会影响到外围方法的事务。

由“场景 2(2-2)”和“场景 2(3-2)”对比,我们可知:

REQUIRES_NEW 和 NESTED 都可以做到内部方法事务回滚而不影响外围方法事务。但是因为 NESTED 是嵌套事务,所以外围方法回滚之后,作为外围方法事务的子事务也会被回滚。而 REQUIRES_NEW 是通过开启新的事务实现的,内部事务和外围事务是两个事务,外围事务回滚不会影响内部事务。

其他几个传播行为

REQUIRED,REQUIRES_NEW,NESTED 这几个算是比较特殊的,比较常用的,剩下的 5 个传播行为,大家可以自己练练。

模拟用例

介绍了这么多事务传播行为,我们在实际工作中如何应用呢?下面我来举一个示例:

假设我们有一个注册的方法,方法中调用添加积分的方法,如果我们希望添加积分不会影响注册流程(即添加积分执行失败回滚不能使注册方法也回滚),我们会这样写:

@Service
public class UserServiceImpl implements UserService {

    @Transactional
    public void register(User user){

        try {
            membershipPointService.addPoint(Point point);
        } catch (Exception e) {
            //省略...
        }
        //省略...
    }
    //省略...
}

我们还规定注册失败要影响addPoint()方法(注册方法回滚添加积分方法也需要回滚),那么addPoint()方法就需要这样实现:

   @Service
   public class MembershipPointServiceImpl implements MembershipPointService{

        @Transactional(propagation = Propagation.NESTED)
        public void addPoint(Point point){
            try {
                recordService.addRecord(Record record);
            } catch (Exception e) {
               //省略...
            }
            //省略...
        }
        //省略...
   }

我们注意到了在addPoint()中还调用了addRecord()方法,这个方法用来记录日志。他的实现如下:

@Service
public class RecordServiceImpl implements RecordService{

    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void addRecord(Record record){

        //省略...
    }
    //省略...
}

我们注意到addRecord()方法中propagation = Propagation.NOT_SUPPORTED,因为对于日志无所谓精确,可以多一条也可以少一条,所以addRecord()方法本身和外围addPoint()方法抛出异常都不会使addRecord()方法回滚,并且addRecord()方法抛出异常也不会影响外围addPoint()方法的执行。

通过这个例子相信大家对事务传播行为的使用有了更加直观的认识,通过各种属性的组合确实能让我们的业务实现更加灵活多样。

总结

通过本文,相信大家对 spring 事务 7 种传播行为都有了深入的了解,希望对大家有所帮助,也欢迎大家加我微信itsoku交流!!!

案例源码

git地址:
https://gitee.com/javacode2018/spring-series

本文案例对应源码:spring-series\lesson-002-tx\src\main\java\com\javacode2018\tx\demo6
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值