关键词:
对于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 ==========
");
}
@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_NEW
和NESTED
相比,两个事务之间没有关系,任何一个回滚,对另外一个无影响
测试case和前面差不多,不多做细说...
8. 小结
前面介绍了7中传播属性,下面简单对比和小结一下
事务 | 特点 |
---|---|
REQUIRED | 默认,如果存在事务,则支持当前事务;不存在,则开启一个新事务 |
SUPPORTS | 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行 |
MANDATORY | 需要在一个正常的事务内执行,否则抛异常 |
REQUIRES_NEW | 不管存不存在事务,都开启一个新事务 |
NOT_SUPPORTED | 不管存不存在,都以非事务方式执行,当存在事务时,挂起事务 |
NEVER | 非事务方式执行,如果存在事务,则抛异常 |
NESTED | 如果不存在事务,则开启一个事务运行;如果存在事务,则运行一个嵌套事务 |
II. 其他
0. 系列博文&源码
系列博文
- 180926-SpringBoot高级篇DB之基本使用
- 190407-SpringBoot高级篇JdbcTemplate之数据插入使用姿势详解
- 190412-SpringBoot高级篇JdbcTemplate之数据查询上篇
- 190417-SpringBoot高级篇JdbcTemplate之数据查询下篇
- 190418-SpringBoot高级篇JdbcTemplate之数据更新与删除
- 200119-SpringBoot系列教程之声明式事务Transactional
- 200120-SpringBoot系列教程之事务隔离级别知识点小结
源码
- 工程:https://github.com/liuyueyi/spring-boot-demo
- 实例源码: https://github.com/liuyueyi/spring-boot-demo/blob/master/spring-boot/101-jdbctemplate-transaction
1. 一灰灰Blog
尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激
下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛
- 一灰灰Blog个人博客 https://blog.hhui.top
- 一灰灰Blog-Spring专题博客 http://spring.hhui.top
springboot系列教程之编程式事务使用姿势介绍篇
SpringBoot系列教程之编程式事务使用姿势介绍篇前面介绍的几篇事务的博文,主要是利用@Transactional注解的声明式使用姿势,其好处在于使用简单,侵入性低,可辨识性高(一看就知道使用了事务);然而缺点也比较明显,不够灵... 查看详情
springboot系列教程之事务不生效的几种case
SpringBoot系列教程之事务不生效的几种case前面几篇博文介绍了声明式事务@Transactional的使用姿势,只知道正确的使用姿势可能还不够,还得知道什么场景下不生效,避免采坑。本文将主要介绍让事务不生效的几种caseI.配置本文的ca... 查看详情
springboot2系列教程|实现声明式事务
前言如题,今天介绍SpringBoot的声明式事务。Spring的事务机制所有的数据访问技术都有事务处理机制,这些技术提供了API用于开启事务、提交事务来完成数据操作,或者在发生错误时回滚数据。而Spring的事务机制是用统一的机制... 查看详情
springspring系列5之spring支持事务处理
5、Spring支持事务处理5.1、事务准备以上代码结构与AOP的前置通知、返回通知、异常通知、后置通知一样。5.2、声明式事务5.2.1、基于注解5.2.2、基于配置文件5.3、事务传播属性5.4、事务其他属性5.4.1、事务的隔离级别5.4.2、事物的... 查看详情
重学springboot系列之整合数据库开发框架---下(代码片段)
重学Springboot系列之整合数据库开发框架---下mybatis+atomikos实现分布式事务整合jta-atomikos配置多数据源统一事务管理器service层测试mybatisplus+atomikos实现分布式事务遗留问题整合jta-atomikos配置多数据源(调整)Spring事务... 查看详情
springboot系列springboot配置全局事务处理(代码片段)
六、SpringBoot配置全局事务处理6.1SringBoot声明式事务6.2@Transactional注解的常用属性:6.3事务传播行为propagation6.4配置全局事务6.1SringBoot声明式事务SpringBoot开启声明式事务,只需要一个@Transactional就可以。因为在SpringBoot... 查看详情
springboot系列之springdatamongodb教程
SpringBoot系列之SpringDataMongoDB教程1、MongoDB下载安装因为没有买linux服务器,所以本博客只安装window来学习,可以点击官网下载链接进行下载,安装过程略过客户端软件可以选择比较高版本的Navicat,比如Navicat15,... 查看详情
springboot进阶之事务管理及并发问题
...有的难点都离不开「基础知识」的铺垫。目前正在出一个SpringBoot长期系列教程,从入门到进阶,篇幅会较多~「大佬可以绕过~」如果你是一路看过来的,很高兴你能够耐心看完。之前带大家学了Springboot基础部分,对基本的使用有... 查看详情
springboot2.x系列教程48--多数据源配置之aop动态切换数据源
SpringBoot2.x系列教程48--多数据源配置之AOP动态切换数据源作者:一一哥在上一节中,我通过分包的方式实现了多数据源的配置,接下来我通过AOP切面的方式,带领大家实现第二种多数据源配置方式,该方式是在前面案例的基础上... 查看详情
springboot2.x系列教程48--多数据源配置之aop动态切换数据源
SpringBoot2.x系列教程48--多数据源配置之AOP动态切换数据源作者:一一哥在上一节中,我通过分包的方式实现了多数据源的配置,接下来我通过AOP切面的方式,带领大家实现第二种多数据源配置方式,该方式是在前面案例的基础上... 查看详情
springboot系列之@propertysource和@value注解
前言本文我们来看看在Spring中如何使用@PropertySource和@Value注解从属性文件读取值,同时呢我们也将讨论有关SpringEnvironment接口的信息以及相应的XML配置。@PropertySource注解主要使用Spring的Environment接口从属性文件中读取,事实上,... 查看详情
重学springboot系列之整合数据库开发框架---上(代码片段)
重学Springboot系列之整合数据库开发框架整合SpringJDBC操作数据jdbc简介使用jdbc操作数据库的步骤将SpringJDBC集成到Springboot项目springbootjdbc基础代码SpringJDBC多数据源的实现配置多个数据源通过JavaConfig将数据源注入到Spring上下文。Artic... 查看详情
springboot实战系列之完整参数校验案例
...会进入controller的方法体中,即没有走校验;传会走校验springboot关于controller层传递单个参数的校验JavaBeanValidation校验@PathVariable和@RequestParam解决办法:@ExceptionHandlervalue中的异常类要和方法体重的参数的异常类相同或者是其父类,... 查看详情
springboot系列:jdbctemplate事务控制
============================SpringJdbcTemplate事务控制============================之前使用JDBCAPI操作,经常用到的对象有:connection和preparedStatement. dbConnection.setAutoCommit(false);//transactionblockstart& 查看详情
事务并发之隔离级别
事务事务是作为单个逻辑工作单元执行的一系列操作。一个逻辑工作单元必须有四个属性,称为原子性、一致性、隔离性和持久性(ACID)属性,只有这样才能成为一个事务。事务并发数据库是多个用户(事务)共享的,当多个用户同... 查看详情
springboot2.0深度实践之核心技术篇
第1章系列总览总览SpringBoot2.0深度实践系列课程的整体议程,包括SpringBoot三大核心特性(组件自动装配、嵌入式Web容?、生产准备特性)、Web应用(传统Servlet、SpringWebMVC、SpringWebFlux)、数据相关(JDBC、JPA、事务)、功能扩展(Sp... 查看详情
springboot事务管理(中)(代码片段)
在上一篇 SpringBoot事务管理(上)的基础上介绍SpringBoot事务属性和事务回滚规则 。4 SpringBoot事务属性什么是事务属性呢?事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上。事务属性包含... 查看详情
springboot实战系列之@value注解的使用心得
参考技术A在工作中使用springboot经常有属性注入的场景,下面说一下有默认值和无默认值两种写法的不同这中是有默认值的写法,默认是分号后的值,这里为true,但是如果在配置文件中(application.properties或application.yml)中设置了app... 查看详情