SpringBoot事务机制:用"公司报销流程"理解传播机制

209 阅读3分钟

一、SpringBoot事务机制:用"公司报销流程"理解传播机制

1. 事务传播的本质场景

想象你在公司处理报销流程:

  • 当你在处理部门报销时(主事务)
  • 突然需要处理紧急的差旅报销(子事务)
  • 两个报销如何处理关系?这就是事务传播要解决的问题

2. 七大传播级别大白话解析

① REQUIRED(默认值):接力盖章

@Transactional(propagation = Propagation.REQUIRED)
public void mainMethod() {
    // 主流程开始盖章(开启事务)
    subMethod(); // 子方法使用REQUIRED
}

@Transactional(propagation = Propagation.REQUIRED)
public void subMethod() {
    // 发现已有盖章,直接使用同一个章
    // 任何一个环节出错,整个报销单作废
}

比喻:部门报销进行到一半,新增差旅报销时直接在同一张报销单上继续填写。任一错误整单重填。

② REQUIRES_NEW:另起炉灶

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void subMethod() {
    // 无论主流程是否盖章,都启用新报销单
    // 新报销单的成败不影响主流程
}

比喻:主报销单处理时,突然需要处理紧急报销,立即启用新报销单单独处理,两个报销互不影响。

③ NESTED:子流程保险箱

@Transactional(propagation = Propagation.NESTED)
public void subMethod() {
    // 在主报销单内创建存档点
    // 子流程失败只回滚存档点之后的操作
}

比喻:主报销单处理时,将当前进度拍照存档。子流程失败时,只恢复到存档点,不影响存档前的操作。

④ MANDATORY:必须搭车

@Transactional(propagation = Propagation.MANDATORY)
public void subMethod() {
    // 必须存在主报销单才能执行
    // 单独调用会抛出异常
}

比喻:补充材料必须依附于主报销单存在,单独提交补充材料会被财务直接驳回。

⑤ SUPPORTS:随波逐流

@Transactional(propagation = Propagation.SUPPORTS)
public void subMethod() {
    // 有主报销单就跟着用
    // 没有就普通流程处理
}

比喻:普通文件审批,如果有报销流程就跟着走流程,没有就按常规处理。

⑥ NOT_SUPPORTED:拒绝参与

@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void subMethod() {
    // 暂停当前报销流程
    // 以非事务方式执行
}

比喻:处理报销时需要查询外部系统,此时暂停报销流程,直接进行查询操作。

⑦ NEVER:严禁参与

@Transactional(propagation = Propagation.NEVER)
public void subMethod() {
    // 严禁在事务中调用
    // 否则直接报错
}

比喻:机密文件处理时,如果发现被包含在常规报销流程中,立即终止操作。

3. 事务失效的六大陷阱

① 自调用陷阱

public class OrderService {
    public void createOrder() {
        validateStock(); // 自调用失效!
    }
    
    @Transactional
    private void validateStock() {
        // 事务注解不生效
    }
}

解决方法:注入自身Bean

@Autowired
private OrderService self;

public void createOrder() {
    self.validateStock(); // 通过代理调用
}

② 异常类型不匹配

@Transactional
public void updateData() throws Exception {
    try {
        // 数据库操作
    } catch (Exception e) {
        throw new Exception("错误"); // 非RuntimeException
    }
}

修正方案

@Transactional(rollbackFor = Exception.class)
public void updateData() throws Exception {
    // ...
}

③ 非public方法

@Transactional
protected void internalProcess() {
    // 事务不生效!
}

④ 多线程断联

@Transactional
public void batchProcess() {
    new Thread(() -> {
        // 新线程中的操作不在事务中
        updateInventory(); 
    }).start();
}

⑤ 特殊方法过滤

@Transactional
public final void finalMethod() {
    // final方法事务失效
}

⑥ 错误捕获

@Transactional
public void process() {
    try {
        // 数据库操作
    } catch (Exception e) {
        // 捕获异常但未抛出
        log.error("错误", e);
    }
}

正确做法

catch (Exception e) {
    throw new RuntimeException(e);
}

4. 传播机制组合效果表

主事务子事务效果
REQUIREDREQUIRED共用事务,任一失败全回滚
REQUIREDREQUIRES_NEW子事务独立,主事务失败不影响子事务
REQUIREDNESTED子事务可部分回滚,主事务失败强制回滚所有
REQUIREDNOT_SUPPORTED子事务暂停主事务,非事务执行
NEVERREQUIRED直接抛出异常,拒绝执行

5. 调试技巧

  1. 开启事务日志:
logging.level.org.springframework.jdbc=DEBUG
logging.level.org.springframework.transaction=TRACE
  1. 检查代理类型:
@SpringBootApplication
@EnableTransactionManagement(proxyTargetClass = true)
public class Application { ... }
  1. 使用事务模板:
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.execute(status -> {
    // 事务操作
    return null;
});