{"record":{"id":"2729fa7aaf0751ca","repo":"yudaocode/SpringBoot-Labs","slug":"error-2729fa","errorCode":null,"errorMessage":"库存不足","messagePattern":"库存不足","errorType":"http","errorClass":"java.lang.Exception","httpStatus":500,"severity":"error","filePath":"labx-17/labx-17-sc-seata-at-feign-demo/labx-17-sc-seata-at-feign-demo-product-service/src/main/java/cn/iocoder/springcloud/labx17/productservice/service/impl/ProductServiceImpl.java","lineNumber":34,"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":16,"sourceCodeEnd":50,"githubUrl":"https://github.com/yudaocode/SpringBoot-Labs/blob/6c12efaed06d12907a0f40dd2ad1f7020aec8798/labx-17/labx-17-sc-seata-at-feign-demo/labx-17-sc-seata-at-feign-demo-product-service/src/main/java/cn/iocoder/springcloud/labx17/productservice/service/impl/ProductServiceImpl.java#L16-L50","documentation":"Thrown by the product service of labx-17's Seata AT + Feign demo. reduceStock runs a conditional stock decrement; a 0-row update means insufficient stock and the code throws at line 34. The exception travels back through the Feign interface to the order service and rolls back the Seata global transaction, restoring the order and account branches.","triggerScenarios":"Order amount greater than remaining stock at UPDATE time — typically after the pre-check passed but stock changed concurrently, or when the seeded stock is simply too low for the requested amount.","commonSituations":"Repeated demo orders drained product.stock without reseeding; concurrent purchases of the final stock; amount parameter confusion (count vs total price) between services.","solutions":["Reset product stock via the lab's SQL or an UPDATE statement.","Retry with a smaller amount.","Treat this as the business outcome: return an 'out of stock' response to the caller instead of a raw exception."],"exampleFix":"// before\nint updateCount = productDao.reduceStock(productId, amount);\nif (updateCount == 0) {\n    throw new Exception(\"库存不足\");\n}\n\n// after — domain exception; the SQL guard stays authoritative\nint updateCount = productDao.reduceStock(productId, amount);\nif (updateCount == 0) {\n    throw new OutOfStockException(productId, amount);\n}","handlingStrategy":"validation","validationCode":"Integer stock = productFeign.getStock(productId);\nif (stock == null || stock < amount) {\n    return Result.error(\"库存不足\");\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":["Pre-check stock via a read endpoint before the transaction.","Restock demo data before load tests.","Keep the conditional stock UPDATE as the authoritative guard."],"tags":["seata","openfeign","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"}