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
- 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.
- 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.
- Reduce the order amount (the demo passes amount through the order-service REST call) or restock the row, then retry.
- 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
- Pre-check stock and communicate remaining quantity in the UI so users do not submit doomed orders.
- Keep the conditional UPDATE (AND stock >= amount in WHERE) so concurrent oversell is impossible even without the pre-check.
- Use a typed InsufficientStockException carrying productId and current stock for actionable errors.
- Reset demo seed stock between lab runs so the exception does not fire on the first request.
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.