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

  1. Confirm the row exists: SELECT count FROM storage WHERE commodity_code = '<commodityCode>'.
  2. Seed the missing commodity: INSERT INTO storage (commodity_code, count) VALUES ('<commodityCode>', <units>).
  3. 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

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.