Spring 的事务传播机制有哪些?
一则或许对你有用的小广告
欢迎加入小哈的星球,你将获得:专属的实战项目(4个项目都能学) / 1v1 提问 / 简历修改 / Java 学习路线 / 社群讨论 / 学习打卡 / 每月赠书
《Spring AI 项目实战(问答机器人、RAG 智能客服、联网搜索)》已完结,基于
Spring AI + Spring Boot 3.x + JDK 21...,查看介绍《从零手撸:仿小红书(微服务架构)》 已完结,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...,查看介绍;演示链接:http://116.62.199.48:7070/《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接:http://116.62.199.48/
新开坑项目:《从零手撸:秒杀系统高并发优化实战》 正在更新中...,查看介绍
截止目前,星球内专栏累计输出 150w+ 字,讲解图 5110+ 张,还在持续爆肝中.. 后续还会上新更多项目,已有 4700+ 小伙伴加入学习,欢迎点击围观
面试考察点
-
传播行为掌握度:面试官不仅仅想听你背出 7 种传播行为的名字,更想知道你能不能讲清楚每种行为的语义区别——谁加入谁、谁新建、谁不支持。
-
场景选型能力:给你一个具体业务场景(比如日志记录、异步调用、嵌套事务),你能不能选对传播行为,而不是不管三七二十一全用默认的。
-
底层实现理解:如果你能说出 Spring 事务传播是基于
ThreadLocal保持同一个数据库连接来实现的,面试官会认为你不只是 "会用",而是真的理解原理。
核心答案
Spring 定义了 7 种 事务传播行为,定义在 Propagation 枚举中:
| 传播行为 | 含义 | 有事务时 | 无事务时 |
|---|---|---|---|
REQUIRED(默认) |
需要事务 | 加入 当前事务 | 新建 一个事务 |
SUPPORTS |
支持事务 | 加入 当前事务 | 以非事务方式执行 |
MANDATORY |
强制事务 | 加入 当前事务 | 抛异常 |
REQUIRES_NEW |
必须新事务 | 挂起 当前事务,新建 | 新建 一个事务 |
NOT_SUPPORTED |
不支持事务 | 挂起 当前事务,以非事务方式执行 | 以非事务方式执行 |
NEVER |
绝不用事务 | 抛异常 | 以非事务方式执行 |
NESTED |
嵌套事务 | 创建 savepoint,嵌套在当前事务中 | 新建 一个事务 |
面试中掌握 REQUIRED、REQUIRES_NEW、NESTED 这三个就够应付 90% 的场景了,但另外四个也得知道,面试官可能会追问。
深度解析
一、REQUIRED —— 默认,最常用
REQUIRED 是 @Transactional 的默认传播行为。逻辑很简单:有事务就加入,没有就新建。
上图说明了 REQUIRED 的两种情况:
- 场景一:
methodA已经开启了事务 T1,methodB标记为REQUIRED,会直接加入 T1。两个方法在同一个事务里,任何一方抛异常,整个事务都会回滚。 - 场景二:
methodA没有事务,methodB标记为REQUIRED,会自己新建一个事务 T1 执行。
@Service
public class OrderService {
@Autowired
private ItemService itemService;
@Transactional // 默认就是 REQUIRED
public void createOrder() {
orderDao.save(order); // 保存订单
itemService.decreaseStock(); // 扣减库存(加入当前事务)
// 任何一步失败,订单和库存都回滚
}
}
@Service
public class ItemService {
@Transactional // REQUIRED:加入 createOrder 的事务
public void decreaseStock() {
itemDao.decrease(itemId, count);
}
}
二、REQUIRES_NEW —— 独立新事务
REQUIRES_NEW 不管外面有没有事务,一律自己新开一个。如果当前有事务,会先把它挂起(suspend),等自己的事务完成后再恢复。
上图的流程:
methodA在事务 T1 中执行,调用methodB时发现传播行为是REQUIRES_NEW。- Spring 先把 T1 挂起(暂停),然后为
methodB开启一个全新的独立事务 T2。 methodB在 T2 中执行完毕后,T2 提交或回滚。然后 Spring 恢复 T1,methodA继续在 T1 中执行。- 关键点:T2 回滚不会导致 T1 回滚,反过来也一样。两个事务完全独立。
典型应用场景——日志记录:
@Service
public class OrderService {
@Autowired
private LogService logService;
@Transactional // REQUIRED
public void createOrder() {
try {
orderDao.save(order);
// 业务逻辑...
} catch (Exception e) {
// 订单创建失败了,但日志必须记录成功
logService.log("订单创建失败: " + e.getMessage());
}
}
}
@Service
public class LogService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void log(String message) {
// 独立事务,即使外面的 createOrder 回滚了,日志也不会丢
logDao.save(new Log(message));
}
}
如果 logService.log() 用的是 REQUIRED,它加入 createOrder 的事务,订单回滚时日志也跟着回滚——日志就丢了。用 REQUIRES_NEW 就不怕,日志有自己独立的事务。
三、NESTED —— 嵌套事务
NESTED 的行为是:有事务就在当前事务里创建一个 savepoint(保存点),没有事务就新建一个。
上图的关键逻辑:
methodB失败:只回滚到 savepoint,methodA的操作不受影响,可以选择继续执行或整体回滚。methodA失败:整个事务 T1 回滚,methodB已经执行的嵌套操作也跟着回滚。
这就是 NESTED 和 REQUIRES_NEW 的本质区别:
REQUIRES_NEW:两个完全独立的事务,各自回滚互不影响。NESTED:嵌套在父事务中,子事务回滚不影响父事务,但父事务回滚会连带子事务一起回滚。
@Service
public class OrderService {
@Transactional
public void createOrder() {
orderDao.save(order);
try {
pointService.addPoints(); // NESTED:加积分
} catch (Exception e) {
// 积分失败不影响订单,回滚到 savepoint
log.warn("积分发放失败,稍后重试");
}
// 订单正常提交
}
}
@Service
public class PointService {
@Transactional(propagation = Propagation.NESTED)
public void addPoints() {
pointDao.add(userId, 100);
}
}
四、剩下的四种
另外四种传播行为面试中偶尔会问,简单过一下:
SUPPORTS —— 随缘模式
@Transactional(propagation = Propagation.SUPPORTS)
public void query() {
// 有事务就加入,没有也无所谓
// 适合只读查询,不强依赖事务
}
MANDATORY —— 强制模式
@Transactional(propagation = Propagation.MANDATORY)
public void deduct() {
// 必须在已有事务中被调用
// 如果没人开事务就直接调用,直接抛异常
// 适合做 "防护":确保这个方法一定在事务上下文中执行
}
NOT_SUPPORTED —— 拒绝事务
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void sendEmail() {
// 有事务就挂起,以非事务方式执行
// 适合耗时操作(发邮件、调外部 API),避免长事务占用数据库连接
}
NEVER —— 绝不用事务
@Transactional(propagation = Propagation.NEVER)
public void check() {
// 如果当前有事务就抛异常
// 比 NOT_SUPPORTED 更严格
}
五、7 种传播行为对比总结
上图的决策树帮你快速判断每种传播行为在有 / 无事务情况下的表现。面试时心里有这棵树,就能从容应对。
六、传播机制的底层原理
Spring 的事务传播行为是靠 ThreadLocal + 数据库连接来实现的。
核心逻辑在 AbstractPlatformTransactionManager 中:
- 同一个线程 中的事务信息通过
ThreadLocal保存,getTransaction()方法根据传播行为决定是复用当前连接(加入事务)、新建连接(新事务)、还是挂起连接(NOT_SUPPORTED/REQUIRES_NEW)。 - 挂起(suspend):把当前事务的连接从
ThreadLocal中取出来暂存,等新事务完成后再恢复(resume)回去。 - 嵌套(savepoint):
NESTED利用数据库的SAVEPOINT机制,在当前连接上设置一个回滚点,子事务回滚时只回到这个点。
这也解释了为什么 多线程场景下事务传播会失效——ThreadLocal 是线程隔离的,新线程拿不到父线程的事务上下文。
面试高频追问
-
NESTED和REQUIRES_NEW有什么区别?REQUIRES_NEW每次都新建一个完全独立的事务,内外事务互不影响。NESTED是在当前事务内设置 savepoint,子事务回滚只回到 savepoint 不影响父事务,但父事务回滚会连带子事务一起回滚。简单说:REQUIRES_NEW是 "各玩各的",NESTED是 "父管子,子不管父"。 -
@Transactional默认是什么传播行为?REQUIRED。有事务就加入,没有就新建。90% 的场景用默认就够了。 -
什么场景用
REQUIRES_NEW?日志记录、审计追踪、消息发送——这些操作不能因为主业务回滚而丢失。比如订单创建失败,但失败日志必须留下来。
-
事务传播在多线程下还有效吗?
无效。Spring 事务上下文基于
ThreadLocal,子线程拿不到父线程的事务。需要在子线程中单独开启事务,或者使用编程式事务(TransactionTemplate)手动控制。
常见面试变体
- "
REQUIRED和REQUIRES_NEW的区别?" - "
NESTED和REQUIRES_NEW怎么选?" - "Spring 事务的传播行为有哪些?你平时用哪几个?"
- "
@Transactional不指定 propagation 默认是什么?"
记忆口诀
"R 加 R 新 R 挂 S 随 M 强 N 嵌 NS 挂 NV 拒"——REQUIRED 加入、REQUIRES_NEW 新建挂起、SUPPORTS 随缘、MANDATORY 强制、NESTED 嵌套、NOT_SUPPORTED 挂起、NEVER 拒绝。
总结
Spring 事务传播机制定义了 7 种行为,核心要掌握三种:REQUIRED(默认,加入当前事务)、REQUIRES_NEW(独立新事务,适合日志等不能被主事务影响场景)、NESTED(嵌套事务,savepoint 机制,适合部分失败不影响主流程的场景)。面试时先把三种核心行为讲透,再顺带提一下另外四种,就足够拿高分了。
