当前位置: 首页 > 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.jsqmd.com/news/854166/

相关文章:

  • 2026年生成式引擎优化服务市场核心机构能力评估及3家头部服务商深度解析 - 产业观察网
  • 3分钟搞定Axure中文界面:告别英文困扰的终极汉化指南
  • SAP ABAP实战:手把手教你调用CKM3函数ZFI003_GET_CKM3_DATA获取成本数据
  • Visio画流程图时,大括号到底藏哪儿了?分享两个我常用的快速插入方法(附详细步骤图)
  • 基于Jeecgboot3.9.0的flowable7.2.0流程串行多实例加签功能的实现
  • 论文AI率从80%降到10%,2026年5月4款降AI软件实测 - 我要发一区
  • 保姆级教程:用Python复现双能X射线安检机的图像预处理与伪彩色效果
  • 调理品腌料生产厂家如何破局?深度解析4C定制赋能方法论 - 资讯速览
  • 从AlphaFold到日常:用AI工具预测蛋白质结构,5分钟看懂三级四级
  • SKP格式看图不用愁,一站式随时随地查看
  • 题解:洛谷 P1144 最短路计数
  • 从PointPillars到BEV空间:手把手拆解BEVFusion中的点云特征提取与转换全流程
  • 别等618当天!京东淘宝618抢先购今晚开抢!淘宝抢先购才是底价,口令红包 + 国补薅到爽保姆级攻略带你无脑抄底 - 资讯速览
  • 别再手动配密码了!用Authelia CLI工具一键生成Argon2id加密密码(附Docker部署避坑点)
  • BepInEx完整指南:5分钟掌握Unity游戏模组开发框架
  • 别再只会用tail -f了!用journalctl实时追踪服务日志的5个高效姿势(附systemd服务排查实战)
  • 中年运维转型实录,三十岁毅然投身网安,坚持过后皆是顺遂前程
  • 华为交换机VRRP配置实战:一个真实企业网故障排查与优化案例
  • 2026年降AI软件天梯榜,4款主流工具技术路线深度对比 - 我要发一区
  • 智慧工业轮胎X光图像金属与结构缺陷检测数据集VOC+YOLO格式896张11类别
  • 灭蚊器哪种牌子好?什么牌灭蚊灯性价比高又好用?详细测评家用灭蚊灯品牌十大排行榜最新
  • Swift Extension UIImage扩展支持加载GIF动画
  • 论文降AI率工具排行榜,2026年5月精选4款知网降AI软件 - 我要发一区
  • 保姆级教程:用5W规则搞定高速差分对布线,告别信号串扰
  • STM32CubeMX零基础实战:5分钟搞定HC-SR505人体感应模块,让你的设备学会“看人下菜碟”
  • STM32F7移植USB-CDC
  • uni-card组件进阶玩法:从基础展示到带交互的‘动态卡片’实战
  • 创业公司如何借助 Taotoken 快速试错不同大模型以确定产品原型方向
  • Python 浅拷贝与深拷贝:为什么我改了 b,a 也跟着变了?
  • AMD Ryzen处理器深度调试终极指南:从核心超频到硬件优化