Spring 事务传播行为详解

一、核心概念与七种行为汇总

事务传播行为 定义了一个事务方法在调用另一个事务方法时,事务该如何传递和交互。它解决的核心问题是:当业务方法嵌套调用时,事务的边界应如何界定?

Spring 定义了 7 种传播行为:

传播行为 常量值 核心行为描述(当方法被调用时) 典型应用场景
REQUIRED (默认) Propagation.REQUIRED "需要有事务":如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。 绝大多数业务方法,确保操作在同一个事务中。
SUPPORTS Propagation.SUPPORTS "支持事务":如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续执行。 查询方法,可以接受在事务内或事务外执行。
MANDATORY Propagation.MANDATORY "强制要有事务":如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。 严格要求必须在已有事务中被调用的方法。
REQUIRES_NEW Propagation.REQUIRES_NEW "必须有新事务":无论如何,都会创建一个新的事务。如果当前存在事务,则将其挂起。 需要独立提交、不受外部事务影响的子操作(如审计日志)。
NOT_SUPPORTED Propagation.NOT_SUPPORTED "不支持事务":以非事务方式执行。如果当前存在事务,则将其挂起。 需要在无事务环境下执行,例如调用不支持事务的遗留接口。
NEVER Propagation.NEVER "绝不要事务":以非事务方式执行。如果当前存在事务,则抛出异常。 严格要求不能在事务中被调用的方法。
NESTED Propagation.NESTED "嵌套事务" :如果当前存在事务,则在嵌套事务(基于保存点)内执行;如果当前没有事务,其行为同REQUIRED 需要支持部分回滚的复杂业务,如批量处理。

二、传播行为核心要解决的问题

想象一个典型场景:

java 复制代码
@Service
public class OrderService {
    @Transactional
    public void placeOrder(Order order) {
        // 1. 保存订单主信息
        orderDao.save(order);
        
        // 2. 更新库存(调用另一个事务方法)
        inventoryService.deductStock(order.getItems());
        
        // 3. 记录操作日志
        logService.addLog(order);
    }
}

placeOrder() 已开启事务,而它调用的 deductStock() 也标注了 @Transactional 时,两个事务是什么关系?

  • 合并成一个事务(同生共死)?
  • 各自独立(库存失败,订单依然提交)?
  • 内部方法不启事务(沿用外部事务)?

传播行为(Propagation) 就是用来回答这个问题的。

三、7种传播行为详解

Spring 定义了 7 种传播行为,按使用频率和重要性可分为 核心常用特殊场景 两类。

3.1 核心常用的传播行为

传播行为 常量值 核心语义 适用场景
REQUIRED (默认) Propagation.REQUIRED "需要有事务":有则加入,无则新建 90%的场景,标准的原子性操作
REQUIRES_NEW Propagation.REQUIRES_NEW "必须有新事务":无论有无,都创建新事务,原事务挂起 需要独立提交/回滚的操作(如审计日志)
NESTED Propagation.NESTED "嵌套事务":有则创建保存点,无则新建(类似REQUIRED) 支持部分回滚的复杂业务
SUPPORTS Propagation.SUPPORTS "支持事务":有则加入,无则以非事务执行 查询方法,不改变数据的事务可有可无
1. REQUIRED(默认且最常用)
java 复制代码
@Service
public class UserService {
    @Transactional(propagation = Propagation.REQUIRED) // 可省略,默认就是它
    public void createUser(User user) {
        userDao.save(user);
        profileService.initProfile(user.getId()); // 也是REQUIRED
    }
}

@Service  
public class ProfileService {
    @Transactional(propagation = Propagation.REQUIRED)
    public void initProfile(Long userId) {
        // 此时会加入外部 createUser 的事务
        // 两者同在一个事务中,一荣俱荣,一损俱损
    }
}

特点 :形成统一的事务边界,任何方法失败都会导致全部回滚。

2. REQUIRES_NEW(强隔离)
java 复制代码
@Service
public class OrderService {
    @Transactional(propagation = Propagation.REQUIRED)
    public void processOrder(Order order) {
        orderDao.save(order); // 在事务A中
        
        // 新启事务B,事务A被挂起
        auditService.logOperation("CREATE_ORDER", order.getId());
        
        // 事务A恢复,继续执行
        inventoryService.update(order); // 仍在事务A中
    }
}

