alibaba/spring-cloud-alibaba · error · BusinessException
deduct stock failed
Error message
deduct stock failed
What it means
Thrown inside StorageServiceImpl.reduceStock (@Transactional Seata branch) when storageMapper.reduceStock(...) returns 0 affected rows. The UPDATE is `UPDATE storage SET count = count - #{count}, update_time=#{updateTime} WHERE commodity_code = #{commodityCode}` — note it has NO count-guard, so a 0-row result means only one thing: no storage row matches the commodityCode. (Unlike the account UPDATE, stock can go negative here; this exception is not about insufficient stock, that is checked separately in checkStock.)
Source
Thrown at spring-cloud-alibaba-examples/integrated-example/integrated-storage/src/main/java/com/alibaba/cloud/integration/storage/service/impl/StorageServiceImpl.java:55
public class StorageServiceImpl implements StorageService {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private StorageMapper storageMapper;
@Override
@Transactional
public void reduceStock(String commodityCode, Integer count)
throws BusinessException {
logger.info("[reduceStock] current XID: {}", RootContext.getXID());
checkStock(commodityCode, count);
Timestamp updateTime = new Timestamp(System.currentTimeMillis());
int updateCount = storageMapper.reduceStock(commodityCode, count, updateTime);
if (updateCount == 0) {
throw new BusinessException("deduct stock failed");
}
}
@Override
public Result<?> getRemainCount(String commodityCode) {
Integer stock = storageMapper.getStock(commodityCode);
if (stock == null) {
return Result.failed("commodityCode wrong,please check commodity code");
}
return Result.success(stock);
}
private void checkStock(String commodityCode, Integer count)
throws BusinessException {
Integer stock = storageMapper.getStock(commodityCode);
if (stock < count) {
throw new BusinessException("no enough stock");
}View on GitHub (pinned to 115d590110)
Solutions
- Confirm the row exists: SELECT count FROM storage WHERE commodity_code = '<commodityCode>'.
- Seed the missing commodity: INSERT INTO storage (commodity_code, count) VALUES ('<commodityCode>', <units>).
- Trim/normalize the commodityCode at the controller boundary and verify it matches the stored value exactly.
Defensive patterns
Strategy: validation
Validate before calling
// Before reduceStock, confirm the commodity row exists (the UPDATE has no count guard).
Integer stock = storageMapper.getStock(commodityCode);
if (stock == null) {
// commodity missing -> avoid a 0-row update that throws 'deduct stock failed'
throw new BusinessException("commodity not found: " + commodityCode);
} Try / catch
try {
storageService.reduceStock(commodityCode, count);
} catch (BusinessException e) {
if ("deduct stock failed".equals(e.getMessage())) {
// no matching commodityCode row; fix the data/seed
}
throw e;
} Prevention
- Ensure every orderable commodityCode has a seeded storage row.
- Normalize commodityCode (trim) at the controller boundary.
- Remember the storage UPDATE matches on commodityCode only — there is no count guard.
When it happens
Trigger: reduceStock(commodityCode, count) invoked for a commodityCode that does not exist in the storage table; the row was deleted between checkStock and the UPDATE; the commodityCode string does not match (whitespace/case mismatch).
Common situations: Order requests with a commodityCode not present in the seeded storage data; storage table not initialized (DDL/DML not run); a typo or trailing whitespace in the commodityCode.
Related errors
AI-assisted analysis of alibaba/spring-cloud-alibaba@115d590110 (2026-08-14).
Data as JSON: /api/errors/be9130a414c0bf5e.
Report an issue: GitHub.