Java事务传播案例如何区分

wen java案例 23

本文目录导读:

Java事务传播案例如何区分

  1. 核心概念:事务传播行为
  2. 案例场景设计
  3. 如何测试和验证
  4. 一个真实场景的判断技巧

理解Java事务传播行为的关键在于“内外事务方法是否共享同一个事务”,为了让你能清晰区分,下面通过几个典型场景的代码案例来分析。

核心概念:事务传播行为

Spring定义了7种传播行为,最常用来区分的是这3个:

传播行为 行为说明
REQUIRED(默认) 支持当前事务,如果没有则新建
REQUIRES_NEW 总是新建事务,如果已有则挂起
NESTED 如果已有事务,则在嵌套事务中执行

案例场景设计

假设有一个银行系统,包含转账和日志记录两个操作,我们通过不同的传播行为来观察它们的行为差异。

案例1:REQUIRED vs REQUIRES_NEW

@Service
public class BankService {
    @Autowired
    private AccountDao accountDao;
    @Autowired
    private LogService logService;
    @Transactional
    public void transfer(String from, String to, int amount) {
        // 扣减转出账户
        accountDao.debit(from, amount);
        // 记录转账日志(不同传播行为)
        logService.recordLog(from, to, amount);
        // 模拟异常:比如对转入账户余额不足
        if (amount > accountDao.getBalance(to)) {
            throw new RuntimeException("余额不足");
        }
        // 增加转入账户
        accountDao.credit(to, amount);
    }
}
@Service
public class LogService {
    @Autowired
    private LogDao logDao;
    // 情况A:使用REQUIRED(默认)
    @Transactional(propagation = Propagation.REQUIRED)
    public void recordLog(String from, String to, int amount) {
        logDao.insert(from + "->" + to + ":" + amount);
    }
    // 情况B:使用REQUIRES_NEW
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void recordLog(String from, String to, int amount) {
        logDao.insert(from + "->" + to + ":" + amount);
    }
}

运行结果对比

场景 转账方法异常时 日志是否回滚 最终数据状态
REQUIRED 抛出异常 回滚 转账未完成,日志不存在
REQUIRES_NEW 抛出异常 不回滚 转账未完成,日志已保存

关键区分点:REQUIRED内外事务绑定在一起,外层回滚内层也会回滚;REQUIRES_NEW内层事务独立提交。

案例2:REQUIRED vs NESTED

@Service
public class OrderService {
    @Transactional
    public void createOrder(Order order) {
        // 保存订单主信息
        orderDao.insert(order);
        // 情况A:NESTED传播
        this.saveOrderItems(order.getItems());
        // 模拟异常
        if (order.getTotal() > 10000) {
            throw new RuntimeException("订单金额超限");
        }
    }
    @Transactional(propagation = Propagation.NESTED)
    public void saveOrderItems(List<Item> items) {
        for (Item item : items) {
            itemDao.insert(item);
        }
    }
}

运行结果对比

场景 异常点 订单主表 订单明细表
所有方法REQUIRED 金额超限异常 回滚 回滚
子方法NESTED 金额超限异常 回滚 回滚
子方法NESTED 子方法内部异常 回滚 部分回滚(仅回滚子事务)

关键区分点

  • NESTED允许子事务部分回滚,而REQUIRED不能
  • 如果父事务最终回滚,NESTED子事务也会回滚(与REQUIRES_NEW不同)

案例3:调用链中的多种传播组合

@Service
public class PaymentService {
    // 主事务
    @Transactional(propagation = Propagation.REQUIRED)
    public void processPayment(Payment payment) {
        paymentDao.insert(payment);
        // 发送通知(REQUIRES_NEW)
        notificationService.sendNotification(payment);
        // 记录审计日志(NESTED)
        auditService.recordAudit(payment);
        // 假设这里抛出异常
        throw new RuntimeException("处理失败");
    }
}
@Service
public class NotificationService {
    // 独立事务,不受外部影响
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void sendNotification(Payment payment) {
        notificationDao.insert(payment.getNotification());
    }
}
@Service
public class AuditService {
    // 嵌套事务
    @Transactional(propagation = Propagation.NESTED)
    public void recordAudit(Payment payment) {
        auditLogDao.insert("Payment: " + payment.getId());
    }
}

运行结果

  • 支付表:回滚
  • 通知表:已提交(REQUIRES_NEW不受影响)
  • 审计表:回滚(NESTED跟随外部事务回滚)

如何测试和验证

写一个单元测试来直观验证:

@RunWith(SpringRunner.class)
@SpringBootTest
public class TransactionPropagationTest {
    @Autowired
    private BankService bankService;
    @Test
    public void testRequiredPropagation() {
        try {
            bankService.transfer("A", "B", 1000);
        } catch (Exception e) {
            // 预期异常
        }
        // 验证:
        // 1. 账户A余额不变 = REQUIRED时日志也回滚
        // 2. 账户B余额不变 = 转账失败
        // 3. 日志表中无记录 = 事务回滚
        assertNull(logService.findLog("A->B:1000"));
    }
    @Test
    public void testRequiresNewPropagation() {
        try {
            bankService.transfer("A", "B", 1000);
        } catch (Exception e) {
            // 预期异常
        }
        // 验证:
        // 1. 账户A余额不变 = 转账失败
        // 2. 日志表中依然有记录 = 独立事务已提交
        assertNotNull(logService.findLog("A->B:1000"));
    }
}

一个真实场景的判断技巧

当你需要判断使用哪种传播行为时,问自己这几个问题:

问题 对应传播行为
内层方法失败,外层是否继续? NESTED
内层方法失败,外层是否回滚? REQUIRED(内层抛异常会传播)
内层事务提交,是否想独立于外层? REQUIRES_NEW
内层方法需要感知外层事务的回滚? REQUIRED
内层方法不想被外层干扰? NEVER 或 NOT_SUPPORTED

一句话总结

  • 内外事务生死与共REQUIRED
  • 内务独善其身REQUIRES_NEW
  • 内务可部分回滚NESTED

抱歉,评论功能暂时关闭!