当前位置: 首页 > news >正文

下单扣库存,要把事务边界放在哪里

在很多后端项目里,“下单扣库存”几乎是最常见的场景之一。它看起来简单,但只要一进入并发、异常、重试,这个小流程就会立刻暴露出事务边界、库存一致性、幂等性这些核心问题。我们这个项目正好用一个很小的下单模型,把这些点串了起来。

核心流程其实就两步:先扣库存,再落订单。项目里 OrderApplicationService.placeOrder() 把这个流程放在一个本地事务里:

@Transactional public CreateOrderResponse placeOrder(CreateOrderRequest request) { inventoryService.reserve(request.skuCode(), request.quantity()); long orderId = orderRepository.save( request.orderNo(), request.skuCode(), request.quantity(), calculateAmount(request.quantity()), OrderStatus.CREATED.name() ); return new CreateOrderResponse(orderId, request.orderNo(), OrderStatus.CREATED.name(), ...); }

这里最重要的不是“代码看起来整齐”,而是它表达了一个明确的业务承诺:要么库存和订单一起成功,要么一起失败。如果库存扣了,订单没落进去,系统状态就会不一致;如果订单落了,库存没扣成功,同样会出错。所以事务边界不能随便放,通常要包住“业务原子性”最强的那一层,也就是应用服务层。

库存扣减本身也不是简单的“先查再减”。项目里 InventoryService.reserve() 只关心结果,不关心过程:

public void reserve(String skuCode, int quantity) { int updatedRows = inventoryRepository.deductAvailableStock(skuCode, quantity); if (updatedRows == 0) { throw new IllegalStateException("Stock is not enough"); } }

真正的关键在仓储层的条件更新:

update inventory_item set available = available - ? where sku_code = ? and available >= ?

这行 SQL 的价值在于,它把“判断库存够不够”和“扣减库存”合成了一次原子操作。这样就避免了经典的先查后改竞态:两个请求如果都先查到库存充足,再分别去扣,就可能超卖。条件更新的方式,能把并发窗口压到最小,数据库直接帮你做一致性判断。

这类写法特别适合解释成一句话:不是先判断再修改,而是把判断写进更新条件里,让数据库替你守住并发边界。

不过事务不是只会“包起来”就完事,真正容易踩坑的是异常和代理机制。项目里的 TransactionInterviewService 很适合拿来讲这两个坑。

第一个坑是:RuntimeException 默认回滚,checked exception 默认不回滚。代码里有两组对比:

@Transactional public void createAndFailWithRuntimeException(String orderNo) { insertOrder(orderNo); throw new IllegalStateException("Runtime exception should trigger rollback"); }
@Transactional public void createAndFailWithCheckedException(String orderNo) throws Exception { insertOrder(orderNo); throw new Exception("Checked exception does not rollback by default"); }

这两个方法非常适合用来讲“事务为什么没有生效”。很多人以为只要加了 @Transactional 就一定会回滚,其实不是。Spring 默认只对运行时异常触发回滚,如果是受检异常,要么显式配置 rollbackFor = Exception.class,要么自己把异常体系设计好。

第二个坑是 self-invocation,也就是类内部自己调用自己的事务方法。项目里这个方法正好能演示:

public void callTransactionalMethodInternally(String orderNo) { try { this.internalTransactionalMethod(orderNo); } catch (IllegalStateException ignored) { // The test checks persisted rows to prove that self-invocation bypasses the proxy. } }

而被调用的方法上虽然也标了事务:

@Transactional public void internalTransactionalMethod(String orderNo) { insertOrder(orderNo); throw new IllegalStateException("Self invocation bypasses transactional proxy"); }

问题在于,Spring 事务依赖代理,如果你在同一个类里 this.xxx() 直接调用,代理层会被绕过,事务切面可能根本没机会介入。这个坑在真实项目里都很常见,属于“看起来加了注解,实际上没生效”。

如果把这套内容整理成文字表达:下单流程拆成应用服务、库存扣减和订单持久化三层。应用服务负责事务边界,库存层用条件更新防止超卖,事务演示里再补充运行时异常回滚、受检异常默认不回滚、以及 self-invocation 失效这几个关键点。

Checklist

  • 事务边界放在真正需要原子性的业务层
  • 库存扣减用条件更新,不要先查后改
  • 关键失败路径要明确抛出异常
  • 区分运行时异常和受检异常的回滚规则
  • 注意同类内部调用会绕过事务代理
http://www.gsyq.cn/news/1333280.html

相关文章:

  • 防爆型红外热成像仪:原理、应用与工业安全监测实践
  • SAP ABAP实战:手把手教你调用CKM3函数ZFI003_GET_CKM3_DATA获取成本数据
  • 调理品腌料生产厂家如何破局?深度解析4C定制赋能方法论 - 资讯速览
  • CentOS 7服务器部署:NFS共享、Nginx-RTMP流媒体与Qt无GUI环境全攻略
  • PCB工程师必看:别再搞错1078玻纤布的Dk了,手把手教你算等效介电常数
  • OpenDevin实践踩坑记:搞定HuggingFace镜像,让你的AI程序员顺利跑起来
  • 别再只会用tail -f了!用journalctl实时追踪服务日志的5个高效姿势(附systemd服务排查实战)
  • 探索AI视频创作新范式:从零到一构建你的智能视频工厂
  • OpCore Simplify:30分钟完成专业级Hackintosh配置的终极指南
  • 华为ENSP模拟器实战:手把手教你配置园区网防火墙双机热备(含心跳线、VRRP、BFD联动)
  • uni-card组件进阶玩法:从基础展示到带交互的‘动态卡片’实战
  • 从Wi-Fi 6到5G:深入浅出聊聊MIMO中的CSI反馈那些事儿(PMI/RI/CQI详解)
  • 嵌入式开发实战:基于RZ/G2L异构处理器与Linux的工业物联网平台深度体验
  • 实战解密:用unveilr深度解析小程序源码架构
  • 智慧工业控制面板工控部件元器件LCD部件检测数据集VOC+YOLO格式365张8类别
  • TI IWR6843ISK-ODS雷达固件开发环境搭建:从MATLAB Runtime到CCS的保姆级避坑指南
  • 不止于测试:用GStreamer打造你的树莓派低成本视频监控/图传系统
  • 收藏!小白程序员必看:如何抓住AI大模型时代红利?从入门到高薪就业全解析!
  • 保姆级教程:用Python复现双能X射线安检机的图像预处理与伪彩色效果
  • 别再手动移植了!用STM32CubeMX+Keil AC6一键搞定QP状态机(STM32F407ZGT6实测)
  • 从电磁铁到无线输电:手把手复现特斯拉线圈核心实验(含电路图与材料清单)
  • 收藏!大学生入局AI大模型应用开发,从0到1完整路线图
  • 应急预警为何总“差一口气“?
  • 开源鸿蒙与星闪融合:RK3506工业物联网边缘节点实战
  • 2026年南京除甲醛企业怎么挑?看准这3个关键点就够了 - 资讯速览
  • Whisky深度评测:如何在Apple Silicon Mac上构建Windows应用运行沙箱
  • 5分钟快速上手ParsecVDisplay:解锁Windows虚拟显示器终极指南
  • 2025届学术党必备的AI辅助写作方案实测分析
  • 深度测评5款主流降AIGC工具,送你免费降AI指令!
  • Taotoken的用量看板如何帮助开发者洞察模型调用模式