Spring事务传播行为
Spring 提供了对数据库事务的支持,除了常说的事务隔离级别,Spring 定义了不同的事务传播行为,用来简化我们在应用代码中事务在不同的方法中的传播,在平时的开发中我们可以简单地说,Spring 的事务传播就是解决被调用方法如果出错那么调用方是否进行回滚的问题。但是长久以来,我都不是很理解这些传播行为究竟具体表示什么意思,即使看过很多讲述的文章,但是看过之后还是不清楚各种传播行为配合使用的结果是怎样的,所以本文谨以实验的方式来记录一下各种传播行为所产生的结果,以加深对其使用的理解。
本文使用 Springboot + MyBatis,仅通过示例代码演示各种事务传播行为时,方法的执行情况。
本文只试验被调用方法中出现异常并且未被捕获时的事务传播情况,调用方法中出现异常的情况下次再进行试验说明
1. 创建表
# user 表
create table if not exists user (
id bigint not null auto_increment,
name varchar(20) not null,
sex tinyint,
create_time datetime not null,
primary key (id)
) engine=INNODB default charset=utf8mb4;
# 账户表
create table if not exists account(
id bigint not null auto_increment,
balance decimal(14, 2) not null default 0.00,
user_id bigint not null,
create_time datetime not null,
primary key (id)
) engine = INNODB default charset=utf8mb4;
2. 搭建Springboot 项目,目录如下
我们主要使用 UserService.java 和 AccountService.java 来演示,代码如下:
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountMapper accountMapper;
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void add(Account account) {
accountMapper.insert(account);
int i = 1 / 0;
}
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private AccountService accountService;
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void add(User user, Account account) {
userMapper.insertUser(user);
accountService.add(account);
}
}
可以看到,我们在UserService.add() 方法中调用了 AccountService.add() 方法,并且我们在AccountService.add() 方法中制造一个异常,两个方法的作用都是向对应的表中插入一条数据,下面我们使用不同的传播行为来进行测试,数据的关联正确性请忽略。
- 调用方法使用 REQUIRED
调用方法 | 被调用方法 | 调用方法执行结果 | 被调用方法执行结果 |
---|---|---|---|
required | 无 | 失败 | 失败 |
required | required | 失败 | 失败 |
required | required_new | 失败 | 失败 |
required | nested | 失败 | 失败 |
required | mandatory | 失败 | 失败 |
required | supports | 失败 | 失败 |
required | not_supports | 失败 | 成功 |
required | never | 失败 | 失败 |
required: 支持当前事务,如果被调用方法没有事务,则将其加入到调用方的事务中;如果被调用方法是not_supported,则将其以非事务方式执行,即被调用方法不会滚;如果被调用方法标记了
never
, 则表示调用方不能存在事务,否则会抛出异常,所以被调用方法不是由于1/0
而回滚,而是因为事务冲突的原因没有得到执行而直接抛出了异常。
- 调用方法使用 SUPPORTS
调用方法 | 被调用方法 | 调用方法执行结果 | 被调用方法执行结果 |
---|---|---|---|
supports | 无 | 成功 | 成功 |
supports | required | 成功 | 失败 |
supports | required_new | 成功 | 失败 |
supports | nested | 成功 | 失败 |
supports | mandatory | 失败 | 失败 |
supports | supports | 成功 | 成功 |
supports | not_supports | 成功 | 成功 |
supports | never | 成功 | 成功 |
supports: 支持当前事务,如果没有事务,则以非事务方式运行。如果被调用方法有非强制的事务(
required, requires_new
),则被调用方法会回滚,调用方法不会回滚;如果被调用方法有强制事务(mandatory
), 则调用方法会抛出事务冲突的的异常,被调用方法得不到执行,所以调用方法和被调用方法都会回滚;如果被调用方没有事务,则调用方法和被调用方法都已非事务方式运行,不会进行回滚。
- 调用方法使用 MANDATORY
调用方法 | 被调用方法 | 调用方法执行结果 | 被调用方法执行结果 |
---|---|---|---|
mandatory | 无 | 失败 | 失败 |
mandatory | required | 失败 | 失败 |
mandatory | required_new | 失败 | 失败 |
mandatory | nested | 失败 | 失败 |
mandatory | mandatory | 失败 | 失败 |
mandatory | supports | 失败 | 失败 |
mandatory | not_supports | 失败 | 失败 |
mandatory | never | 失败 | 失败 |
mandatory: 支持当前事务,如果没有事务,则抛出异常。当被调用方法标记了
not_supported、 never
或者没有事务时,直接抛出事务异常,被调用方法不能得到执行,所以失败;其他情况均以事务方式执行,所以进行了回滚。
- 调用方法使用 NOT_SUPPORTS
调用方法 | 被调用方法 | 调用方法执行结果 | 被调用方法执行结果 |
---|---|---|---|
not_supports | 无 | 成功 | 成功 |
not_supports | required | 成功 | 失败 |
not_supports | required_new | 成功 | 失败 |
not_supports | nested | 成功 | 失败 |
not_supports | mandatory | 成功 | 失败 |
not_supports | supports | 成功 | 成功 |
not_supports | not_supports | 成功 | 成功 |
not_supports | never | 成功 | 成功 |
not_supported: 不支持当前事务,如果当前有事务,则将其挂起,以非事务方式执行。在调用方法上标记not_supported, 则调用方法不会进行回滚,被调用方法是否回滚取决于被调用方法自身的事务机制。
- 调用方法使用 REQUIRES_NEW
调用方法 | 被调用方法 | 调用方法执行结果 | 被调用方法执行结果 |
---|---|---|---|
requires_new | 无 | 失败 | 失败 |
requires_new | required | 失败 | 失败 |
requires_new | required_new | 失败 | 失败 |
requires_new | nested | 失败 | 失败 |
requires_new | mandatory | 失败 | 失败 |
requires_new | supports | 失败 | 失败 |
requires_new | not_supports | 失败 | 成功 |
requires_new | never | 失败 | 失败 |
requires_new: 不支持当前事务,如果没有事务,则新建事务,如果有事务,则将当前事务挂起。如果被调用方法上标记了
never
,则直接抛出事务异常,被调用方法不能得到执行,所以调用方法也进行回滚; 如果被调用方法标记了not_supported
, 则被调用方法以非事务方式执行,不会进行回滚,调用方法由于1/0
的异常而进行回滚;如果被调用方法没有事务,则新建事务,所以进行了回滚。
- 调用方法使用NEVER
调用方法 | 被调用方法 | 调用方法执行结果 | 被调用方法执行结果 |
---|---|---|---|
never | 无 | 成功 | 成功 |
never | required | 成功 | 失败 |
never | required_new | 成功 | 失败 |
never | nested | 成功 | 失败 |
never | mandatory | 成功 | 失败 |
never | supports | 成功 | 成功 |
never | not_supports | 成功 | 成功 |
never | never | 成功 | 成功 |
never: 不支持当前事务,以非事务方式运行,如果当前有事务,则抛出异常。所以,当被调用方有强制事务时,直接抛出事务冲突的异常导致没有得到执行导致失败;当被调用方法有
required、requires_new
时,因为1/0
而进行回滚。但是如果never
是被标记在被调用方式上时,如果调用方法有事务就会抛出异常。
- 调用方法使用NESTED
调用方法 | 被调用方法 | 调用方法执行结果 | 被调用方法执行结果 |
---|---|---|---|
nested | 无 | 失败 | 失败 |
nested | required | 失败 | 失败 |
nested | required_new | 失败 | 失败 |
nested | nested | 失败 | 失败 |
nested | mandatory | 失败 | 失败 |
nested | supports | 失败 | 失败 |
nested | not_supports | 失败 | 成功 |
nested | never | 失败 | 失败 |
nested: 如果存在事务,则以嵌套事务方式执行。可以看到,在不捕获异常的情况下,和
required
的方式相似,如果被调用方法标记了nested
, 并且在调用方法中try-catch了,则只有当调用方法提交后,被调用方法才会被提交,否则被调用方法会回滚。
3. 总结
-
什么是当前事务?
相对于被调用方法来说,调用方法的事务状态就是当前事务。 -
什么是支持当前事务?
被调用方法是否按照当前事务的执行状态来运行,如果是,则表示支持当前事务。
可以看到,
Spring
中事务传播行为主要分为两种类型:支持当前事务(required/ mandatory/ support
)、 不支持当前事务(required_new/ not_supported/ never
)。
以上谨代表个人的理解,如有不当之处,敬请批评指正。
上一篇: 剑指offer面试题36---二叉搜索树与双向链表
下一篇: 466. 回文日期
推荐阅读