spring事务传播行为
Spring 事务传播行为(Propagation)指的是:一个事务方法调用另一个事务方法时,事务应该如何传播和处理。
Spring 一共有 7 种事务传播行为:
| 传播行为 | 说明 |
|---|---|
| REQUIRED | 如果当前有事务,则加入;没有则新建事务(默认) |
| REQUIRES_NEW | 无论当前是否有事务,都创建新事务 |
| SUPPORTS | 有事务就加入,没有事务就非事务执行 |
| NOT_SUPPORTED | 有事务则挂起,以非事务方式执行 |
| MANDATORY | 必须在事务中运行,没有事务则抛异常 |
| NEVER | 必须在非事务中运行,有事务则抛异常 |
| NESTED | 如果有事务,则创建嵌套事务;没有则新建事务 |
1. REQUIRED(默认)
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
}
特点:
- 当前有事务 → 加入当前事务
- 当前无事务 → 创建新事务
例如:
@Transactional
public void order() {
pay();
}
@Transactional
public void pay() {
}
执行过程:
order()
↓
开启事务T1
↓
pay()
加入事务T1
最终:
order异常
↓
pay一起回滚
这是最常用的传播行为。
2. REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodA() {
}
特点:
无论外部有没有事务:
先挂起当前事务
再开启新事务
例如:
@Transactional
public void order() {
saveOrder();
saveLog();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveLog() {
}
执行:
order()
↓
事务T1
saveLog()
↓
挂起T1
开启T2
提交T2
恢复T1
图示:
T1
├─saveOrder
├─T2(saveLog)
└─继续T1
如果:
saveLog成功
order失败
结果:
T2提交
T1回滚
日志保留。
经典场景
操作日志:
下单
↓
记录日志
↓
订单失败
希望:
订单回滚
日志保留
就用:
REQUIRES_NEW
3. SUPPORTS
@Transactional(propagation = Propagation.SUPPORTS)
特点:
有事务 → 加入
无事务 → 不开启事务
例如:
public void query() {
getUser();
}
@Transactional(propagation = SUPPORTS)
public User getUser() {
}
此时:
无事务执行
如果:
@Transactional
public void business() {
getUser();
}
则:
加入业务事务
经典场景
查询接口:
SELECT
因为:
查询不一定需要事务
4. NOT_SUPPORTED
@Transactional(propagation = Propagation.NOT_SUPPORTED)
特点:
有事务 -> 挂起
无事务 -> 直接执行
例如:
@Transactional
public void order() {
saveOrder();
queryBigTable();
}
@Transactional(propagation = NOT_SUPPORTED)
public void queryBigTable() {
}
执行:
T1开启
saveOrder()
挂起T1
queryBigTable()
恢复T1
经典场景
耗时查询:
导出Excel
统计报表
大数据查询
避免长事务。
5. MANDATORY
@Transactional(propagation = MANDATORY)
特点:
必须存在事务
否则抛异常
例如:
mandatoryMethod();
如果外面没有事务:
IllegalTransactionStateException
经典场景
某些核心方法:
扣库存
扣余额
必须由上层控制事务。
6. NEVER
@Transactional(propagation = NEVER)
特点:
必须没有事务
有事务立即报错
例如:
@Transactional
public void order() {
neverMethod();
}
@Transactional(propagation = NEVER)
public void neverMethod() {
}
结果:
抛异常
经典场景
禁止事务访问:
日志采集
监控系统
缓存查询
7. NESTED(嵌套事务)
@Transactional(propagation = NESTED)
特点:
利用:
SavePoint(保存点)
实现局部回滚。
例子:
@Transactional
public void order() {
saveOrder();
saveLog();
saveUser();
}
@Transactional(propagation = NESTED)
public void saveLog() {
int a = 1 / 0;
}
执行:
T1开始
saveOrder()
创建SavePoint
saveLog异常
回滚到SavePoint
saveUser()
提交T1
结果:
saveOrder 成功
saveLog 失败
saveUser 成功
NESTED 与 REQUIRES_NEW区别(面试高频)
REQUIRES_NEW
外层事务 T1
内层事务 T2
完全独立:
T2提交后不可回滚
NESTED
只有一个事务 T1
内部:
SavePoint
回滚:
回滚到保存点
图示:
T1
├─saveOrder
├─SavePoint
│ └─saveLog
└─saveUser
面试必背总结
REQUIRED
有则加入,无则创建(默认)
REQUIRES_NEW
总是新事务
SUPPORTS
有事务加入,无事务直接执行
NOT_SUPPORTED
挂起事务,非事务执行
MANDATORY
必须有事务
NEVER
必须没有事务
NESTED
SavePoint嵌套事务
高频面试题:
订单 + 日志场景应该选哪个?
@Transactional(propagation = REQUIRES_NEW)
因为:
订单回滚
日志不回滚
这是面试中最经典的事务传播行为应用场景。
请用真实应用中的代码说明 这几种事务传播级别在生产中的应用
面试的时候,如果只是背概念,面试官一般会继续追问:
你在项目中实际用过哪些传播行为?
为什么用它?
不用会出现什么问题?
实际上生产环境最常用的是:
REQUIRED (90%)
REQUIRES_NEW (8%)
NOT_SUPPORTED (1%)
NESTED (1%)
其他几个几乎见不到。
1. REQUIRED(最常用)
场景:下单
OrderService
@Service
public class OrderService {
@Autowired
private StockService stockService;
@Autowired
private AccountService accountService;
@Transactional
public void createOrder(Long userId, Long productId) {
// 扣库存
stockService.deduct(productId);
// 扣余额
accountService.deduct(userId);
// 创建订单
orderMapper.insert(...);
}
}
StockService
@Transactional(propagation = Propagation.REQUIRED)
public void deduct(Long productId){
stockMapper.updateStock(productId);
}
AccountService
@Transactional(propagation = Propagation.REQUIRED)
public void deduct(Long userId){
accountMapper.updateMoney(userId);
}
执行流程:
createOrder()
↓
开启事务T1
deductStock()
加入T1
deductMoney()
加入T1
insertOrder()
加入T1
如果余额不足:
throw new RuntimeException();
结果:
库存回滚
余额回滚
订单回滚
保证一致性。
2. REQUIRES_NEW
场景:订单失败也要记录日志
生产中非常常见。
下单
@Transactional
public void createOrder() {
try {
saveOrder();
} catch (Exception e) {
logService.saveErrorLog(e);
throw e;
}
}
日志服务
@Service
public class LogService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveErrorLog(Exception e){
logMapper.insert(...);
}
}
执行:
订单事务 T1
saveErrorLog()
↓
挂起T1
开启T2
提交T2
恢复T1
T1回滚
结果:
订单失败
日志保留
如果不用 REQUIRES_NEW
@Transactional
public void saveErrorLog(){}
日志加入T1:
日志写入成功
订单异常
整个事务回滚
最后:
日志没了
排查问题非常痛苦。
3. REQUIRES_NEW
场景:发送站内信
比如:
注册送100金币
↓
发送站内信通知
即使通知失败:
金币也不能回滚
用户注册
@Transactional
public void register(User user){
userMapper.insert(user);
rewardService.sendCoin(user);
messageService.sendMessage(user);
}
消息服务
@Transactional(propagation = REQUIRES_NEW)
public void sendMessage(User user){
messageMapper.insert(...);
}
结果:
注册送金币成功
站内信失败
用户依然注册成功。
4. NOT_SUPPORTED
场景:导出百万数据
很多公司都踩过坑。
错误写法:
@Transactional
public void exportExcel(){
List<User> users =
userMapper.queryAll();
generateExcel(users);
}
导出耗时:
5分钟
事务一直不提交:
连接占用
undo log暴涨
锁资源长期持有
改成:
@Transactional(propagation = NOT_SUPPORTED)
public void exportExcel(){
List<User> users =
userMapper.queryAll();
generateExcel(users);
}
执行:
挂起事务
非事务查询
避免长事务。
5. NESTED
场景:批量导入用户
需求:
1000条数据
998条成功
2条失败
不要全部回滚
ImportService
@Transactional
public void importUsers(List<User> users){
for(User user : users){
try{
saveUser(user);
}catch(Exception e){
log.error("导入失败");
}
}
}
UserService
@Transactional(propagation = Propagation.NESTED)
public void saveUser(User user){
userMapper.insert(user);
if(user.getAge() < 0){
throw new RuntimeException();
}
}
执行:
主事务 T1
用户1 成功
用户2 成功
创建SavePoint
用户3失败
回滚到SavePoint
用户4继续
用户5继续
结果:
999成功
1失败
而不是全部回滚。
6. MANDATORY
生产中较少。
场景:资金扣减
金融系统经常用。
@Transactional
public void transfer(){
accountService.deduct();
}
@Transactional(propagation = MANDATORY)
public void deduct(){
}
要求:
必须在事务中执行
如果开发人员忘记:
accountService.deduct();
直接报错:
IllegalTransactionStateException
避免资金数据异常。
7. NEVER
非常少见。
场景:缓存查询
有些查询要求:
绝对不能开启事务
例如:
@Transactional(propagation = NEVER)
public User getCacheUser(Long id){
return redisService.get(id);
}
如果有人:
@Transactional
public void test(){
getCacheUser();
}
立即报错。
避免:
事务污染缓存查询
真实互联网项目中使用频率
| 传播行为 | 使用频率 | 典型场景 |
|---|---|---|
| REQUIRED | ⭐⭐⭐⭐⭐ | 下单、支付、库存 |
| REQUIRES_NEW | ⭐⭐⭐⭐ | 日志、消息、通知 |
| NOT_SUPPORTED | ⭐⭐⭐ | 导出、报表 |
| NESTED | ⭐⭐ | 批量导入 |
| SUPPORTS | ⭐ | 查询接口 |
| MANDATORY | ⭐ | 金融扣款 |
| NEVER | ⭐ | 特殊缓存查询 |
如果是你这种做**游戏后端(下注、结算、奖励发放)**的场景,我最建议你重点掌握这几个实际案例:
REQUIRED
下注
↓
扣余额
↓
写下注记录
失败一起回滚。
REQUIRES_NEW
下注失败
↓
记录风控日志
日志必须保留。
REQUIRES_NEW
结算成功
↓
发送站内信
站内信失败不能影响结算。
NOT_SUPPORTED
后台导出玩家投注记录
避免长事务拖垮数据库。
这几个基本覆盖了大多数游戏业务和互联网业务的真实生产场景,也是面试官最爱追问的部分。
什么是io多路
浙公网安备 33010602011771号