@Service
public class AuditService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logOperation(String action, Long targetId) {
        // 总是在自己的新事务中执行
        // 即使外部事务回滚,这条日志也一定会提交
    }
}

特点

  • 创建完全独立的新事务
  • 原事务被挂起,新事务提交/回滚后,原事务恢复
  • 新事务的回滚不会影响原事务(除非原事务捕获了异常)
  • 原事务的回滚不会影响已提交的新事务
3. NESTED(嵌套事务 - 数据库需支持保存点)
java 复制代码
@Service
public class BatchService {
    @Transactional
    public void batchImport(List<Product> products) {
        for (int i = 0; i < products.size(); i++) {
            try {
                productService.importProduct(products.get(i)); // NESTED传播
            } catch (DataIntegrityViolationException e) {
                // 仅跳过当前产品,继续导入下一个
                log.error("产品{}导入失败: {}", i, e.getMessage());
            }
        }
    }
}

@Service
public class ProductService {
    @Transactional(propagation = Propagation.NESTED)
    public void importProduct(Product product) {
        // 如果当前有事务,会设置保存点
        // 此方法失败只会回滚到保存点,不会影响外部事务
    }
}

特点

  • 外部事务内创建保存点(Savepoint)
  • 内部方法失败时,只回滚到保存点,外部事务可选择继续
  • 外部事务提交时,所有嵌套操作一起提交
  • 外部事务回滚时,所有嵌套操作一起回滚
  • 数据库必须支持保存点(如MySQL的InnoDB支持)
4. SUPPORTS(查询优化)
java 复制代码
@Service
public class QueryService {
    @Transactional(propagation = Propagation.SUPPORTS) // 有事务就用,没有就算了
    public User getUser(Long id) {
        // 如果在事务中,则享受事务的只读/隔离级别好处
        // 如果无事务,直接执行查询
        return userDao.findById(id);
    }
}

3.2 特殊场景的传播行为

传播行为 常量值 核心语义 注意点
MANDATORY Propagation.MANDATORY "强制要有事务":有则加入,无则抛异常 严格的事务环境要求
NOT_SUPPORTED Propagation.NOT_SUPPORTED "不支持事务":以非事务执行,原事务挂起 强制非事务执行,用于不兼容事务的操作
NEVER Propagation.NEVER "绝不要事务":有事务则抛异常 严格非事务环境要求
使用示例
java 复制代码
// MANDATORY:必须被一个事务方法调用
@Transactional(propagation = Propagation.MANDATORY)
public void updateBalance(Long userId, BigDecimal amount) {
    // 此方法绝不能直接被非事务方法调用
}

// NOT_SUPPORTED:强制非事务(如调用外部不支持事务的遗留系统)
@Transactional(propagation = Propagation.NOT_SUPPORTED)  
public void syncToLegacySystem(Data data) {
    // 即使调用方有事务,这里也会挂起事务
    legacyClient.send(data); // 不支持事务的老系统
}

// NEVER:严格禁止事务
@Transactional(propagation = Propagation.NEVER)
public void clearCache() {
    // 纯内存操作,不需要事务
    // 如果调用方有事务,直接抛异常
}

3.3. 核心对比:REQUIRED vs REQUIRES_NEW vs NESTED

场景 外部有事务时 外部无事务时 回滚影响范围 适用场景
REQUIRED 加入外部事务 新建事务 全部回滚 标准业务操作(默认选择)
REQUIRES_NEW 挂起外部事务,建新事务 新建事务 各自独立 审计日志、监控数据
NESTED 嵌套事务(保存点) 新建事务(同REQUIRED) 部分回滚(到保存点) 批量操作(允许部分失败)

四、实际应用中的选择策略

4.1 决策流程

复制代码
是否需要强隔离(失败不影响调用方)?
    ├─ 是 → REQUIRES_NEW(如:操作日志)
    │
    ├─ 否 → 是否需要部分回滚能力?
    │   ├─ 是 → NESTED(如:批量导入)
    │   │
    │   └─ 否 → REQUIRED(默认,90%场景)
    │
    └─ 特殊情况:
        ├─ 查询方法 → SUPPORTS
        ├─ 强制事务环境 → MANDATORY  
        ├─ 强制非事务 → NOT_SUPPORTED/NEVER
        └─ 避免使用 → SUPPORTS/NOT_SUPPORTED/NEVER(除非明确需求)

