yudaocode/SpringBoot-Labs · error · java.lang.Exception
余额不足
Error message
余额不足
What it means
Thrown by the account service of labx-17's Seata AT + Dubbo variant. reduceBalance executes a guarded UPDATE that decrements only when balance suffices; a 0 affected-row count means the WHERE guard rejected the deduction and the service throws Exception('余额不足') at line 33. Within the Seata global transaction this exception propagates over Dubbo to the order service and triggers a global rollback.
Source
Thrown at labx-17/labx-17-sca-seata-at-dubbo-demo/labx-17-sca-seata-at-dubbo-demo-account-service/src/main/java/cn/iocoder/springcloudalibaba/labx17/accountservice/service/AccountServiceImpl.java:33
@Autowired
private AccountDao accountDao;
@Override
@Transactional // 开启新事物
public void reduceBalance(Long userId, Integer price) throws Exception {
logger.info("[reduceBalance] 当前 XID: {}", RootContext.getXID());
// 检查余额
checkBalance(userId, price);
logger.info("[reduceBalance] 开始扣减用户 {} 余额", userId);
// 扣除余额
int updateCount = accountDao.reduceBalance(price);
// 扣除成功
if (updateCount == 0) {
logger.warn("[reduceBalance] 扣除用户 {} 余额失败", userId);
throw new Exception("余额不足");
}
logger.info("[reduceBalance] 扣除用户 {} 余额成功", userId);
}
private void checkBalance(Long userId, Integer price) throws Exception {
logger.info("[checkBalance] 检查用户 {} 余额", userId);
Integer balance = accountDao.getBalance(userId);
if (balance < price) {
logger.warn("[checkBalance] 用户 {} 余额不足,当前余额:{}", userId, balance);
throw new Exception("余额不足");
}
}
}
View on GitHub (pinned to 6c12efaed0)
Solutions
- Top up / reset the account balance for the test user.
- Retry with a lower order amount.
- Catch this at the order service and convert to a user-facing business error, keeping Seata's rollback semantics intact.
Example fix
// before
int updateCount = accountDao.reduceBalance(price);
if (updateCount == 0) {
throw new Exception("余额不足");
}
// after — guarded SQL keyed by user, domain exception
int updateCount = accountDao.reduceBalance(userId, price);
if (updateCount == 0) {
throw new InsufficientBalanceException(userId, price);
} Defensive patterns
Strategy: validation
Validate before calling
// Dubbo generic/pre-check before ordering
Integer balance = accountService.getBalance(userId); // if exposed
if (balance == null || balance < orderAmount) {
return Result.error("余额不足");
} Try / catch
try {
orderService.create(userId, productId, amount);
} catch (Exception e) {
if (e.getMessage() != null && e.getMessage().contains("余额不足")) {
return ResponseEntity.badRequest().body("insufficient balance");
}
throw e; // Seata TM handles rollback
} Prevention
- Reseed the account balance before demo runs.
- Pre-check balance over the Dubbo reference where a read API exists.
- Keep the guarded UPDATE; use the exception as the business signal, not as a crash.
When it happens
Trigger: Order price exceeds the user's remaining balance at UPDATE time — either the pre-check raced with a concurrent deduction, or the balance is simply below the price and the pre-check path (line 43) was bypassed/reordered.
Common situations: Demo account data not reseeded after earlier runs; parallel requests draining the same account; price calculation differences between order and account services.
Related errors
AI-assisted analysis of yudaocode/SpringBoot-Labs@6c12efaed0 (2026-08-14).
Data as JSON: /api/errors/373d7a084e79ee5e.
Report an issue: GitHub.