文章目录
- 一、概念
- 二、为什么需要事务传播机制?
- 三、事务传播机制有哪些?
- 四、Spring 事务传播机制使⽤和各种场景演示
- 4.1 ⽀持当前事务(REQUIRED)
- 4.2 不⽀持当前事务(REQUIRES_NEW)
- 4.3 NESTED 嵌套事务
- 4.4 嵌套事务和加⼊事务的区别
一、概念
Spring 事务传播机制是指多个包含事务的⽅法相互调⽤时,事务是如何在这些⽅法间进⾏传递的
二、为什么需要事务传播机制?
事务隔离级别是保证多个并发事务执⾏的可控性(稳定性的),⽽事务传播机制是保证⼀个事务在多个调⽤⽅法间传递的可控性
事务隔离级别解决的是多个事务同时调⽤⼀个数据库的问题,如下图所示:
⽽事务传播机制解决的是⼀个事务在多个节点(⽅法)中传递的问题,如下图所示:
三、事务传播机制有哪些?
Spring 事务传播机制包含以下 7 种:
- Propagation.REQUIRED:默认的事务传播级别,它表示如果当前存在事务,则加⼊该事务;如果当前没有事务,则创建⼀个新的事务。
- Propagation.SUPPORTS:如果当前存在事务,则加⼊该事务;如果当前没有事务,则以⾮事务的⽅式继续运⾏。
- Propagation.MANDATORY:(mandatory:强制性)如果当前存在事务,则加⼊该事务;如果当前没有事务,则抛出异常。
- Propagation.REQUIRES_NEW:表示创建⼀个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部⽅法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部⽅法会新开启⾃⼰的事务,且开启的事务相互独⽴,互不⼲扰。
- Propagation.NOT_SUPPORTED:以⾮事务⽅式运⾏,如果当前存在事务,则把当前事务挂起。
- Propagation.NEVER:以⾮事务⽅式运⾏,如果当前存在事务,则抛出异常。
- Propagation.NESTED:如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来运⾏;如果当前没有事务,则该取值等价于 PROPAGATION_REQUIRED。
以上 7 种传播⾏为,可以根据是否⽀持当前事务分为以下 3 类:
如何理解当前事务?举个例子:
方法A(包含事务a) 去调用 方法B(包含事务b) ,此时A就是外部方法,而a就是外部方法事务即所谓的当前事务,而B就是内部方法,b就是内部方法事务。所以所谓的当前事务,就是外部方法的事务
上面说的很抽象,我们通过具体的代码来说明,如下
四、Spring 事务传播机制使⽤和各种场景演示
目标实现:当在用户表中添加一条数据后,相应的日志表也会记录一条消息
正确的业务代码是当我添加用户成功时就会自动添加一条日志,所以直接在一个service里调用这两个添加接口就行了。而下面我们为了验证事务的传播机制特意将用户添加和日志添加方法分开,分别在 controller 中调用 !!
准备工作:
-
创建用户表和日志表
-
准备相应的实体类
-
mapper 代码实现
-
xml 文件实现
<insert id="add">
insert into loginfo(name,description)
values (#{name}, #{description})
</insert>
<insert id="add">
insert into userinfo(username,password) values (#{username},#{password})
</insert>
- controller 代码实现
@RestController
public class UserController {
@Resource
private UserService userService;
@Resource
private LogService logService;
@Transactional(propagation = Propagation.REQUIRED) //默认就是 REQUIRED
@RequestMapping("/add3")
public int add3(UserInfo userInfo) {
if (userInfo == null ||
!StringUtils.hasLength(userInfo.getUsername()) ||
!StringUtils.hasLength(userInfo.getPassword())) {
return 0;
}
int userResult = userService.add(userInfo);
System.out.println("添加用户:" + userResult);
LogInfo logInfo = new LogInfo();
logInfo.setName("添加用户的姓名");
logInfo.setDescription("添加用户结果:" + userResult);
int logResult = logService.add(logInfo);
return userResult;
}
}
具体 service 代码实现,以及验证结果
4.1 ⽀持当前事务(REQUIRED)
以下代码实现中,先开启事务先成功插⼊⼀条⽤户数据,然后再执⾏⽇志报错,⽽在⽇志报错是发⽣了异常,观察 propagation = Propagation.REQUIRED 的执⾏结果
UserService 实现代码如下:
@Service
public class UserService {
@Resource
private UserInfoMapper userInfoMapper;
@Transactional(propagation = Propagation.REQUIRED)
public int add(UserInfo userInfo){
return userInfoMapper.add(userInfo);
}
}
LogService 实现代码如下:
@Service
public class LogService {
@Resource
private LogInfoMapper logInfoMapper;
@Transactional(propagation = Propagation.REQUIRED)
public int add(LogInfo logInfo) {
int result = logInfoMapper.add(logInfo);
System.out.println("添加日志结果:" + result);
int number = 10 / 0;
return result;
}
}
验证前的数据表:
通过 url 访问,控制台结果:
再次查看数据表:
我们发现,两张数据表都没有成功插入数据,没有存在异常的添加用户操作也同样失败了。这是为什么呢?
这是因为我们将两个添加方法上的事务都设置成了 REQUIRED ,而在前面我们知道了它表示如果当前存在事务,则加⼊该事务;如果当前没有事务,则创建⼀个新的事务。而 controller 中的添加方法我们设置了事务的,该添加方法调用了用户添加和日志添加方法,所以他们两的事务直接加入到了外部方法事务中,成为了一个整体!现在他们处于同一事务中,当添加日志操作出现异常后进行回滚操作,整个事务都会实现回滚操作,所以用户添加操作也会回滚,用户添加操作失败
4.2 不⽀持当前事务(REQUIRES_NEW)
UserController 类中的代码不变,将添加⽤户和添加⽇志的⽅法修改为 REQUIRES_NEW 不⽀持当前事务,重新创建事务,观察执⾏结果
重复代码就不写了,只需要将两个添加方法中的事务的传播机制设置为 REQUIRES_NEW
通过 url 访问,控制台结果:
查看数据表结果
我们发现,用户表中成功插入了数据,而日志表中还是为空!!
分析如下,我们将两个添加方法的事务传播机制设置为了 REQUIRES_NEW,它表示创建⼀个新的事务,如果当前存在事务,则把当前事务挂起。即外部方法事务是否存在已经不重要了,两个添加方法都创建了各自的事务,且互不干扰。所以添加日志操作出现异常,它的事务就会进行回滚操作,而该回滚操作并不会影响处于另一个事务的添加用户操作 ! 所以用户添加能够成功,而日志添加失败
4.3 NESTED 嵌套事务
老样子,重复代码就不写了,只需要将两个添加方法中的事务的传播机制设置为 NESTED
并使用 try…catch 将添加日志中的异常捕获(使用 NESTED 的前提) 如下:
logservice 代码修改
@Service
public class LogService {
@Resource
private LogInfoMapper logInfoMapper;
@Transactional(propagation = Propagation.NESTED)
public int add(LogInfo logInfo) {
int result = logInfoMapper.add(logInfo);
System.out.println("添加日志结果:" + result);
try {
int number = 10 / 0;
} catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return result;
}
}
通过 url 访问,控制台结果:
查看数据表结果:
用户添加操作成功,而日志添加操作失败 !!由此可见,日志添加事务已经回滚,但是该嵌套事务不会回滚嵌套之前的事务即外部方法事务,也就是说嵌套事务可以实现部分事务回滚!!
那嵌套事务是如何实现部分事务回滚的呢?嵌套事务只所以能够实现部分事务的回滚,是因为事务中有⼀个保存点(savepoint),嵌套事务进⼊之后相当于新建了⼀个保存点,⽽滚回时只回滚到当前保存点,因此之前的事务是不受影响的。⽽ REQUIRED 是加⼊到当前事务中,并没有创建事务的保存点,因此出现了回滚就是整个事务回滚,这就是嵌套事务和加⼊事务的区别
(重要) 前面我们说使用 try…catch 将添加日志中的异常捕获是使用 NESTED 的前提,如果不这样做的话日志添加中的回滚操作会持续往上找调⽤它的⽅法和事务进行回滚,最终我们的外部方法事务也会被回滚,所以即使用户添加操作没有异常出现,该操作也会被回滚,最终的结果是⽤户表和⽇志表都没有添加任何数据 !!
与嵌套事务相关文档 !!!
4.4 嵌套事务和加⼊事务的区别
前面我们分别演示了这两种事务,也对各自的结果进行了分析
REQUIRED 是如果当前存在事务就加入该事务,成为该事务的一部分,既然都是一家人了,大家有难一起担!所以当其中的一个事务出现异常进行了回滚操作,那么整个事务都将会进行回滚操作。而 NESTED 是如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来运⾏,即附属在当前事务,并没有真正的成为一家人,所以当这个附属事务进行回滚操作时,当前事务并不会进行回滚,也就是说嵌套事务可以实现部分事务回滚 !!
总结如下:
- 整个事务如果全部执⾏成功,⼆者的结果是⼀样的。
- 如果事务执⾏到⼀半失败了,那么加⼊事务整个事务会全部回滚;⽽嵌套事务会局部回滚,不会影响上⼀个⽅法中执⾏的结果