对于mysql而言,关于事务的主要知识点可能几种在隔离级别上;在Spring体系中,使用事务的时候,还有一个知识点事务的传递属性一样重要,本文将主要介绍7中传递属性的使用场景mysql
<!-- more -->git
本文的case,将使用声明式事务,首先咱们建立一个SpringBoot项目,版本为2.2.1.RELEASE
,使用mysql做为目标数据库,存储引擎选择Innodb
,事务隔离级别为RRgithub
在项目pom.xml
文件中,加上spring-boot-starter-jdbc
,会注入一个DataSourceTransactionManager
的bean,提供了事务支持spring
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency>
进入spring配置文件application.properties
,设置一下db相关的信息sql
## 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=
新建一个简单的表结构,用于测试数据库
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;
在正式开始以前,得先准备一些基础数据app
@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方法spring-boot
@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; } }
也是默认的传递属性,其特色在于学习
使用方式也比较简单,不设置@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 ==========
其特色是在事务里面,就事务执行;不然就非事务执行,即
使用姿式和前面基本一致
@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 ==========
从上面的输出,也能够得出结果:非事务执行时,不会回滚;事务执行时,回滚
须要在一个正常的事务内执行,不然抛异常
使用姿式以下
@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 ==========
从上面的输出可知,直接抛出了异常,并不会执行方法内的逻辑
这个比较有意思,被它标记的方法,老是非事务地执行,若是存在活动事务,则挂起
(实在是没有想到,有什么场景须要这种传播属性)
一个简单的使用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 ==========
从上面输出能够看出
老是非事务地执行,若是存在一个活动事务,则抛出异常。
使用姿式以下
/** * 老是非事务地执行,若是存在一个活动事务,则抛出异常。 * * @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 ==========
直接抛出了异常,并无执行方法内的业务逻辑
其主要特色以下
上面提出了一个嵌套事务的概念,什么是嵌套事务呢?
接下来设计两个测试用例,一个是内部事务回滚;一个是外部事务回滚
@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 ==========
仔细看一下上面的结果,外部事务修改的结果都被保存了,内部事务的修改被回滚了,没有影响最终的结果
@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,其特别在于所有回滚了,内部事务的修改也被回滚了
这个和上面的NESTED有点类似,可是又不同
注意
REQUIRES_NEW
和NESTED
相比,两个事务之间没有关系,任何一个回滚,对另一个无影响
测试case和前面差很少,很少作细说...
前面介绍了7中传播属性,下面简单对比和小结一下
事务 | 特色 |
---|---|
REQUIRED | 默认,若是存在事务,则支持当前事务;不存在,则开启一个新事务 |
SUPPORTS | 若是存在一个事务,支持当前事务。若是没有事务,则非事务的执行 |
MANDATORY | 须要在一个正常的事务内执行,不然抛异常 |
REQUIRES_NEW | 无论存不存在事务,都开启一个新事务 |
NOT_SUPPORTED | 无论存不存在,都以非事务方式执行,当存在事务时,挂起事务 |
NEVER | 非事务方式执行,若是存在事务,则抛异常 |
NESTED | 若是不存在事务,则开启一个事务运行;若是存在事务,则运行一个嵌套事务 |
系列博文
源码
尽信书则不如,以上内容,纯属一家之言,因我的能力有限,不免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激
下面一灰灰的我的博客,记录全部学习和工做中的博文,欢迎你们前去逛逛