面试官:“请说一下Spring事务的传播行为有哪些?REQUIRED和REQUIRES_NEW的区别是什么?事务在什么情况下会失效?你在项目中遇到过事务失效的问题吗?如何排查?”
你:(有条不紊地从事务传播行为开始,结合实际案例层层剖析...)
考察重点
这道题是 Spring面试的必考题,面试官想考察:
- 原理理解:对事务传播行为、隔离级别的掌握
- 源码功底:是否了解Spring事务的实现原理(AOP、TransactionInterceptor)
- 实战能力:能否列举事务失效的场景并分析原因
- 架构视野:对分布式事务的理解和解决方案
一、Spring事务核心原理
1.1 事务管理架构
┌─────────────────────────────────────────────────────────────┐
│ Spring事务管理架构 │
├─────────────────────────────────────────────────────────────┤
│ PlatformTransactionManager(事务管理器接口) │
│ ├── DataSourceTransactionManager(JDBC) │
│ ├── JtaTransactionManager(JTA) │
│ └── HibernateTransactionManager(Hibernate) │
├─────────────────────────────────────────────────────────────┤
│ TransactionDefinition(事务定义) │
│ ├── 传播行为(Propagation) │
│ ├── 隔离级别(Isolation) │
│ ├── 超时时间(Timeout) │
│ └── 只读状态(ReadOnly) │
├─────────────────────────────────────────────────────────────┤
│ TransactionStatus(事务状态) │
│ ├── 是否是新事务 │
│ ├── 是否有保存点 │
│ └── 是否已回滚 │
└─────────────────────────────────────────────────────────────┘
1.2 事务执行流程
Spring事务通过AOP实现,核心拦截器是 TransactionInterceptor:
// TransactionInterceptor.invoke()
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// 获取目标方法的事务属性
TransactionAttribute txAttr = getTransactionAttributeSource()
.getTransactionAttribute(method, targetClass);
// 获取事务管理器
PlatformTransactionManager tm = getTransactionManager();
// 执行事务
return invokeWithinTransaction(invocation, txAttr, tm);
}
二、事务传播行为详解
2.1 7种传播行为
Spring在 TransactionDefinition 中定义了7种传播行为:
| 传播行为 |
含义 |
常用场景 |
| REQUIRED |
支持当前事务,如果不存在则新建 |
默认值,绝大多数场景 |
| SUPPORTS |
支持当前事务,如果不存在则以非事务方式执行 |
查询方法,有无事务都可 |
| MANDATORY |
支持当前事务,如果不存在则抛异常 |
强制在事务中执行 |
| REQUIRES_NEW |
新建事务,如果存在则挂起当前事务 |
独立日志记录、异步操作 |
| NOT_SUPPORTED |
以非事务方式执行,如果存在则挂起 |
不需要事务的操作 |
| NEVER |
以非事务方式执行,如果存在则抛异常 |
禁止事务的操作 |
| NESTED |
如果存在事务则在嵌套事务中执行,否则等同于REQUIRED |
部分回滚场景 |
2.2 核心传播行为源码分析
// TransactionAspectSupport.createTransactionIfNecessary()
protected TransactionInfo createTransactionIfNecessary(
PlatformTransactionManager tm, TransactionAttribute txAttr, String joinpointIdentification) {
// 如果未指定事务属性,返回null
if (txAttr == null || tm == null) {
return null;
}
TransactionStatus status = null;
if (txAttr.getPropagationBehavior() != TransactionDefinition.PROPAGATION_SUPPORTS) {
// 获取当前事务(通过TransactionSynchronizationManager)
Object currentTransaction = tm.getTransaction(txAttr);
status = tm.getTransaction(txAttr);
} else {
// SUPPORTS传播级别,没有事务则直接执行
}
// 包装事务信息
return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}
2.3 传播行为演示
@Service
public class PropagationService {
@Autowired
private PropagationService self;
// 1. REQUIRED(默认)
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
// 如果当前有事务,加入;否则新建
}
// 2. REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
// 总是新建事务,挂起当前事务
}
// 3. NESTED
@Transactional(propagation = Propagation.NESTED)
public void methodC() {
// 嵌套事务,依赖于数据库的保存点
}
}
// 测试场景
@Service
public class TransactionTestService {
@Autowired
private PropagationService propagationService;
@Transactional(propagation = Propagation.REQUIRED)
public void testRequired() {
// 事务A开始
try {
propagationService.methodA(); // 加入A
propagationService.methodB(); // 挂起A,创建新事务B
// B提交后,恢复A
} catch (Exception e) {
// A可以回滚,但B已经提交
}
}
}
REQUIRED vs REQUIRES_NEW 对比:
| 特性 |
REQUIRED |
REQUIRES_NEW |
| 事务关系 |
加入已有事务 |
挂起已有事务,创建新事务 |
| 回滚影响 |
内部异常会导致外部回滚 |
内部回滚不影响外部 |
| 外部异常 |
内部会回滚 |
内部不会回滚 |
| 资源占用 |
共享连接 |
独立连接(挂起会释放连接) |
三、事务隔离级别
3.1 数据库隔离级别回顾
| 隔离级别 |
脏读 |
不可重复读 |
幻读 |
| READ_UNCOMMITTED |
✅ |
✅ |
✅ |
| READ_COMMITTED |
❌ |
✅ |
✅ |
| REPEATABLE_READ |
❌ |
❌ |
✅(MySQL默认) |
| SERIALIZABLE |
❌ |
❌ |
❌ |
3.2 Spring支持的隔离级别
public enum Isolation {
DEFAULT(-1), // 使用数据库默认隔离级别
READ_UNCOMMITTED(1), // 读未提交
READ_COMMITTED(2), // 读已提交
REPEATABLE_READ(4), // 可重复读
SERIALIZABLE(8); // 串行化
}
3.3 隔离级别设置
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void updateStock() {
// 业务代码
}
四、事务失效的7种场景
4.1 场景一:非public方法
// ❌ 失效:private方法无法被代理
@Transactional
private void updateData() {
// 事务不生效
}
// ❌ 失效:protected、default(package-private)同样失效
@Transactional
void updateData() {
// 事务不生效(如果未配置proxy-target-class)
}
// ✅ 正确:public方法
@Transactional
public void updateData() {
// 事务生效
}
原因:Spring默认使用JDK动态代理,只能代理public方法;即使使用CGLIB,也只能代理public和protected(但一般不推荐)。
4.2 场景二:自调用(内部调用)
@Service
public class UserService {
@Transactional
public void methodA() {
// 外部调用:事务生效
methodB(); // ❌ 内部调用,事务失效
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
// 期望:REQUIRES_NEW,实际:无事务
}
}
解决方案:
// 方案1:注入自身(通过代理调用)
@Service
public class UserService {
@Autowired
private UserService self;
public void methodA() {
self.methodB(); // ✅ 事务生效
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {}
}
// 方案2:使用AopContext.currentProxy()
public void methodA() {
((UserService) AopContext.currentProxy()).methodB();
}
4.3 场景三:异常类型不正确
// ❌ 默认只回滚RuntimeException和Error
@Transactional
public void method() {
try {
// 业务代码
} catch (SQLException e) { // SQLException是Exception,不是RuntimeException
// 事务不会回滚
throw e; // 但抛出的是SQLException,也不会回滚
}
}
// ✅ 指定回滚异常
@Transactional(rollbackFor = Exception.class)
public void method() {
// 所有异常都会回滚
}
// ✅ 明确不回滚某些异常
@Transactional(noRollbackFor = IllegalArgumentException.class)
public void method() {
// 抛出IllegalArgumentException不会回滚
}
4.4 场景四:多线程环境
@Service
public class AsyncService {
@Transactional
public void methodA() {
new Thread(() -> {
// ❌ 子线程中的事务不生效
methodB();
}).start();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
// 无事务
}
}
原因:Spring事务管理器和数据库连接绑定到当前线程(ThreadLocal),子线程无法获取父线程的事务。
4.5 场景五:数据库引擎不支持事务
-- MySQL MyISAM引擎不支持事务,事务失效
CREATE TABLE user (
id int,
name varchar(100)
) ENGINE=MyISAM;
注意:确保你的表使用的是支持事务的引擎,例如 InnoDB。这是很多事务配置正确但依然不生效的常见“坑”之一,更多 数据库 相关知识可以深入探讨。
4.6 场景六:事务传播特性使用不当
// 外层无事务,内层REQUIRES_NEW不会创建新事务
public void outer() { // 无事务
inner(); // ❌ 不会创建新事务,因为外层没有事务
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void inner() {
// 事务不生效
}
4.7 场景七:异常被捕获未抛出
@Transactional
public void method() {
try {
// 业务代码,可能抛出异常
} catch (Exception e) {
// ❌ 捕获异常后未重新抛出,事务不会回滚
log.error("error", e);
}
}
五、分布式事务解决方案
当微服务架构下,一个业务操作涉及多个数据源或服务时,就需要分布式事务。
5.1 分布式事务理论
CAP理论:分布式系统无法同时满足一致性(Consistency)、可用性(Availability)、分区容错性(Partition Tolerance),只能三选二。
BASE理论:基本可用(Basically Available)、软状态(Soft State)、最终一致性(Eventually Consistent)。
5.2 常见解决方案
| 方案 |
原理 |
适用场景 |
优点 |
缺点 |
| 2PC/3PC |
两阶段提交协议 |
强一致性要求 |
实现简单 |
性能差,单点故障 |
| TCC |
Try-Confirm-Cancel |
强一致性、高并发 |
最终一致,性能好 |
侵入性大,实现复杂 |
| 可靠消息 |
消息事务+本地消息表 |
最终一致 |
解耦,高可用 |
有延迟 |
| 最大努力通知 |
异步通知+重试 |
对一致性要求不高 |
简单 |
不能保证强一致 |
| Seata |
AT模式(自动补偿) |
通用微服务 |
无侵入,支持多种模式 |
性能略低于TCC |
5.3 Seata AT模式原理(主流方案)
Seata的AT模式通过 数据源代理 实现,无需修改业务代码:
全局事务(Global Transaction)
├── 分支事务1(Branch Transaction)
├── 分支事务2(Branch Transaction)
└── 分支事务3(Branch Transaction)
执行流程:
- 第一阶段(执行分支事务):
- 业务SQL执行
- 记录
undo_log(前置镜像和后置镜像)
- 提交本地事务
- 第二阶段(全局提交/回滚):
- 全局提交:异步清理undo_log
- 全局回滚:根据undo_log生成反向SQL补偿
5.4 可靠消息最终一致性(本地消息表)
// 本地消息表示例
@Transactional
public void createOrder(Order order) {
// 1. 保存订单
orderDao.insert(order);
// 2. 保存消息记录(状态=待发送)
messageDao.insert(new Message(order.getId(), MessageStatus.PENDING));
}
// 定时任务扫描待发送消息
@Scheduled(cron = "0/10 * * * * ?")
public void sendMessage() {
List<Message> messages = messageDao.selectPending();
for (Message msg : messages) {
try {
// 发送MQ消息
mqProducer.send(msg);
// 更新状态为已发送
messageDao.updateStatus(msg.getId(), MessageStatus.SENT);
} catch (Exception e) {
// 重试
}
}
}
六、实战案例
6.1 案例一:资金转账事务失效排查
现象:转账接口偶尔出现数据不一致,A账户扣款成功,B账户未入账。
排查过程:
- 检查方法是否为public(✅)
- 检查异常是否被捕获(❌ 发现
SQLException 被捕获后未抛出)
- 检查事务传播行为(✅ REQUIRED)
- 检查数据库引擎(✅ InnoDB)
根因:
catch (SQLException e) {
log.error("转账失败", e);
throw new RuntimeException("转账失败", e); // ✅ 解决方案:抛出运行时异常
}
6.2 案例二:嵌套事务回滚问题
需求:主方法记录操作日志,即使业务失败也要保存日志。
@Service
public class BusinessService {
@Autowired
private LogService logService;
@Transactional
public void doBusiness() {
try {
// 业务逻辑
insertData();
} catch (Exception e) {
// 业务失败,需要回滚,但日志不能回滚
logService.saveLog("业务失败");
throw e;
}
}
}
@Service
public class LogService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveLog(String msg) {
// 独立事务,不受外部影响
}
}
6.3 案例三:分布式事务Seata实践
# application.yml
seata:
enabled: true
application-id: order-service
tx-service-group: my_test_tx_group
config:
type: nacos
registry:
type: nacos
@GlobalTransactional
public void createOrder(Order order) {
// 本地事务(订单库)
orderDao.insert(order);
// 远程调用库存服务(独立数据源)
inventoryFeign.decrease(order.getProductId(), order.getQuantity());
// 远程调用账户服务(独立数据源)
accountFeign.decrease(order.getUserId(), order.getAmount());
}
七、思维导图总结
Spring事务机制
├── 核心原理
│ ├── PlatformTransactionManager
│ ├── TransactionInterceptor(AOP)
│ └── TransactionStatus
├── 传播行为(7种)
│ ├── REQUIRED(默认)
│ ├── REQUIRES_NEW
│ ├── NESTED
│ └── SUPPORTS / MANDATORY / NOT_SUPPORTED / NEVER
├── 隔离级别
│ ├── DEFAULT(数据库默认)
│ ├── READ_UNCOMMITTED / READ_COMMITTED
│ └── REPEATABLE_READ / SERIALIZABLE
├── 事务失效场景
│ ├── 非public方法
│ ├── 自调用(内部调用)
│ ├── 异常类型不匹配
│ ├── 多线程环境
│ ├── 数据库引擎不支持
│ ├── 传播特性使用不当
│ └── 异常被捕获未抛出
└── 分布式事务
├── 2PC/3PC
├── TCC
├── 可靠消息
├── 最大努力通知
└── Seata(AT模式)
八、面试Tips
加分话术
- 源码结合:“Spring事务通过
TransactionInterceptor 拦截目标方法,根据 @Transactional 配置的事务属性,由 PlatformTransactionManager 管理事务。”
- 失效分析:“自调用失效是因为Spring的AOP基于代理,内部调用走的是
this 而非代理对象,无法触发事务增强。”
- 传播行为:“
REQUIRES_NEW 会挂起当前事务,开启新事务,常用于日志记录等独立操作,避免主事务回滚导致日志也回滚。”
- 分布式事务:“Seata的AT模式通过数据源代理记录
undo_log ,实现自动补偿,对业务代码无侵入,适合微服务架构。”
常见追问准备
- Spring事务的传播行为在嵌套事务中如何工作?
@Transactional 注解的 readOnly 属性有什么用?
- 什么是“事务挂起”?如何实现?
- 如何自定义事务管理器?
- 分布式事务的最终一致性如何保证?
避坑指南
- ❌ 不要混淆
Propagation.NESTED 和 REQUIRES_NEW——嵌套事务依赖于保存点,外部回滚内部也回滚
- ❌ 不要认为所有异常都会回滚——默认只回滚
RuntimeException 和 Error
- ❌ 不要忽视
@Transactional 在同一个类中调用失效的问题
- ✅ 强调“使用
self 注入或 AopContext.currentProxy() 解决自调用”
- ✅ 区分“声明式事务”和“编程式事务”
理解Spring事务的这些核心机制,不仅能从容应对 面试求职 中的挑战,更是编写健壮、可靠业务代码的基石。希望这篇深入浅出的解析能帮助大家,也欢迎在 云栈社区 的 Java 版块交流更多实战经验和疑难杂症。