{"record":{"id":"40082aff6c113d66","repo":"yudaocode/SpringBoot-Labs","slug":"error-40082a","errorCode":null,"errorMessage":"库存不足","messagePattern":"库存不足","errorType":"http","errorClass":"java.lang.Exception","httpStatus":500,"severity":"error","filePath":"lab-53/lab-53-seata-at-dubbo-demo/lab-53-seata-at-dubbo-demo-product-service/src/main/java/cn/iocoder/springboot/lab53/productservice/service/ProductServiceImpl.java","lineNumber":33,"sourceCode":"\n    @Autowired\n    private ProductDao productDao;\n\n    @Override\n    @Transactional // 开启新事物\n    public void reduceStock(Long productId, Integer amount) throws Exception {\n        logger.info(\"[reduceStock] 当前 XID: {}\", RootContext.getXID());\n\n        // 检查库存\n        checkStock(productId, amount);\n\n        logger.info(\"[reduceStock] 开始扣减 {} 库存\", productId);\n        // 扣减库存\n        int updateCount = productDao.reduceStock(productId, amount);\n        // 扣除成功\n        if (updateCount == 0) {\n            logger.warn(\"[reduceStock] 扣除 {} 库存失败\", productId);\n            throw new Exception(\"库存不足\");\n        }\n        // 扣除失败\n        logger.info(\"[reduceStock] 扣除 {} 库存成功\", productId);\n    }\n\n    private void checkStock(Long productId, Integer requiredAmount) throws Exception {\n        logger.info(\"[checkStock] 检查 {} 库存\", productId);\n        Integer stock = productDao.getStock(productId);\n        if (stock < requiredAmount) {\n            logger.warn(\"[checkStock] {} 库存不足，当前库存: {}\", productId, stock);\n            throw new Exception(\"库存不足\");\n        }\n    }\n\n}\n","sourceCodeStart":15,"sourceCodeEnd":49,"githubUrl":"https://github.com/yudaocode/SpringBoot-Labs/blob/6c12efaed06d12907a0f40dd2ad1f7020aec8798/lab-53/lab-53-seata-at-dubbo-demo/lab-53-seata-at-dubbo-demo-product-service/src/main/java/cn/iocoder/springboot/lab53/productservice/service/ProductServiceImpl.java#L15-L49","documentation":"Thrown by the product (stock) service in the Seata AT demo (lab-53). reduceStock issues a conditional UPDATE (typically `UPDATE product SET stock = stock - ? WHERE id = ? AND stock >= ?`); a return of 0 affected rows means the guard failed, and the service throws to signal insufficient stock. As a Seata branch exception it causes the global transaction (order + account + product) to roll back.","triggerScenarios":"Placing an order whose amount exceeds the product's remaining stock such that the conditional UPDATE matches 0 rows (e.g. stock exhausted between the pre-check and the update, or the pre-check itself was skipped/bypassed). Line 33 is the `throw new Exception(\"库存不足\")` after `updateCount == 0`.","commonSituations":"Previous demo runs consumed the seeded stock without reseeding the product table; concurrent purchases of the last units race past the checkStock pre-check; the amount parameter is accidentally large (e.g. passing a price instead of a quantity).","solutions":["Reset the product stock in the DB (re-run the lab's SQL init script or `UPDATE product SET stock = 1000 WHERE id = ?`).","Retry the order with a smaller amount.","Keep the conditional UPDATE as the authoritative guard and map this exception to a user-facing 'out of stock' message at the order-service boundary."],"exampleFix":"// before\nint updateCount = productDao.reduceStock(productId, amount);\nif (updateCount == 0) {\n    throw new Exception(\"库存不足\");\n}\n\n// after — domain exception; SQL guard remains the source of truth\nint updateCount = productDao.reduceStock(productId, amount);\nif (updateCount == 0) {\n    throw new OutOfStockException(productId, amount);\n}","handlingStrategy":"validation","validationCode":"// Check stock before creating the order\nInteger stock = productApi.getStock(productId);\nif (stock == null || stock < amount) {\n    return Result.error(\"库存不足，当前库存：\" + stock);\n}","typeGuard":null,"tryCatchPattern":"try {\n    orderService.create(userId, productId, amount);\n} catch (Exception e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"库存不足\")) {\n        return ResponseEntity.badRequest().body(\"out of stock\");\n    }\n    throw e;\n}","preventionTips":["Restock demo products before test runs.","Keep amount ≤ single-item quantity sanity-checked at the controller.","Rely on the guarded UPDATE, not the pre-check, for correctness under concurrency."],"tags":["seata","dubbo","distributed-transaction","business-rule","sql-update"],"backgroundTag":null,"analyzedSha":"6c12efaed06d12907a0f40dd2ad1f7020aec8798","analyzedAt":"2026-08-14T13:06:31.500Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}