订单主表明细如何保存在同一事务

zjc 于 2026-08-20 发布

订单是最典型的主表加明细结构:只保存主表成功、明细失败,会出现一个没有商品的空订单;反过来则会出现孤儿明细。项目把这笔业务动作封装到事务方法里。

一、事务方法

@Transactional(rollbackFor = Exception.class)
public void saveWithDetails(Order order, List<OrderDetail> details) {
    save(order);
    details.forEach(detail -> {
        detail.setId(null);
        detail.setOrderId(order.getOrderId());
        detail.setOrderNo(order.getOrderNo());
    });
    orderDetailService.saveBatch(details);
}

https://github.com/springvortex/spring-cloud-alibaba/blob/main/service-provider/src/main/java/com/zjc/provider/service/impl/OrderServiceImpl.java

Controller 负责 DTO 转换和调用,不自己拆分两次数据库写入。

二、rollbackFor 不能省

Spring 默认只对运行时异常回滚。加上 rollbackFor = Exception.class 后,受检异常同样触发回滚。业务代码将来引入受检异常时,事务语义不会被悄悄改变。

三、先补齐关联字段

明细的主键 ID 会被清空,避免调用方传入旧 ID 造成覆盖或冲突;订单 ID 和订单号统一由服务端补齐,保证明细一定挂在这张新订单上。

四、失败时向上抛

保存失败抛出 BusinessException,由全局异常处理转换成统一响应。事务边界内不要吞异常,否则方法看起来失败,数据库却已经提交了一半。

五、经验总结

事务应该围绕业务用例,而不是围绕 Repository 方法。订单保存这个动作天然要求主表和明细一致,所以边界就放在 saveWithDetails