4.2 经典场景配置

java 复制代码
@Service
@Transactional // 类级别默认REQUIRED
public class OrderServiceImpl implements OrderService {
    
    // 1. 核心业务:使用默认REQUIRED
    public Order createOrder(OrderRequest request) {
        // 订单、库存、支付在同一事务
    }
    
    // 2. 需要独立提交的审计日志
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void auditLog(String action, String detail) {
        // 即使订单失败,日志也要保留
    }
    
    // 3. 批量处理:允许单条失败
    @Transactional
    public BatchResult batchProcess(List<Item> items) {
        for (Item item : items) {
            try {
                processItem(item); // 内部NESTED事务
            } catch (BusinessException e) {
                // 记录失败,继续处理下一个
                failedItems.add(item);
            }
        }
        return new BatchResult(failedItems);
    }
    
    @Transactional(propagation = Propagation.NESTED)
    private void processItem(Item item) {
        // 单条处理,失败只影响自己
    }
    
    // 4. 纯查询:不需要严格事务
    @Transactional(propagation = Propagation.SUPPORTS, 
                   readOnly = true)
    public Order queryOrder(Long id) {
        return orderDao.findById(id);
    }
}

五、重要注意事项

5.1 基于代理的局限

Spring事务基于AOP代理实现,自调用会失效

java 复制代码
@Service
public class ProblemService {
    @Transactional
    public void methodA() {
        methodB(); // ❌ 自调用,事务注解不生效!
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void methodB() {
        // 不会创建新事务,仍使用methodA的事务
    }
}

解决方案

  1. 注入自身代理(不推荐)
  2. 将methodB提取到另一个Service中(推荐)

5.2 异常回滚规则

默认只回滚RuntimeExceptionError,受检异常(Checked Exception)不会触发回滚。

java 复制代码
@Transactional(propagation = Propagation.REQUIRED,
               rollbackFor = {IOException.class, BusinessException.class})
public void process() throws IOException {
    // 现在IOException也会触发回滚
}

5.3 性能影响

  • REQUIRES_NEW:开销最大,涉及事务挂起/恢复
  • NESTED:保存点有额外开销,但小于新建事务
  • REQUIRED:性能最优

六、最佳实践总结

  1. 无明确需求时,保持默认 :绝大多数方法使用 REQUIRED 即可。
  2. 谨慎使用 REQUIRES_NEW:虽然隔离性好,但会带来性能开销和复杂的事务管理,仅在必要时使用。
  3. 明确使用只读事务 :对于纯查询方法,结合 @Transactional(readOnly = true) 使用 SUPPORTSREQUIRED,能带来性能优化。
  4. 处理好异常 :默认情况下,Spring 事务只在遇到 RuntimeExceptionError 时回滚。受检异常(Checked Exception)不会触发回滚,如有需要请使用 rollbackFor 参数显式配置。
  5. 保持事务精简:事务中应只包含必要的数据库操作,避免长时间持有数据库连接。
相关推荐
武子康2 小时前
Java-210 Spring AMQP 整合 RabbitMQ:JavaConfig 注解配置、RabbitTemplate 发送/同步接收与坑位速查
xml·java·spring·消息队列·rabbitmq·java-rabbitmq·mq
2345VOR2 小时前
【ESP32C3接入2025年冬火山大模型教程】
开发语言·数据库·豆包·火山
IvanCodes2 小时前
openGauss 核心体系架构深度解析
数据库·sql·postgresql·openguass
陌路202 小时前
redis主从复制
数据库·redis
@淡 定2 小时前
事务ACID特性与隔离级别详解
数据库·oracle
·云扬·2 小时前
MySQL大批量数据导入性能优化:从原理到实践
数据库·mysql·性能优化
廋到被风吹走2 小时前
【Spring】ThreadLocal详解 线程隔离的魔法与陷阱
java·spring·wpf
李白你好2 小时前
Burp Suite 插件 | SQL 注入自定义扫描和分析
sql
weixin_406898222 小时前
人大金仓pg模式批量检查表是否存在
数据库·oracle