事务是后端开发中最容易"以为对了实际没生效"的环节。本文将系统拆解 Spring 事务抽象、传播行为、隔离级别、失效场景、声明式与编程式事务,以及多数据源下的事务方案,帮你彻底避开生产事故。
一、Spring 事务抽象:PlatformTransactionManager
1️⃣ 核心接口
Spring 事务的核心抽象是 PlatformTransactionManager:
PlatformTransactionManager
├── DataSourceTransactionManager (JDBC / MyBatis / MP)
├── JpaTransactionManager (JPA / Hibernate)
├── JtaTransactionManager (分布式事务)
└── ReactiveTransactionManager (响应式)
📌 Spring Boot 4 自动配置:只要 classpath 中有 DataSource + spring-tx,就会自动创建 DataSourceTransactionManager。
2️⃣ 事务的 ACID
|
Atomicity(原子性) |
全部成功或全部回滚 |
|
Consistency(一致性) |
数据从一个一致状态到另一个一致状态 |
|
Isolation(隔离性) |
并发事务互不干扰 |
|
Durability(持久性) |
提交后永久生效 |
二、@Transactional 注解全解
1️⃣ 基础用法
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private AccountMapper accountMapper;
@Transactional
public void createOrder(Long userId, BigDecimal amount) {
// 扣余额
accountMapper.deduct(userId, amount);
// 创建订单
orderMapper.insert(new Order(userId, amount));
// 如果这里抛异常,上面扣的钱会回滚
}
}
✅ 默认行为:
- 传播行为:Propagation.REQUIRED
- 隔离级别:Isolation.DEFAULT(跟随数据库,MySQL 默认 REPEATABLE_READ)
- 回滚:RuntimeException 和 Error 自动回滚
- 超时:-1(不超时)
- 只读:false
2️⃣ 完整参数
@Transactional(
propagation = Propagation.REQUIRED, // 传播行为
isolation = Isolation.READ_COMMITTED, // 隔离级别
rollbackFor = {Exception.class}, // 回滚异常类型
noRollbackFor = {BusinessException.class}, // 不回滚的异常
timeout = 30, // 超时(秒)
readOnly = false // 只读优化
)
public void complexBusiness() {
// …
}
三、传播行为 7 种全解(面试必问)
1️⃣ 速查表
|
REQUIRED(默认) |
有事务加入,没有新建 |
大多数业务方法 |
|
REQUIRES_NEW |
挂起当前事务,新建独立事务 |
日志/审计(必须独立提交) |
|
NESTED |
嵌套事务(保存点) |
部分回滚 |
|
SUPPORTS |
有事务加入,没有非事务运行 |
查询方法 |
|
NOT_SUPPORTED |
非事务运行,挂起当前事务 |
批量处理 |
|
MANDATORY |
必须在事务中,否则抛异常 |
强制调用方提供事务 |
|
NEVER |
必须在非事务中,否则抛异常 |
极少用 |
2️⃣ 经典场景:REQUIRES_NEW
@Service
public class BusinessService {
@Autowired
private LogService logService;
@Transactional
public void placeOrder(Order order) {
// 主事务
orderMapper.insert(order);
try {
// 独立事务:即使主事务回滚,日志也提交
logService.saveLog("订单创建: " + order.getId());
} catch (Exception e) {
// 日志失败不影响主流程
}
if (order.getAmount().compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("金额不能为负");
}
}
}
@Service
public class LogService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveLog(String content) {
logMapper.insert(new Log(content));
}
}
📌 执行流程:
placeOrder 事务开始
→ orderMapper.insert()
→ saveLog 事务开始(挂起 placeOrder 事务)
→ logMapper.insert() → 提交
→ placeOrder 事务恢复
→ 抛异常 → placeOrder 回滚
→ 结果:订单没插入,日志已提交 ✅
3️⃣ NESTED vs REQUIRES_NEW
|
是否独立事务 |
否(同一连接,保存点) |
是(新连接) |
|
外层回滚 |
内层一起回滚 |
内层不受影响 |
|
内层回滚 |
外层可捕获,不回滚外层 |
外层可捕获,不回滚外层 |
|
数据库要求 |
需要 JDBC 保存点支持 |
无 |
四、隔离级别与并发问题
1️⃣ 并发事务的 3 大问题
|
脏读 |
读到未提交的数据 |
A 改了没提交,B 读到了 |
|
不可重复读 |
同一事务内两次读结果不同 |
A 事务中两次查余额,B 事务中间改了 |
|
幻读 |
同一事务内两次查询结果集行数不同 |
A 事务查 count,B 事务插入新行 |
2️⃣ 隔离级别对照
|
READ_UNCOMMITTED |
❌ |
❌ |
❌ |
最高 |
|
READ_COMMITTED |
✅ |
❌ |
❌ |
高 |
|
REPEATABLE_READ |
✅ |
✅ |
❌ |
中 |
|
SERIALIZABLE |
✅ |
✅ |
✅ |
最低 |
📌 MySQL 默认 REPEATABLE_READ,Oracle 默认 READ_COMMITTED
3️⃣ 实战配置
@Transactional(isolation = Isolation.READ_COMMITTED)
public BigDecimal getBalance(Long userId) {
return accountMapper.getBalance(userId);
}
五、@Transactional 失效的 6 种经典场景
❌ 场景1:同类方法自调用
@Service
public class UserService {
public void methodA() {
methodB(); // ❌ 事务失效!
}
@Transactional
public void methodB() {
// …
}
}
📌 原因:Spring 事务基于代理,自调用不经过代理对象
✅ 解决方式 1:注入自己
@Service
public class UserService {
@Autowired
private UserService self; // 注入代理对象
public void methodA() {
self.methodB(); // ✅ 走代理
}
@Transactional
public void methodB() {
// …
}
}
✅ 解决方式 2:AOP 上下文
AopContext.currentProxy(); // 需要 @EnableAspectJAutoProxy(exposeProxy = true)
✅ 解决方式 3:拆到另一个 Service
❌ 场景2:方法不是 public
@Transactional
private void method() {} // ❌ 不生效
✅ 解决:改为 public
❌ 场景3:异常被 catch 吞掉
@Transactional
public void method() {
try {
int i = 1 / 0;
} catch (Exception e) {
// ❌ 吞掉异常,事务不回滚
}
}
✅ 解决:
catch (Exception e) {
throw e; // 或 throw new RuntimeException(e);
}
❌ 场景4:异常类型不匹配
@Transactional
public void method() throws Exception {
throw new Exception("checked exception"); // ❌ 默认不回滚
}
✅ 解决:
@Transactional(rollbackFor = Exception.class)
❌ 场景5:数据库引擎不支持事务
— MyISAM 不支持事务
CREATE TABLE user (…) ENGINE=MyISAM; — ❌
✅ 解决:用 InnoDB
❌ 场景6:多数据源未指定事务管理器
@Transactional // ❌ 不知道用哪个事务管理器
✅ 解决:
@Transactional(transactionManager = "masterTransactionManager")
六、声明式 vs 编程式事务
1️⃣ 声明式(@Transactional)
✅ 优点:简单、侵入性低
❌ 缺点:粒度粗、自调用失效
2️⃣ 编程式(TransactionTemplate)
@Service
public class UserService {
@Autowired
private TransactionTemplate transactionTemplate;
public Result<?> batchCreate(List<User> users) {
return transactionTemplate.execute(status -> {
try {
users.forEach(userMapper::insert);
return Result.success();
} catch (Exception e) {
status.setRollbackOnly(); // 手动回滚
return Result.error("批量创建失败");
}
});
}
}
✅ 适用场景:需要细粒度控制、try-catch 内回滚
七、多数据源事务方案
方案1:指定事务管理器(单事务)
@Transactional(transactionManager = "masterTransactionManager")
public void masterOp() {}
@Transactional(transactionManager = "slaveTransactionManager")
public void slaveOp() {}
📌 限制:两个操作不能在同一事务中
方案2:JTA(分布式事务)
引入 Atomikos / Narayana:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
📌 XA 两阶段提交,性能差,不推荐高并发
方案3:Seata(阿里开源,推荐)
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
📌 AT 模式,无侵入,性能优于 XA
八、Spring Boot 4 中的变化
- 虚拟线程下事务同步管理器兼容
- 事务超时与虚拟线程调度更精确
- 对 record 类型事务代理支持完善
九、常见坑位总结
|
自调用事务失效 |
拆类 / 注入代理 / AopContext |
|
异常被吞 |
重新抛出 |
|
checked 异常不回滚 |
rollbackFor = Exception.class |
|
多数据源事务混乱 |
明确指定 transactionManager |
|
只读事务没优化 |
@Transactional(readOnly = true) |
|
长事务锁表 |
缩小事务范围,不要包含 RPC 调用 |


