[Spring]事务传播
事务传播行为之一
事务传播行为是为了解决业务层方法之间互相调用的事务问题。
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
举个例子:我们在 A 类的aMethod()方法中调用了 B 类的 bMethod() 方法。这个时候就涉及到业务层方法之间互相调用的事务问题。如果我们的 bMethod()如果发生异常需要回滚,如何配置事务传播行为才能让 aMethod()也跟着回滚呢?这个时候就需要事务传播行为的知识了,如果你不知道的话一定要好好看一下。
@Service
Class A {
@Autowired
B b;
@Transactional(propagation = Propagation.xxx)
public void aMethod {
//do something
b.bMethod();
}
}
@Service
Class B {
@Transactional(propagation = Propagation.xxx)
public void bMethod {
//do something
}
}
在TransactionDefinition定义中包括了如下几个表示传播行为的常量:
public interface TransactionDefinition {
int PROPAGATION_REQUIRED = 0;
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION_NEVER = 5;
int PROPAGATION_NESTED = 6;
......
}
不过,为了方便使用,Spring 相应地定义了一个枚举类:Propagation
package org.springframework.transaction.annotation;
import org.springframework.transaction.TransactionDefinition;
public enum Propagation {
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
NEVER(TransactionDefinition.PROPAGATION_NEVER),
NESTED(TransactionDefinition.PROPAGATION_NESTED);
private final int value;
s
Propagation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
正确的事务传播行为可能的值如下:
1.TransactionDefinition.PROPAGATION_REQUIRED
使用的最多的一个事务传播行为,我们平时经常使用的@Transactional注解默认使用就是这个事务传播行为。
如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
也就是说:
如果外部方法没有开启事务的话,Propagation.REQUIRED修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
如果外部方法开启事务并且被Propagation.REQUIRED的话,所有Propagation.REQUIRED修饰的内部方法和外部方法均属于同一事务 ,只要一个方法回滚,整个事务均回滚。
举个例子:如果我们上面的aMethod()和bMethod()使用的都是PROPAGATION_REQUIRED传播行为的话,两者使用的就是同一个事务,只要其中一个方法回滚,整个事务均回滚。
@Service
Class A {
@Autowired
B b;
@Transactional(propagation = Propagation.REQUIRED)
public void aMethod {
//do something
b.bMethod();
}
}
@Service
Class B {
@Transactional(propagation = Propagation.REQUIRED)
public void bMethod {
//do something
}
}
2.TransactionDefinition.PROPAGATION_REQUIRES_NEW
创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
举个例子:如果我们上面的bMethod()使用PROPAGATION_REQUIRES_NEW事务传播行为修饰,aMethod还是用PROPAGATION_REQUIRED修饰的话。如果aMethod()发生异常回滚,bMethod()不会跟着回滚,因为 bMethod()开启了独立的事务。但是,如果 bMethod()抛出了未被捕获的异常并且这个异常满足事务回滚规则的话,aMethod()同样也会回滚,因为这个异常被 aMethod()的事务管理机制检测到了。
@Service
Class A {
@Autowired
B b;
@Transactional(propagation = Propagation.REQUIRED)
public void aMethod {
//do something
b.bMethod();
}
}
@Service
Class B {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void bMethod {
//do something
}
}
3.TransactionDefinition.PROPAGATION_NESTED:
如果当前存在事务,就在嵌套事务内执行;如果当前没有事务,就执行与TransactionDefinition.PROPAGATION_REQUIRED类似的操作。也就是说:
在外部方法开启事务的情况下,在内部开启一个新的事务,作为嵌套事务存在。
如果外部方法无事务,则单独开启一个事务,与 PROPAGATION_REQUIRED 类似。
这里还是简单举个例子:如果 bMethod() 回滚的话,aMethod()不会回滚。如果 aMethod() 回滚的话,bMethod()会回滚。
@Service
Class A {
@Autowired
B b;
@Transactional(propagation = Propagation.REQUIRED)
public void aMethod {
//do something
b.bMethod();
}
}
@Service
Class B {
@Transactional(propagation = Propagation.NESTED)
public void bMethod {
//do something
}
}
4.TransactionDefinition.PROPAGATION_MANDATORY
如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)
这个使用的很少,就不举例子来说了。
若是错误的配置以下 3 种事务传播行为,事务将不会发生回滚,这里不对照案例讲解了,使用的很少。
TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。
更多关于事务传播行为的内容请看这篇文章:《太难了~面试官让我结合案例讲讲自己对 Spring 事务传播行为的理解。》
事务传播行为之二
Spring 事务传播机制是指,包含多个事务的方法在相互调用时,事务是如何在这些方法间传播的。
既然是“事务传播”,所以事务的数量应该在两个或两个以上,Spring 事务传播机制的诞生是为了规定多个事务在传播过程中的行为的。比如方法 A 开启了事务,而在执行过程中又调用了开启事务的 B 方法,那么 B 方法的事务是应该加入到 A 事务当中呢?还是两个事务相互执行互不影响,又或者是将 B 事务嵌套到 A 事务中执行呢?所以这个时候就需要一个机制来规定和约束这两个事务的行为,这就是 Spring 事务传播机制所解决的问题。
Spring 事务传播机制有哪些?
Spring 事务传播机制可使用 @Transactional(propagation=Propagation.REQUIRED) 来定义,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 类:

看到这里,有人可能会说:说了这么多,我也看不懂啊,即使看懂了,我也记不住啊?这要咋整?
没关系,接下来我们用一个例子,来说明这 3 类事务传播机制的区别。
以情侣之间是否要买房为例,我们将以上 3 类事务传播机制可以看作是恋爱中的 3 类女生类型:
普通型
强势型
懂事型
这三类女生如下图所示:

支持当前事务的“女生”,这里的事务指的是“房子”,它分为 3 种(普通型女生):
Propagation.REQUIRED(需要有房子):有房子了咱们一起住,没房子买房子。
Propagation.SUPPORTS(可以有房子):有房子了就一起住,没房子就不住房子。
Propagation.MANDATORY(强制有房子):有房子了就一起住,没房子了就分手。
不支持当前事务的“女生”也分为 3 种(强势型或者叫事业型):
Propagation.REQUIRES_NEW:不要你的房子,必须一起赚钱买房子。
Propagation.NOT_SUPPORTED:不要你的房子,不许住房子。
Propagation.NEVER:必须一起租房子,你有房子就分手。
最后一种是嵌套性事务 Propagation.NESTED,它属于懂事型女友,如果有房子了就以房子为基础做点小生意,卖个花生、水果啥的,如果买卖成了,那就继续发展;如果失败了,至少还有房子;如果没房子也没关系,一起赚钱买房子。
事务传播机制使用与演示
接下来我们演示一下事务传播机制的使用,以下面 3 个最典型的事务传播级别为例:
支持当前事务的 REQUIRED;
不支持当前事务的 REQUIRES_NEW;
嵌套事务 NESTED。
下来我们分别来看。
事务传播机制的示例,需要用到以下两张表:
-- 用户表
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
`password` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
`createtime` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC;
-- 日志表
CREATE TABLE `log` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`content` text NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
创建一个 Spring Boot 项目,核心业务代码有 3 个:UserController、UserServcie 以及 LogService。在 UserController 里面调用 UserService 添加用户,并调用 LogService 添加日志。
REQUIRED 使用演示
REQUIRED 支持当前事务。
UserController 实现代码如下,其中 save 方法开启了事务:
@RestController
public class UserController {
@Resource
private UserService userService;
@Resource
private LogService logService;
@RequestMapping("/save")
@Transactional
public Object save(User user) {
// 插入用户操作
userService.save(user);
// 插入日志
logService.saveLog("用户插入:" + user.getName());
return true;
}
}
UserService 实现代码如下:
@Service
public class UserService {
@Resource
private UserMapper userMapper;
@Transactional(propagation = Propagation.REQUIRED)
public int save(User user) {
return userMapper.save(user);
}
}
LogService 实现代码如下:
@Service
public class LogService {
@Resource
private LogMapper logMapper;
@Transactional(propagation = Propagation.REQUIRED)
public int saveLog(String content) {
// 出现异常
int i = 10 / 0;
return logMapper.saveLog(content);
}
}
执行结果:程序报错,两张表中都没有插入任何数据。
执行流程描述:
首先 UserService 中的添加用户方法正常执行完成。
LogService 保存日志程序报错,因为使用的是 UserController 中的全局事务,所以整个事务回滚,步骤 1 中的操作也跟着回滚。
所以数据库中没有添加任何数据。
REQUIRED_NEW 使用演示
REQUIRED_NEW 不支持当前事务。
UserController 实现代码:
@RequestMapping("/save")
@Transactional
public Object save(User user) {
// 插入用户操作
userService.save(user);
// 插入日志
logService.saveLog("用户插入:" + user.getName());
return true;
}
UserService 实现代码:
@Service
public class UserService {
@Resource
private UserMapper userMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public int save(User user) {
System.out.println("执行 save 方法.");
return userMapper.save(user);
}
}
LogService 实现代码:
@Service
public class LogService {
@Resource
private LogMapper logMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public int saveLog(String content) {
// 出现异常
int i = 10 / 0;
return logMapper.saveLog(content);
}
}
程序执行结果:
User 表中成功添加了一条用户数据,Log 表执行失败,没有加入任何数据,但它并没有影响到 UserController 中的事务执行。
通过以上结果可以看出:LogService 中使用的是单独的事务,虽然 LogService 中的事务执行失败了,但并没有影响 UserController 和 UserService 中的事务。
NESTED 使用演示
NESTED 是嵌套事务。
UserController 实现代码如下:
@RequestMapping("/save")
@Transactional
public Object save(User user) {
// 插入用户操作
userService.save(user);
return true;
}
UserService 实现代码如下:
@Transactional(propagation = Propagation.NESTED)
public int save(User user) {
int result = userMapper.save(user);
System.out.println("执行 save 方法.");
// 插入日志
logService.saveLog("用户插入:" + user.getName());
return result;
}
LogService 实现代码如下:
@Transactional(propagation = Propagation.NESTED)
public int saveLog(String content) {
// 出现异常
int i = 10 / 0;
return logMapper.saveLog(content);
}
最终执行结果,用户表和日志表都没有添加任何数据。
执行流程描述:
UserController 中调用了 UserService 的添加用户方法,UserService 使用 NESTED 循环嵌套事务,并成功执行了添加用户的方法。
UserService 中调用了 LogService 的添加方法,LogService 使用了 NESTED 循环嵌套事务,但在方法执行中出现的异常,因此回滚了当前事务。
因为 UserService 使用的是嵌套事务,所以发生回滚的事务是全局的,也就是说 UserService 中的添加用户方法也被回滚了,最终执行结果是用户表和日志表都没有添加任何数据。
总结
Spring 事务传播机制是包含多个事务的方法在相互调用时,事务是如何在这些方法间传播的。事务的传播级别有 7 个,支持当前事务的:REQUIRED、SUPPORTS、MANDATORY;不支持当前事务的:REQUIRES_NEW、NOT_SUPPORTED、NEVER,以及嵌套事务 NESTED,其中 REQUIRED 是默认的事务传播级别。

浙公网安备 33010602011771号