200202-SpringBoot系列教程之事务传递属性

对于mysql而言,关于事务的主要知识点可能几种在隔离级别上;在Spring体系中,使用事务的时候,还有一个知识点事务的传递属性同样重要,本文将主要介绍7中传递属性的使用场景

I. 配置

本文的case,将使用声明式事务,首先我们创建一个SpringBoot项目,版本为2.2.1.RELEASE,使用mysql作为目标数据库,存储引擎选择Innodb,事务隔离级别为RR

1. 项目配置

在项目pom.xml文件中,加上spring-boot-starter-jdbc,会注入一个DataSourceTransactionManager的bean,提供了事务支持

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

2. 数据库配置

进入spring配置文件application.properties,设置一下db相关的信息

## DataSource
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/story?useUnicode=true&characterEncoding=UTF-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=

3. 数据库

新建一个简单的表结构,用于测试

CREATE TABLE `money` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL DEFAULT '' COMMENT '用户名',
`money` int(26) NOT NULL DEFAULT '0' COMMENT '钱',
`is_deleted` tinyint(1) NOT NULL DEFAULT '0',
`create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=551 DEFAULT CHARSET=utf8mb4;

II. 使用说明

0. 准备

在正式开始之前,得先准备一些基础数据

@Component
public class PropagationDemo {
@Autowired
private JdbcTemplate jdbcTemplate; @PostConstruct
public void init() {
String sql = "replace into money (id, name, money) values (420, '初始化', 200)," + "(430, '初始化', 200)," +
"(440, '初始化', 200)," + "(450, '初始化', 200)," + "(460, '初始化', 200)," + "(470, '初始化', 200)," +
"(480, '初始化', 200)," + "(490, '初始化', 200)";
jdbcTemplate.execute(sql);
}
}

其次测试事务的使用,我们需要额外创建一个测试类,后面的测试case都放在类PropagationSample中; 为了使输出结果更加友好,提供了一个封装的call方法

@Component
public class PropagationSample {
@Autowired
private PropagationDemo propagationDemo; private void call(String tag, int id, CallFunc<Integer> func) {
System.out.println("============ " + tag + " start ========== ");
propagationDemo.query(tag, id);
try {
func.apply(id);
} catch (Exception e) {
System.out.println(e.getMessage());
}
propagationDemo.query(tag, id);
System.out.println("============ " + tag + " end ========== \n");
} @FunctionalInterface
public interface CallFunc<T> {
void apply(T t) throws Exception;
}
}

1. REQUIRED

也是默认的传递属性,其特点在于

  • 如果存在一个事务,则在当前事务中运行
  • 如果没有事务则开启一个新的事务

使用方式也比较简单,不设置@Transactional注解的propagation属性,或者设置为 REQUIRED即可

/**
* 如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务
*
* @param id
*/
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void required(int id) throws Exception {
if (this.updateName(id)) {
this.query("required: after updateMoney name", id);
if (this.updateMoney(id)) {
return;
}
} throw new Exception("事务回滚!!!");
}

上面就是一个基础的使用姿势

private void testRequired() {
int id = 420;
call("Required事务运行", id, propagationDemo::required);
}

输出结果如下

============ Required事务运行 start ==========
Required事务运行 >>>> {id=420, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0}
required: after updateMoney name >>>> {id=420, name=更新, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:46.0}
事务回滚!!!
Required事务运行 >>>> {id=420, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0}
============ Required事务运行 end ==========

2. SUPPORTS

其特点是在事务里面,就事务执行;否则就非事务执行,即

  • 如果存在一个事务,支持当前事务
  • 如果没有事务,则非事务的执行

使用姿势和前面基本一致

@Transactional(propagation = Propagation.SUPPORTS, rollbackFor = Exception.class)
public void support(int id) throws Exception {
if (this.updateName(id)) {
this.query("support: after updateMoney name", id);
if (this.updateMoney(id)) {
return;
}
} throw new Exception("事务回滚!!!");
}

这个传递属性比较特别,所以我们的测试case需要两个,一个事务调用,一个非事务调用

测试事务调用时,我们新建一个bean: PropagationDemo2,下面的support方法支持事务运行

@Component
public class PropagationDemo2 {
@Autowired
private PropagationDemo propagationDemo; @Transactional(rollbackFor = Exception.class)
public void support(int id) throws Exception {
// 事务运行
propagationDemo.support(id);
}
}

对于非事务调用,则是直接在测试类中调用(请注意下面的call方法,调用的是两个不同bean中的support方法)

private void testSupport() {
int id = 430;
// 非事务方式,异常不会回滚
call("support无事务运行", id, propagationDemo::support); // 事务运行
id = 440;
call("support事务运行", id, propagationDemo2::support);
}

输出结果如下:

============ support无事务运行 start ==========
support无事务运行 >>>> {id=430, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0}
support: after updateMoney name >>>> {id=430, name=更新, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:46.0}
事务回滚!!!
support无事务运行 >>>> {id=430, name=更新, money=210, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:46.0}
============ support无事务运行 end ========== ============ support事务运行 start ==========
support事务运行 >>>> {id=440, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0}
support: after updateMoney name >>>> {id=440, name=更新, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:46.0}
事务回滚!!!
support事务运行 >>>> {id=440, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0}
============ support事务运行 end ==========

从上面的输出,也可以得出结果:非事务执行时,不会回滚;事务执行时,回滚

3. MANDATORY

需要在一个正常的事务内执行,否则抛异常

使用姿势如下

@Transactional(propagation = Propagation.MANDATORY, rollbackFor = Exception.class)
public void mandatory(int id) throws Exception {
if (this.updateName(id)) {
this.query("mandatory: after updateMoney name", id);
if (this.updateMoney(id)) {
return;
}
} throw new Exception("事务回滚!!!");
}

这种传播属性的特点是这个方法必须在一个已有的事务中运行,所以我们的测试case也比较简单,不再事务中运行时会怎样?

private void testMandatory() {
int id = 450;
// 非事务方式,抛异常,这个必须在一个事务内部执行
call("mandatory非事务运行", id, propagationDemo::mandatory);
}

输出结果

============ mandatory非事务运行 start ==========
mandatory非事务运行 >>>> {id=450, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0}
No existing transaction found for transaction marked with propagation 'mandatory'
mandatory非事务运行 >>>> {id=450, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0}
============ mandatory非事务运行 end ==========

从上面的输出可知,直接抛出了异常,并不会执行方法内的逻辑

4. NOT_SUPPORT

这个比较有意思,被它标记的方法,总是非事务地执行,如果存在活动事务,则挂起

(实在是没有想到,有什么场景需要这种传播属性)

一个简单的使用case如下:

@Transactional(propagation = Propagation.NOT_SUPPORTED, rollbackFor = Exception.class)
public void notSupport(int id) throws Exception {
if (this.updateName(id)) {
this.query("notSupport: after updateMoney name", id);
if (this.updateMoney(id)) {
return;
}
}
throw new Exception("回滚!");
}

接下来需要好好的想一下我们的测试用例,首先是它需要在一个事务中调用,外部事物失败回滚,并不会影响上面这个方法的执行结果

我们在PropagationDemo2中,添加测试case如下

@Transactional(rollbackFor = Exception.class)
public void notSupport(int id) throws Exception {
// 挂起当前事务,以非事务方式运行
try {
propagationDemo.notSupport(id);
} catch (Exception e) {
} propagationDemo.query("notSupportCall: ", id);
propagationDemo.updateName(id, "外部更新");
propagationDemo.query("notSupportCall: ", id);
throw new Exception("回滚");
}

输出结果如下

============ notSupport start ==========
notSupport >>>> {id=460, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0}
notSupport: after updateMoney name >>>> {id=460, name=更新, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:46.0}
notSupportCall: >>>> {id=460, name=更新, money=210, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:46.0}
notSupportCall: >>>> {id=460, name=外部更新, money=210, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:46.0}
回滚
notSupport >>>> {id=460, name=更新, money=210, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:46.0}
============ notSupport end ==========

从上面输出可以看出

  • NOT_SUPPORT 标记的方法,属于非事务运行(因为抛异常,修改没有回滚)
  • 外部事务回滚,不会影响其修改

5. NEVER

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

使用姿势如下

/**
* 总是非事务地执行,如果存在一个活动事务,则抛出异常。
*
* @param id
* @throws Exception
*/
@Transactional(propagation = Propagation.NEVER, rollbackFor = Exception.class)
public void never(int id) throws Exception {
if (this.updateName(id)) {
this.query("notSupport: after updateMoney name", id);
if (this.updateMoney(id)) {
return;
}
}
}

我们的测试就比较简单了,如果在事务中运行,是不是会抛异常

PropagationDemo2中,添加一个事务调用方法

@Transactional(rollbackFor = Exception.class)
public void never(int id) throws Exception {
propagationDemo.never(id);
}

测试代码

private void testNever() {
int id = 470;
call("never非事务", id, propagationDemo2::never);
}

输出结果

============ never非事务 start ==========
never非事务 >>>> {id=470, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0}
Existing transaction found for transaction marked with propagation 'never'
never非事务 >>>> {id=470, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0}
============ never非事务 end ==========

直接抛出了异常,并没有执行方法内的业务逻辑

6. NESTED

其主要特点如下

  • 如果不存在事务,则开启一个事务运行
  • 如果存在事务,则运行一个嵌套事务;

上面提出了一个嵌套事务的概念,什么是嵌套事务呢?

  • 一个简单的理解:外部事务回滚,内部事务也会被回滚;内部事务回滚,外部无问题,并不会回滚外部事务

接下来设计两个测试用例,一个是内部事务回滚;一个是外部事务回滚

a. case1 内部事务回滚

@Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)
public void nested(int id) throws Exception {
if (this.updateName(id)) {
this.query("nested: after updateMoney name", id);
if (this.updateMoney(id)) {
return;
}
} throw new Exception("事务回滚!!!");
}

PropagationDemo2这个bean中,添加一个外部事务,捕获上面方法的异常,因此外部执行正常

@Transactional(rollbackFor = Exception.class)
public void nested(int id) throws Exception {
propagationDemo.updateName(id, "外部事务修改");
propagationDemo.query("nestedCall: ", id);
try {
propagationDemo.nested(id);
} catch (Exception e) {
}
}

测试代码

private void testNested() {
int id = 480;
call("nested事务", id, propagationDemo2::nested);
}

输出结果如下

============ nested事务 start ==========
nested事务 >>>> {id=480, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0}
nestedCall: >>>> {id=480, name=外部事务修改, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:46.0}
nested: after updateMoney name >>>> {id=480, name=更新, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:46.0}
nested事务 >>>> {id=480, name=外部事务修改, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:46.0}
============ nested事务 end ==========

仔细看一下上面的结果,外部事务修改的结果都被保存了,内部事务的修改被回滚了,没有影响最终的结果

b. case2 外部事务回滚

@Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)
public void nested2(int id) throws Exception {
if (this.updateName(id)) {
this.query("nested: after updateMoney name", id);
if (this.updateMoney(id)) {
return;
}
}
}

PropagationDemo2这个bean中,添加一个外部事务,内部事务正常,但是外部事务抛异常,主动回滚

@Transactional(rollbackFor = Exception.class)
public void nested2(int id) throws Exception {
// 嵌套事务,外部回滚,会同步回滚内部事务
propagationDemo.updateName(id, "外部事务修改");
propagationDemo.query("nestedCall: ", id);
propagationDemo.nested2(id);
throw new Exception("事务回滚");
}

测试代码

private void testNested() {
int id = 490;
call("nested事务2", id, propagationDemo2::nested2);
}

输出结果如下

============ nested事务2 start ==========
nested事务2 >>>> {id=490, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0}
nestedCall: >>>> {id=490, name=外部事务修改, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:46.0}
nested: after updateMoney name >>>> {id=490, name=更新, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:46.0}
事务回滚
nested事务2 >>>> {id=490, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0}
============ nested事务2 end ==========

仔细看上面的输出,对别case1,其特别在于全部回滚了,内部事务的修改也被回滚了

7. REQUIRES_NEW

这个和上面的NESTED有点相似,但是又不一样

  • 当存在活动事务时,新创建一个事务执行
  • 当不存在活动事务时,和REQUIRES效果一致,创建一个事务执行

注意

REQUIRES_NEWNESTED相比,两个事务之间没有关系,任何一个回滚,对另外一个无影响

测试case和前面差不多,不多做细说...

8. 小结

前面介绍了7中传播属性,下面简单对比和小结一下

事务 特点
REQUIRED 默认,如果存在事务,则支持当前事务;不存在,则开启一个新事务
SUPPORTS 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行
MANDATORY 需要在一个正常的事务内执行,否则抛异常
REQUIRES_NEW 不管存不存在事务,都开启一个新事务
NOT_SUPPORTED 不管存不存在,都以非事务方式执行,当存在事务时,挂起事务
NEVER 非事务方式执行,如果存在事务,则抛异常
NESTED 如果不存在事务,则开启一个事务运行;如果存在事务,则运行一个嵌套事务

II. 其他

0. 系列博文&源码

系列博文

源码

1. 一灰灰Blog

尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

SpringBoot系列教程之事务传递属性的更多相关文章

  1. SpringBoot 系列教程之事务不生效的几种 case

    SpringBoot 系列教程之事务不生效的几种 case 前面几篇博文介绍了声明式事务@Transactional的使用姿势,只知道正确的使用姿势可能还不够,还得知道什么场景下不生效,避免采坑.本文 ...

  2. SpringBoot 系列教程之事务隔离级别知识点小结

    SpringBoot 系列教程之事务隔离级别知识点小结 上一篇博文介绍了声明式事务@Transactional的简单使用姿势,最文章的最后给出了这个注解的多个属性,本文将着重放在事务隔离级别的知识点上 ...

  3. SpringBoot 系列教程之编程式事务使用姿势介绍篇

    SpringBoot 系列教程之编程式事务使用姿势介绍篇 前面介绍的几篇事务的博文,主要是利用@Transactional注解的声明式使用姿势,其好处在于使用简单,侵入性低,可辨识性高(一看就知道使用 ...

  4. Java工程师之SpringBoot系列教程前言&目录

    前言 与时俱进是每一个程序员都应该有的意识,当一个Java程序员在当代步遍布的时候,你就行该想到我能多学点什么.可观的是后端的框架是稳定的,它们能够维持更久的时间在应用中,而不用担心技术的更新换代.但 ...

  5. SpringBoot系列教程起步

    本篇学习目标 Spring Boot是什么? 构建Spring Boot应用程序 三分钟开发SpringBoot应用程序 本章源码下载 Spring Boot是什么? spring Boot是由Piv ...

  6. SpringBoot系列教程web篇之过滤器Filter使用指南

    web三大组件之一Filter,可以说是很多小伙伴学习java web时最早接触的知识点了,然而学得早不代表就用得多.基本上,如果不是让你从0到1写一个web应用(或者说即便从0到1写一个web应用) ...

  7. SpringBoot系列教程JPA之新增记录使用姿势

    SpringBoot系列教程JPA之新增记录使用姿势 上一篇文章介绍了如何快速的搭建一个JPA的项目环境,并给出了一个简单的演示demo,接下来我们开始业务教程,也就是我们常说的CURD,接下来进入第 ...

  8. springBoot系列教程07:异常捕获

    发生异常是很正常的事,异常种类也是千奇百怪,发生异常并不可怕,只要正确的处理,并正确的返回错误信息并无大碍,如果不进行捕获或者处理,分分钟服务器宕机是很正常的事 所以处理异常时,最基本的要求就是发生异 ...

  9. SpringBoot系列教程web篇之自定义异常处理HandlerExceptionResolver

    关于Web应用的全局异常处理,上一篇介绍了ControllerAdvice结合@ExceptionHandler的方式来实现web应用的全局异常管理: 本篇博文则带来另外一种并不常见的使用方式,通过实 ...

随机推荐

  1. jmeter安装配置教程及使用

    背景: 因为双11,黑五快到了,所有的互联网电商行业都要做一件事情,那就是压测,常见的压测很多区分,接口压测和全链路压测.线上压测和线下压测,单元压测和功能压测.我们这里介绍一下接口压测和全链路压测. ...

  2. Mysql(超级详细)

    Mysql(超级详细) (黑小子-余) 一.Mysql介绍 MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,属于 Oracle 旗下产品.MySQL 是最流行的关系型数据库管理 ...

  3. 专业版12.0试用,打开演示账套,提示&ldquo;列名FPlatver无效&rdquo;

    你好,我代表研发来说明一下这个问题:一.产生的原因:由于KIS产品今年陆续都增加了应用平台,前期平台兼容性还有些问题,如果一台电脑有多个不同版本的平台,就会产生冲突,因此报错.1.可能您的机器装了多个 ...

  4. DOCKER学习_002:Docker的容器管理

    一 Docker的基本信息 前面已经安装了Docker,现在看一下已安装Docker的安装环境以及其他信息 1.1 系统环境 [root@docker-server3 ~]# uname -r -.e ...

  5. ubuntu手动升级系统

    之前自己安装的是ubuntu14.04,现在需要升级到16.04,于是上网搜索了一下升级步骤以及相关命令,将这些整理出来分享给大家,希望能够给大家提供帮助. 1.更新资源: sudo apt-get ...

  6. # Okhttp解析—Interceptor详解

    Okhttp解析-Interceptor详解 Interceptor可以说是okhttp的精髓之一,Okhttp重写请求/响应.重试.缓存响应等操作,基本都是在各个Interceptor中完成的,上篇 ...

  7. Linux 查看实时网卡流量的方法 sar nload iftop

    1.sar 计量脚本 sar(System Activity Reporter 系统活动情况报告)是目前 Linux 上最为全面的系统性能分析工具之一,可以从多方面对系统的活动进行报告,包括:文件的读 ...

  8. java基础之----分布式事务tcc

    最近研究了一下分布式事务框架,ttc,总体感觉还可以,当然前提条件下是你要会使用这个框架.下面分层次讲,尽量让想学习的同学读了这篇文章能加以操作运用.我不想废话,直接上干货. 一.什么是tcc?干什么 ...

  9. kubernetes基础——一文读懂k8s

    容器 容器与虚拟机对比图(左边为容器.右边为虚拟机)   容器技术是虚拟化技术的一种,以Docker为例,Docker利用Linux的LXC(LinuX Containers)技术.CGroup(Co ...

  10. Notepad++中安装json格式化插件

    在线工具固然好,一旦没网就凉凉 Notepad++编辑器中提供了 json 数据格式化显示的插件 安装插件过程如下: 注意: 安装过程需要联网状态 插件安装过程会自动退出程序,等待几秒钟后插件安装完成 ...