一、核心概念
事务传播行为,指的是当一个事务方法被另一个事务方法调用时,这个方法应该如何参与事务的规则 。Spring 提供了 7 种传播行为,通过 @Transactional(propagation = Propagation.XXX) 来指定。
二、7 种传播行为详解(附代码示例)
1. REQUIRED(默认传播行为)
-
规则:如果当前存在事务,就加入该事务;如果当前没有事务,就创建一个新事务。
-
适用场景:绝大多数业务场景,保证方法在事务中执行。
-
代码示例:
@Service
public class OrderService {
@Autowired
private OrderDao orderDao;
@Autowired
private ProductService productService;// 外层方法:无事务时创建新事务 @Transactional(propagation = Propagation.REQUIRED) public void createOrder(Order order) { orderDao.save(order); // 调用内层方法:加入当前事务 productService.reduceStock(order.getProductId(), order.getCount()); }}
@Service
public class ProductService {
@Autowired
private ProductDao productDao;// 内层方法:默认REQUIRED,会加入外层事务 @Transactional(propagation = Propagation.REQUIRED) public void reduceStock(Long productId, Integer count) { productDao.updateStock(productId, count); // 若此处抛异常,外层事务也会回滚 if (count < 0) { throw new RuntimeException("库存不足"); } }}
-
说明 :如果
createOrder先执行(无事务),会创建新事务;reduceStock调用时会加入这个事务。若reduceStock抛异常,整个事务回滚,订单和库存操作都会撤销。
2. REQUIRES_NEW
-
规则:无论当前是否存在事务,都创建一个新事务;如果当前存在事务,将当前事务挂起。
-
适用场景:需要独立事务的场景(如日志记录、第三方通知,即使主事务回滚,日志也要保留)。
-
代码示例:
@Service
public class OrderService {
@Autowired
private OrderDao orderDao;
@Autowired
private LogService logService;@Transactional(propagation = Propagation.REQUIRED) public void createOrder(Order order) { orderDao.save(order); // 调用日志方法:创建新事务 logService.recordLog("创建订单:" + order.getId()); // 模拟异常,主事务回滚 throw new RuntimeException("创建订单失败"); }}
@Service
public class LogService {
@Autowired
private LogDao logDao;@Transactional(propagation = Propagation.REQUIRES_NEW) public void recordLog(String content) { Log log = new Log(); log.setContent(content); logDao.save(log); }}
-
说明 :
createOrder抛异常回滚,但recordLog是独立新事务,会正常提交,日志会被保存,不受主事务影响。
3. SUPPORTS
-
规则:如果当前存在事务,就加入该事务;如果当前没有事务,就以非事务方式执行。
-
适用场景:不需要强制事务的辅助方法(如查询类方法,有事务时共享事务,无事务时正常执行)。
-
代码示例:
@Service
public class OrderService {
@Autowired
private OrderDao orderDao;
@Autowired
private QueryService queryService;@Transactional(propagation = Propagation.REQUIRED) public void updateOrder(Order order) { orderDao.update(order); // 调用查询方法:加入当前事务 queryService.getOrderDetail(order.getId()); }}
@Service
public class QueryService {
@Autowired
private OrderDao orderDao;@Transactional(propagation = Propagation.SUPPORTS) public Order getOrderDetail(Long orderId) { // 有事务时,查询在事务中;无事务时,直接查询 return orderDao.selectById(orderId); }}
-
说明 :如果
updateOrder有事务,getOrderDetail会加入事务;如果直接调用getOrderDetail(无事务),则非事务执行。
4. NOT_SUPPORTED
-
规则:以非事务方式执行;如果当前存在事务,将当前事务挂起。
-
适用场景:不需要事务支持的操作(如纯查询、缓存更新,避免事务锁影响性能)。
-
代码示例:
@Service
public class CacheService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;@Transactional(propagation = Propagation.NOT_SUPPORTED) public void updateCache(String key, Object value) { // 非事务执行,即使外层有事务也会被挂起 redisTemplate.opsForValue().set(key, value); }}
-
说明 :如果外层方法有事务,调用
updateCache时会挂起外层事务,执行完后再恢复外层事务。
5. MANDATORY
-
规则:如果当前存在事务,就加入该事务;如果当前没有事务,就抛出异常。
-
适用场景:必须在事务中执行的核心业务方法(确保方法不会在无事务环境下执行)。
-
代码示例:
@Service
public class PaymentService {
@Autowired
private PaymentDao paymentDao;@Transactional(propagation = Propagation.MANDATORY) public void pay(Payment payment) { paymentDao.save(payment); }}
// 测试:无事务调用 MANDATORY 方法
@Service
public class TestService {
@Autowired
private PaymentService paymentService;// 无事务注解 public void testMandatory() { // 调用 pay 时,因为无事务,会抛出 IllegalTransactionStateException paymentService.pay(new Payment()); }}
-
说明 :
testMandatory无事务,调用pay(MANDATORY)会直接抛异常,确保pay必须在事务中执行。
6. NESTED
-
规则:如果当前存在事务,就在嵌套事务中执行;如果当前没有事务,就创建一个新事务。嵌套事务是外层事务的子事务,可独立提交 / 回滚,但外层回滚时子事务也会回滚。
-
适用场景:需要部分回滚的场景(如批量操作,某条失败回滚该条,不影响其他)。
-
代码示例:
@Service
public class BatchService {
@Autowired
private ItemDao itemDao;@Transactional(propagation = Propagation.REQUIRED) public void batchInsert(List<Item> items) { for (Item item : items) { try { insertItem(item); } catch (Exception e) { // 子事务回滚,不影响外层事务 System.out.println("插入失败:" + item.getName() + ",仅回滚该条"); } } } @Transactional(propagation = Propagation.NESTED) public void insertItem(Item item) { itemDao.save(item); if (item.getPrice() < 0) { throw new RuntimeException("价格非法"); } }}
-
说明 :
batchInsert是外层事务,insertItem是嵌套事务。若某条item价格非法,insertItem回滚,但外层事务继续执行其他插入,最终外层提交时所有成功的插入都会生效。
7. NEVER
-
规则:以非事务方式执行;如果当前存在事务,就抛出异常。
-
适用场景:绝对不允许在事务中执行的方法(如某些性能敏感的纯查询,避免事务锁)。
-
代码示例:
@Service
public class FastQueryService {
@Autowired
private DataDao dataDao;@Transactional(propagation = Propagation.NEVER) public List<Data> fastQuery() { // 非事务执行,若外层有事务则抛异常 return dataDao.selectAll(); }}
// 测试:有事务调用 NEVER 方法
@Service
public class TestService {
@Autowired
private FastQueryService fastQueryService;@Transactional public void testNever() { // 调用 fastQuery 时,因为外层有事务,会抛出 IllegalTransactionStateException fastQueryService.fastQuery(); }}
-
说明 :
testNever有事务,调用fastQuery(NEVER)会直接抛异常,确保方法不在事务中执行。
三、关键补充
- 传播行为的生效依赖 Spring AOP 代理,因此同类方法内部调用不会触发传播行为(需通过代理对象调用)。
- 嵌套事务(NESTED)依赖数据库的保存点(Savepoint)功能,并非所有数据库都支持。
保存点(Savepoint)是什么?
保存点是数据库事务中的一个标记点 ,作用是让你在事务执行过程中,只回滚到这个标记点,而不是回滚整个事务。
举个例子:假设你在一个事务里执行了三步操作:
- 插入订单记录
- 扣减商品库存
- 生成支付流水
你在第 2 步执行完后设置了一个保存点 sp1。如果第 3 步失败了,你可以选择只回滚到 sp1,这样 "插入订单" 和 "扣减库存" 的操作会保留,只有 "生成支付流水" 被撤销,然后你可以修正第 3 步的逻辑继续执行,最后整个事务提交。
如果没有保存点,一旦第 3 步失败,整个事务(1+2+3)都会被回滚,之前的操作全部作废。
为什么 Spring 的 NESTED 嵌套事务依赖保存点?
Spring 的 NESTED 传播行为,本质是外层事务的子事务,它的核心特性是:
- 内层方法可以独立提交 / 回滚,但外层事务回滚时,内层也会跟着回滚
- 内层回滚时,不会影响外层事务的其他部分
这个特性完全依赖数据库的 Savepoint 实现:
- 当进入内层
NESTED方法时,Spring 会通过 JDBC 向数据库发送SAVEPOINT sp_name命令,设置一个保存点。 - 如果内层方法正常执行,Spring 会释放这个保存点。
- 如果内层方法抛出异常,Spring 会执行
ROLLBACK TO SAVEPOINT sp_name,只回滚到保存点的位置,而不是回滚整个外层事务。 - 外层事务可以继续执行其他逻辑,最终一起提交。
如果数据库不支持 Savepoint,Spring 就无法创建这个标记点,NESTED 的 "独立回滚" 特性会失效 ------ 内层异常会直接导致整个外层事务回滚,达不到嵌套事务的预期效果。
哪些数据库不支持 Savepoint?
主流的生产级数据库大多支持 Savepoint,但一些轻量级、嵌入式或较老的数据库可能不支持或支持有限:
| 数据库类型 | Savepoint 支持情况 |
|---|---|
| MySQL(InnoDB) | ✅ 支持 |
| PostgreSQL | ✅ 支持 |
| Oracle | ✅ 支持 |
| SQL Server | ✅ 支持 |
| SQLite | ⚠️ 3.6.8 之后支持,但部分场景有局限 |
| HSQLDB(早期版本) | ❌ 不支持 |
| Derby(某些模式) | ⚠️ 支持但有性能限制 |
| Access / FoxPro | ❌ 不支持 |
| 一些小众嵌入式数据库 | ❌ 不支持 |
开发中的注意事项
-
如果用了不支持 Savepoint 的数据库:
- 不要使用
NESTED传播行为,否则会导致事务行为异常。 - 可以改用
REQUIRES_NEW(完全独立的新事务)来实现类似 "部分回滚" 的效果,但注意REQUIRES_NEW会创建独立事务,外层回滚不会影响内层提交。
- 不要使用
-
即使数据库支持 Savepoint:
- 要确保 JDBC 驱动版本支持 Savepoint API(JDBC 3.0 及以上才支持)。
- 有些数据库对嵌套 Savepoint(一个事务里设置多个保存点)的支持有限,需要测试验证。