yudaocode/SpringBoot-Labs · error · Exception

库存不足

Error message

库存不足

What it means

Hand-rolled business exception in the Seata AT product service (labx-17). ProductServiceImpl.reduceStock first calls checkStock (a read-only pre-check), then executes a conditional UPDATE (reduce stock only where stock >= amount); if productDao.reduceStock returns updateCount == 0 the deduction did not happen and the method throws Exception("库存不足") (insufficient stock). The throw propagates up through Dubbo inside the Seata global transaction and triggers rollback of the order and account deductions. The log line '[reduceStock] 扣除 {} 库存失败' right before the throw distinguishes this branch (conditional UPDATE hit 0 rows) from the pre-check branch in checkStock.

Source

Thrown at labx-17/labx-17-sca-seata-at-dubbo-demo/labx-17-sca-seata-at-dubbo-demo-product-service/src/main/java/cn/iocoder/springcloudalibaba/labx17/productservice/service/ProductServiceImpl.java:33

    @Autowired
    private ProductDao productDao;

    @Override
    @Transactional // 开启新事物
    public void reduceStock(Long productId, Integer amount) throws Exception {
        logger.info("[reduceStock] 当前 XID: {}", RootContext.getXID());

        // 检查库存
        checkStock(productId, amount);

        logger.info("[reduceStock] 开始扣减 {} 库存", productId);
        // 扣减库存
        int updateCount = productDao.reduceStock(productId, amount);
        // 扣除成功
        if (updateCount == 0) {
            logger.warn("[reduceStock] 扣除 {} 库存失败", productId);
            throw new Exception("库存不足");
        }
        // 扣除失败
        logger.info("[reduceStock] 扣除 {} 库存成功", productId);
    }

    private void checkStock(Long productId, Integer requiredAmount) throws Exception {
        logger.info("[checkStock] 检查 {} 库存", productId);
        Integer stock = productDao.getStock(productId);
        if (stock < requiredAmount) {
            logger.warn("[checkStock] {} 库存不足,当前库存: {}", productId, stock);
            throw new Exception("库存不足");
        }
    }

}

View on GitHub (pinned to 6c12efaed0)

Solutions

  1. Check the product table: SELECT id, stock FROM product WHERE id = ? — confirm the row exists and stock >= the amount you are requesting; re-import the labx-17 SQL seed or reset stock.
  2. Look for the preceding log '[checkStock] {} 库存不足,当前库存: {}' — if present, the pre-check already rejected it and the value printed is the actual stock; if absent but '[reduceStock] 扣除 {} 库存失败' appears, stock changed concurrently or the row is missing.
  3. Reduce the order amount (the demo passes amount through the order-service REST call) or restock the row, then retry.
  4. Introduce a typed InsufficientStockException instead of generic Exception so the caller can render a 4xx response while Seata's rollback-on-exception still reverts the account deduction.

Example fix

// before
int updateCount = productDao.reduceStock(productId, amount);
if (updateCount == 0) {
    logger.warn("[reduceStock] 扣除 {} 库存失败", productId);
    throw new Exception("库存不足");
}

// after — typed exception, message carries actual stock for diagnosis
Integer stock = productDao.getStock(productId);
logger.warn("[reduceStock] 扣减 {} 库存失败, 当前库存: {}", productId, stock);
throw new InsufficientStockException(productId, stock);
Defensive patterns

Strategy: validation

Validate before calling

// before placing the order, ask the product service whether it can fulfill it
Integer stock = productDao.getStock(productId);
if (stock == null || stock < amount) {
    return Result.error(400, "库存不足, 当前库存: " + stock);
}

Try / catch

try {
    productService.reduceStock(productId, amount);
} catch (Exception e) {
    if ("库存不足".equals(e.getMessage())) {
        return Result.error(409, "库存不足,请减少购买数量"); // Seata rolls back account deduction
    }
    throw e;
}

Prevention

When it happens

Trigger: Ordering more units of a productId than exist: productDao.reduceStock(productId, amount) executes UPDATE ... SET stock = stock - #{amount} WHERE id = #{id} AND stock >= #{amount}; when stock < amount (or the productId row does not exist) the WHERE matches nothing, updateCount == 0 at ProductServiceImpl.java:31-34, and the exception is thrown. Typical trigger: the demo's seed data has stock=10 and you request amount > 10 via the order-service HTTP endpoint.

Common situations: Fresh database without the product seed rows imported (every productId yields updateCount == 0); repeated demo runs exhausting the seeded stock; concurrent orders for the last units racing between checkStock's SELECT and the UPDATE; passing the wrong productId (e.g. using the order table id instead of product id).

Related errors


AI-assisted analysis of yudaocode/SpringBoot-Labs@6c12efaed0 (2026-08-14). Data as JSON: /api/errors/7970bbd4e27944b3. Report an issue: GitHub.