Spring 事务管理
一句话核心:Spring 用 AOP 将数据库连接与业务方法绑定在 同一线程,把“开始–提交/回滚”的模板逻辑交给 PlatformTransactionManager
,开发者只需通过 @Transactional
描述业务边界。
1. 事务管理方式
方式 | 典型用法 | 适合场景 | 优劣 |
---|---|---|---|
声明式(推荐)@Transactional |
在类/方法上加注解 | 绝大多数业务方法 | 语义清晰、与业务解耦,易统一管控 |
编程式TransactionTemplate |
transactionTemplate.execute(cb) |
需要围绕 部分代码块 做事务、或按运行时条件决定事务属性 | API 简单,但仍破坏业务代码整洁 |
底层 APIPlatformTransactionManager |
getTransaction→commit/rollback |
框架/中间件自研、对事务粒度完全掌控 | 代码冗长,易漏提交/回滚,更常用于封装模板 |
2. 核心配置与属性
属性 | 默认 | 说明 |
---|---|---|
传播行为 Propagation | REQUIRED |
调用方有事务→加入,没有则新建 |
隔离级别 Isolation | DEFAULT (由驱动决定,一般 = DB 默认) |
防脏读/不可重复读/幻读;应与 DB 事务隔离级别对应 |
超时 Timeout | -1 (无限) |
单位秒,超过则抛 TransactionTimedOutException 回滚 |
只读 ReadOnly | false |
表示逻辑上只读,部分驱动可做优化;若执行写操作会被 DB 拒绝(取决于驱动) |
回滚规则 RollbackFor | 仅 RuntimeException , Error |
可指定 rollbackFor = Exception.class 覆盖;noRollbackFor 反向指定 |
易错点:
@Transactional
只有 外部调用 经过 Spring 代理时才生效;同类内部方法互调无法触发事务拦截(需拆分 Service 或注入代理自身)。
3. 传播行为速查表
Propagation | 行为描述 | 常见用例 |
---|---|---|
REQUIRED | 有则加入,无则新建 | 默认;大多数业务场景 |
REQUIRES_NEW | 挂起外层事务,永远新建 | 记录操作日志、发送消息等子事务 |
NESTED | 若存在事务则开启 保存点,可局部回滚 | 多步扣减库存(回滚子步骤) |
NOT_SUPPORTED | 以 非事务 运行,挂起外层事务 | 查询大表避免长事务锁 |
SUPPORTS | 有事务就加入,无事务就非事务 | 读场景兼容 |
MANDATORY | 必须在事务中调用,否则异常 | DAO 层硬性要求 |
NEVER | 调用方不能有事务 | 只读报表专用 |
4. 典型代码范式
4.1 Service 层声明式事务
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository repo;
private final PaymentService paymentService;
@Transactional(rollbackFor = Exception.class)
public void createOrder(OrderDTO dto) {
Order order = repo.save(convert(dto)); // ① 写订单
paymentService.pay(order); // ② 调远程
// ③ 其它逻辑
}}
- 传播:默认
REQUIRED
,pay()
若遇异常将回滚整个事务。- 回滚:显式
rollbackFor = Exception.class
,防止业务抛 受检异常 时 Spring 不回滚。
4.2 编程式—模板控制
@TransactionalService
public class ReportJob {
private final TransactionTemplate tx;
public void generate() {
tx.executeWithoutResult(status -> {
try { doWriteHeavyQuery(); // 可能超时
} catch (Exception ex) { status.setRollbackOnly(); // 手动回滚
log.error("生成报表失败", ex);
} }); }}
4.3 多数据源 ChainedTransactionManager
@Bean
public PlatformTransactionManager chainedTx(
DataSourceTransactionManager dsTx, JpaTransactionManager jpaTx) { return new ChainedTransactionManager(dsTx, jpaTx);}
应用场景:单个业务方法需同时写关系表和 NoSQL(Mongo 等),要求原子性。Spring 通过依次提交/回滚多个 TxM “伪两段” 解决,仍非强一致,生产建议升级为 分布式事务/最终一致 方案(如 Seata、TCC、SAGA)。
5. 与分布式事务的衔接
思路 | 方案 | 适用 |
---|---|---|
本地事务 + 可靠消息 | Spring 事务内插入消息表,提交后异步发送 | 电商下单、异步库存扣减 |
Seata AT / TCC | Spring Boot + Seata Starter | 多库 ACID,要在同局域网 |
Saga(状态机) | Spring Cloud Alibaba Saga / Akka | 长事务、跨微服务编排 |
两阶段提交 XA | Atomikos, Narayana | 对性能敏感度低、需要强一致 |
6. 常见坑与最佳实践 ✅
- 同类内部调用失效
- 解决:将方法抽到新 Bean;或通过
AopContext.currentProxy()
获取代理再调用。
- 受检异常不回滚
- 添加
rollbackFor = Exception.class
或把业务异常继承RuntimeException
。
- Long Tx 锁表
- 拆分读写、使用
PROPAGATION_REQUIRES_NEW
子事务,或按乐观锁重试。
- 批量写性能差
- 关闭自动提交,分批 flush;或用 JDBC batch。
- 事务传播 + 异常捕获
- 不要在外层吞掉异常,否则 Spring 判断不到失败 ➜ 无回滚;捕获后务必
throw
或setRollbackOnly()
。
- 只读事务滥用
readOnly=true
仅在某些驱动生效;若混用写操作会抛错。
- 多线程不共享事务
@Async
、CompletableFuture 会脱离当前线程,无事务;需要在父线程先提交或采用TransactionAware
设计。
- 连接泄漏
- 必须使用 Spring 数据访问模板或
@Transactional
让框架接管,自己getConnection()
要确保 finally 关闭。
7. Checklist for Production
- ☐ 将事务边界放在 Service 层,Controller 尽量无事务。
- ☐ 所有新增的 DAO 方法确保在事务内调用。
- ☐ 定期用 p6spy / slow query 审计长事务 & 慢 SQL。
- ☐ 监控连接池活跃连接数,防重叠长事务耗尽连接。
- ☐ 对高并发写入表开启 乐观锁 + 重试,减少死锁。
- ☐ 核心业务使用 幂等设计 + 补偿机制,降低分布式回滚复杂度。
总结:Spring 事务本质是 AOP + ThreadLocal 封装模板,把海量重复的连接与回滚逻辑隐藏,让开发只关注业务边界。掌握
@Transactional
的属性组合、传播语义以及常见陷阱,即可在单体或微服务架构中编写高可靠的数据库操作。