Spring事务传播机制详解
Spring 事务用起来简单,但失效场景非常多。很多人遇到过 @Transactional 不生效的情况,却不知道原因。本文把日常开发中常见的坑和对应的排查思路整理出来,帮你避免踩坑。
七种传播行为
| 传播行为 |
含义 |
| REQUIRED |
默认,当前有事务则加入,无则新建 |
| SUPPORTS |
有事务则加入,无则以非事务执行 |
| MANDATORY |
必须有事务,无则抛异常 |
| REQUIRES_NEW |
挂起当前事务,创建新事务 |
| NOT_SUPPORTED |
挂起当前事务,以非事务执行 |
| NEVER |
必须无事务,有则抛异常 |
| NESTED |
在当前事务中创建嵌套事务(savepoint) |
REQUIRED(默认)
@Service public class OrderService { @Transactional public void createOrder() { orderDao.insert(order); stockService.deductStock(); } }
@Service public class StockService { @Transactional(propagation = Propagation.REQUIRED) public void deductStock() { stockDao.update(stock); } }
|
createOrder() └── 事务 T1 开始 ├── orderDao.insert() ← 在 T1 中 ├── deductStock() │ └── stockDao.update() ← 也在 T1 中 └── 事务 T1 提交/回滚(整体)
|
REQUIRES_NEW
@Service public class OrderService { @Transactional public void createOrder() { orderDao.insert(order); logService.saveLog(); orderDao.update(order); } }
@Service public class LogService { @Transactional(propagation = Propagation.REQUIRES_NEW) public void saveLog() { logDao.insert(log); } }
|
createOrder() └── 事务 T1 开始 ├── orderDao.insert() ← T1 ├── saveLog() │ ├── 挂起 T1 │ ├── 事务 T2 开始 │ ├── logDao.insert() ← T2 │ └── 事务 T2 提交/回滚 ├── 恢复 T1 └── orderDao.update() ← T1
|
适用场景:日志记录、审计,即使主业务回滚也要保存。
NESTED
@Service public class OrderService { @Transactional public void createOrder() { orderDao.insert(order); try { paymentService.charge(); } catch (Exception e) { } orderDao.update(order); } }
@Service public class PaymentService { @Transactional(propagation = Propagation.NESTED) public void charge() { paymentDao.insert(payment); if (payment.getAmount().compareTo(BigDecimal.ZERO) < 0) { throw new IllegalArgumentException("金额不能为负"); } } }
|
createOrder() └── 事务 T1 开始 ├── orderDao.insert() ← T1 ├── charge() │ ├── savepoint SP1 创建 │ ├── paymentDao.insert() ← 在 SP1 后 │ ├── 异常发生 │ └── 回滚到 SP1(T1 未回滚) ├── 继续 T1 └── orderDao.update() ← T1
|
NESTED vs REQUIRES_NEW:
| 特性 |
NESTED |
REQUIRES_NEW |
| 事务关系 |
父子事务 |
独立事务 |
| 回滚影响 |
子回滚不影响父 |
互不影响 |
| 实现方式 |
Savepoint |
挂起+新事务 |
| 提交 |
随父事务一起提交 |
独立提交 |
传播行为对比
@Service public class DemoService { @Transactional public void caller() { calleeRequired(); calleeRequiresNew(); calleeNested(); calleeSupports(); calleeNotSupported(); calleeMandatory(); calleeNever(); } }
|
实际应用案例
#
案例1:订单创建(REQUIRED + REQUIRES_NEW)
@Service public class OrderService { @Transactional public void createOrder(OrderRequest request) { Order order = orderDao.save(request.toOrder()); stockService.deduct(order.getSkuId(), order.getQuantity()); logService.saveOperationLog("CREATE_ORDER", order.getId()); messageService.sendOrderCreatedMessage(order); } }
|
#
案例2:部分回滚(NESTED)
@Service public class BatchService { @Transactional public void batchImport(List<Data> dataList) { for (Data data : dataList) { try { importService.importSingle(data); } catch (Exception e) { errorLogService.save(data.getId(), e.getMessage()); } } } }
|
#
案例3:只读查询(SUPPORTS/NOT_SUPPORTED)
@Service public class ReportService { @Transactional(propagation = Propagation.SUPPORTS, readOnly = true) public Report generateReport() { return reportDao.generate(); } }
|
注意事项
#
1. 同类方法调用不生效
@Service public class OrderService { @Transactional public void createOrder() { saveLog(); } @Transactional(propagation = Propagation.REQUIRES_NEW) public void saveLog() { } }
|
解决:
@Service public class OrderService { @Autowired private ApplicationContext context; @Transactional public void createOrder() { OrderService proxy = context.getBean(OrderService.class); proxy.saveLog(); } }
|
#
2. 异常回滚规则
@Transactional(rollbackFor = Exception.class) public void method() throws Exception { }
|
#
3. REQUIRES_NEW 的坑
@Transactional public void parent() { try { childRequiresNew(); } catch (Exception e) { } }
|
总结
| 传播行为 |
使用场景 |
| REQUIRED |
默认,大多数场景 |
| REQUIRES_NEW |
独立操作(日志、消息) |
| NESTED |
部分回滚 |
| SUPPORTS |
查询,不强制事务 |
| NOT_SUPPORTED |
复杂查询,避免事务开销 |
| MANDATORY |
强制必须在事务中 |
| NEVER |
强制必须不在事务中 |
选择正确的传播行为,可以避免事务问题,提高系统可靠性。
核心要点
事务失效的常见原因:非 public 方法、自调用、异常被吞掉、错误的传播级别
事务传播级别决定了方法之间的事务关系,REQUIRED 是默认值
使用 @Transactional(rollbackFor = Exception.class) 确保异常回滚
编程式事务在某些场景下比声明式事务更灵活
总结
事务管理是保证数据一致性的关键。理解事务的工作机制和常见陷阱,能帮你写出更健壮的代码。在实际项目中,合理配置事务边界非常重要。