目录
一, 什么是事务传播行为?
二, 事务的 7 种传播行为
三, 7 种传播行为实战
本文介绍 Spring 的七种事务传播行为并通过代码演示下.
一, 什么是事务传播行为?
事务传播行为 (propagation behavior) 指的就是当一个事务方法被另一个事务方法调用时, 这个事务方法应该如何运行.
例如: methodA 方法调用 methodB 方法时, methodB 是继续在调用者 methodA 的事务中运行呢, 还是为自己开启一个新事务运行, 这就是由 methodB 的事务传播行为决定的.
二, 事务的 7 种传播行为
Spring 在 TransactionDefinition 接口中规定了 7 种类型的事务传播行为. 事务传播行为是 Spring 框架独有的事务增强特性. 这是 Spring 为我们提供的强大的工具箱, 使用事务传播行为可以为我们的开发工作提供许多便利.
7 种事务传播行为如下:
1.PROPAGATION_REQUIRED
如果当前没有事务, 就创建一个新事务, 如果当前存在事务, 就加入该事务, 这是最常见的选择, 也是 Spring 默认的事务传播行为.
2.PROPAGATION_SUPPORTS
支持当前事务, 如果当前存在事务, 就加入该事务, 如果当前不存在事务, 就以非事务执行.
3.PROPAGATION_MANDATORY
支持当前事务, 如果当前存在事务, 就加入该事务, 如果当前不存在事务, 就抛出异常.
4.PROPAGATION_REQUIRES_NEW
创建新事务, 无论当前存不存在事务, 都创建新事务.
5.PROPAGATION_NOT_SUPPORTED
以非事务方式执行操作, 如果当前存在事务, 就把当前事务挂起.
6.PROPAGATION_NEVER
以非事务方式执行, 如果当前存在事务, 则抛出异常.
7.PROPAGATION_NESTED
如果当前存在事务, 则在嵌套事务内执行. 如果当前没有事务, 则按 REQUIRED 属性执行.
其实这 7 中我也没看懂, 不过不急, 咱们接下来直接看效果.
三, 7 种传播行为实战
演示前先建两个表, 用户表和用户角色表, 一开始两个表里没有数据.
需要注意下, 为了数据更直观, 每次执行代码时 先清空下 user 和 user_role 表的数据.
user 表:
- CREATE TABLE `user` (
- `id` int(11) NOT NULL AUTO_INCREMENT,
- `name` varchar(255) DEFAULT NULL,
- `password` varchar(255) DEFAULT NULL,
- `sex` int(11) DEFAULT NULL,
- `des` varchar(255) DEFAULT NULL,
- PRIMARY KEY (`id`)
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
user_role 表:
- CREATE TABLE `user_role` (
- `id` int(11) NOT NULL AUTO_INCREMENT,
- `user_id` int(11) DEFAULT NULL,
- `role_id` int(11) DEFAULT NULL,
- PRIMARY KEY (`id`)
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
1.PROPAGATION_REQUIRED 测试
如果当前没有事务, 就创建一个新事务, 如果当前存在事务, 就加入该事务, 这是最常见的选择, 也是 Spring 默认的事务传播行为.
场景一:
此场景外围方法没有开启事务.
1. 验证方法
两个实现类 UserServiceImpl 和 UserRoleServiceImpl 制定事物传播行为 propagation=Propagation.REQUIRED, 然后在测试方法中同时调用两个方法并在调用结束后抛出异常.
2. 主要代码
外层调用方法代码:
- /**
- * 测试 PROPAGATION_REQUIRED
- *
- * @Author: java_suisui
- */
- @Test
- void test_PROPAGATION_REQUIRED() {
- // 增加用户表
- User user = new User();
- user.setName("Java 碎碎念");
- user.setPassword("123456");
- userService.add(user);
- // 增加用户角色表
- UserRole userRole = new UserRole();
- userRole.setUserId(user.getId());
- userRole.setRoleId(200);
- userRoleService.add(userRole);
- // 抛异常
- throw new RuntimeException();
- }
UserServiceImpl 代码:
- /**
- * 增加用户
- */
- @Transactional(propagation = Propagation.REQUIRED)
- @Override
- public int add(User user) {
- return userMapper.add(user);
- }
UserRoleServiceImpl 代码:
- /**
- * 增加用户角色
- */
- @Transactional(propagation = Propagation.REQUIRED)
- @Override
- public int add(UserRole userRole) {
- return userRoleMapper.add(userRole);
- }
3. 代码执行后数据库截图
两张表数据都新增成功, 截图如下:
4. 结果分析
外围方法未开启事务, 插入用户表和用户角色表的方法在自己的事务中独立运行, 外围方法异常不影响内部插入, 所以两条记录都新增成功.
场景二:
此场景外围方法开启事务.
1. 主要代码
测试方法代码如下:
- /**
- * 测试 PROPAGATION_REQUIRED
- *
- * @Author: java_suisui
- */
- @Transactional
- @Test
- void test_PROPAGATION_REQUIRED() {
- // 增加用户表
- User user = new User();
- user.setName("Java 碎碎念");
- user.setPassword("123456");
- userService.add(user);
- // 增加用户角色表
- UserRole userRole = new UserRole();
- userRole.setUserId(user.getId());
- userRole.setRoleId(200);
- userRoleService.add(userRole);
- // 抛异常
- throw new RuntimeException();
- }
2. 代码执行后数据库截图
两张表数据都为空, 截图如下:
3. 结果分析
外围方法开启事务, 内部方法加入外围方法事务, 外围方法回滚, 内部方法也要回滚, 所以两个记录都插入失败.
结论: 以上结果证明在外围方法开启事务的情况下 Propagation.REQUIRED 修饰的内部方法会加入到外围方法的事务中, 所以 Propagation.REQUIRED 修饰的内部方法和外围方法均属于同一事务, 只要一个方法回滚, 整个事务均回滚.
2.PROPAGATION_SUPPORTS 测试
支持当前事务, 如果当前存在事务, 就加入该事务, 如果当前不存在事务, 就以非事务执行.
场景一:
此场景外围方法没有开启事务.
1. 验证方法
两个实现类 UserServiceImpl 和 UserRoleServiceImpl 制定事物传播行为 propagation=Propagation.SUPPORTS, 然后在测试方法中同时调用两个方法并在调用结束后抛出异常.
2. 主要代码
外层调用方法代码:
- /**
- * 测试 PROPAGATION_SUPPORTS
- *
- * @Author: java_suisui
- */
- @Test
- void test_PROPAGATION_SUPPORTS() {
- // 增加用户表
- User user = new User();
- user.setName("Java 碎碎念");
- user.setPassword("123456");
- userService.add(user);
- // 增加用户角色表
- UserRole userRole = new UserRole();
- userRole.setUserId(user.getId());
- userRole.setRoleId(200);
- userRoleService.add(userRole);
- // 抛异常
- throw new RuntimeException();
- }
UserServiceImpl 代码:
- /**
- * 增加用户
- */
- @Transactional(propagation = Propagation.SUPPORTS)
- @Override
- public int add(User user) {
- return userMapper.add(user);
- }
UserRoleServiceImpl 代码:
- /**
- * 增加用户角色
- */
- @Transactional(propagation = Propagation.SUPPORTS)
- @Override
- public int add(UserRole userRole) {
- return userRoleMapper.add(userRole);
- }
3. 代码执行后数据库截图
两张表数据都新增成功, 截图如下:
4. 结果分析
外围方法未开启事务, 插入用户表和用户角色表的方法以非事务的方式独立运行, 外围方法异常不影响内部插入, 所以两条记录都新增成功.
场景二:
此场景外围方法开启事务.
1. 主要代码
test_PROPAGATION_SUPPORTS 方法添加注解 @Transactional 即可.
2. 代码执行后数据库截图
两张表数据都为空, 截图如下:
3. 结果分析
外围方法开启事务, 内部方法加入外围方法事务, 外围方法回滚, 内部方法也要回滚, 所以两个记录都插入失败.
结论: 以上结果证明在外围方法开启事务的情况下 Propagation.SUPPORTS 修饰的内部方法会加入到外围方法的事务中, 所以 Propagation.SUPPORTS 修饰的内部方法和外围方法均属于同一事务, 只要一个方法回滚, 整个事务均回滚.
3.PROPAGATION_MANDATORY 测试
支持当前事务, 如果当前存在事务, 就加入该事务, 如果当前不存在事务, 就抛出异常.
通过上面的测试,"支持当前事务, 如果当前存在事务, 就加入该事务", 这句话已经验证了, 外层添加 @Transactional 注解后两条记录都新增失败, 所以这个传播行为只测试下外层没有开始事务的场景.
场景一:
此场景外围方法没有开启事务.
1. 验证方法
两个实现类 UserServiceImpl 和 UserRoleServiceImpl 制定事物传播行为 propagation = Propagation.MANDATORY, 主要代码如下.
2. 主要代码
外层调用方法代码:
- /**
- * 测试 PROPAGATION_MANDATORY
- *
- * @Author: java_suisui
- */
- @Test
- void test_PROPAGATION_MANDATORY() {
- // 增加用户表
- User user = new User();
- user.setName("Java 碎碎念");
- user.setPassword("123456");
- userService.add(user);
- // 增加用户角色表
- UserRole userRole = new UserRole();
- userRole.setUserId(user.getId());
- userRole.setRoleId(200);
- userRoleService.add(userRole);
- // 抛异常
- throw new RuntimeException();
- }
UserServiceImpl 代码:
- /**
- * 增加用户
- */
- @Transactional(propagation = Propagation.MANDATORY)
- @Override
- public int add(User user) {
- return userMapper.add(user);
- }
UserRoleServiceImpl 代码:
- /**
- * 增加用户角色
- */
- @Transactional(propagation = Propagation.MANDATORY)
- @Override
- public int add(UserRole userRole) {
- return userRoleMapper.add(userRole);
- }
3. 代码执行后数据库截图
两张表数据都为空, 截图如下:
4. 结果分析
运行日志如下, 可以发现在调用 userService.add()时候已经报错了, 所以两个表都没有新增数据, 验证了 "如果当前不存在事务, 就抛出异常".
- at com.example.springboot.mybatisannotation.service.impl.UserServiceImpl$$EnhancerBySpringCGLIB$$50090f18.add(<generated>)
- at com.example.springboot.mybatisannotation.SpringBootMybatisAnnotationApplicationTests.test_PROPAGATION_MANDATORY(SpringBootMybatisAnnotationApplicationTests.java:78)
- at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
- at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
- at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
- at java.lang.reflect.Method.invoke(Method.java:498)
4.PROPAGATION_REQUIRES_NEW 测试
创建新事务, 无论当前存不存在事务, 都创建新事务.
这种情况每次都创建事务, 所以我们验证一种情况即可.
场景一:
此场景外围方法开启事务.
1. 验证方法
两个实现类 UserServiceImpl 和 UserRoleServiceImpl 制定事物传播行为 propagation = Propagation.REQUIRES_NEW, 主要代码如下.
2. 主要代码
外层调用方法代码:
- /**
- * 测试 REQUIRES_NEW
- *
- * @Author: java_suisui
- */
- @Test
- @Transactional
- void test_REQUIRES_NEW() {
- // 增加用户表
- User user = new User();
- user.setName("Java 碎碎念");
- user.setPassword("123456");
- userService.add(user);
- // 增加用户角色表
- UserRole userRole = new UserRole();
- userRole.setUserId(user.getId());
- userRole.setRoleId(200);
- userRoleService.add(userRole);
- // 抛异常
- throw new RuntimeException();
- }
UserServiceImpl 代码:
- /**
- * 增加用户
- */
- @Transactional(propagation = Propagation.REQUIRES_NEW)
- @Override
- public int add(User user) {
- return userMapper.add(user);
- }
UserRoleServiceImpl 代码:
- /**
- * 增加用户角色
- */
- @Transactional(propagation = Propagation.REQUIRES_NEW)
- @Override
- public int add(UserRole userRole) {
- return userRoleMapper.add(userRole);
- }
3. 代码执行后数据库截图
两张表数据都新增成功, 截图如下:
4. 结果分析
无论当前存不存在事务, 都创建新事务, 所以两个数据新增成功.
5.PROPAGATION_NOT_SUPPORTED 测试
以非事务方式执行操作, 如果当前存在事务, 就把当前事务挂起.
场景一:
此场景外围方法不开启事务.
1. 验证方法
两个实现类 UserServiceImpl 和 UserRoleServiceImpl 制定事物传播行为 propagation = Propagation.NOT_SUPPORTED, 主要代码如下.
2. 主要代码
外层调用方法代码:
- /**
- * 测试 PROPAGATION_NOT_SUPPORTED
- *
- * @Author: java_suisui
- */
- @Test
- void test_PROPAGATION_NOT_SUPPORTED() {
- // 增加用户表
- User user = new User();
- user.setName("Java 碎碎念");
- user.setPassword("123456");
- userService.add(user);
- // 增加用户角色表
- UserRole userRole = new UserRole();
- userRole.setUserId(user.getId());
- userRole.setRoleId(200);
- userRoleService.add(userRole);
- // 抛异常
- throw new RuntimeException();
- }
UserServiceImpl 代码:
- /**
- * 增加用户
- */
- @Transactional(propagation = Propagation.NOT_SUPPORTED)
- @Override
- public int add(User user) {
- return userMapper.add(user);
- }
UserRoleServiceImpl 代码:
- /**
- * 增加用户角色
- */
- @Transactional(propagation = Propagation.NOT_SUPPORTED)
- @Override
- public int add(UserRole userRole) {
- return userRoleMapper.add(userRole);
- }
3. 代码执行后数据库截图
两张表数据都新增成功, 截图如下:
4. 结果分析
以非事务方式执行, 所以两个数据新增成功.
场景二:
此场景外围方法开启事务.
1. 主要代码
test_PROPAGATION_NOT_SUPPORTED 方法添加注解 @Transactional 即可.
2. 代码执行后数据库截图
两张表数据都新增成功, 截图如下:
3. 结果分析
如果当前存在事务, 就把当前事务挂起, 相当于以非事务方式执行, 所以两个数据新增成功.
6.PROPAGATION_NEVER 测试
以非事务方式执行, 如果当前存在事务, 则抛出异常.
上面已经有类似情况, 外层没有事务会以非事务的方式运行, 两个表新增成功; 有事务则抛出异常, 两个表都都没有新增数据.
7.PROPAGATION_NESTED 测试
如果当前存在事务, 则在嵌套事务内执行. 如果当前没有事务, 则按 REQUIRED 属性执行.
上面已经有类似情况, 外层没有事务会以 REQUIRED 属性的方式运行, 两个表新增成功; 有事务但是用的是一个事务, 方法最后抛出了异常导致回滚, 两个表都都没有新增数据.
到此 Spring 的 7 种事务传播行为已经全部介绍完成了, 有问题欢迎留言沟通哦!
完整源码地址: https://github.com/suisui2019/springboot-study
来源: https://www.cnblogs.com/haha12/p/11855001.html