{"record":{"id":"373d7a084e79ee5e","repo":"yudaocode/SpringBoot-Labs","slug":"error-373d7a","errorCode":null,"errorMessage":"余额不足","messagePattern":"余额不足","errorType":"http","errorClass":"java.lang.Exception","httpStatus":500,"severity":"error","filePath":"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","lineNumber":33,"sourceCode":"\n    @Autowired\n    private AccountDao accountDao;\n\n    @Override\n    @Transactional // 开启新事物\n    public void reduceBalance(Long userId, Integer price) throws Exception {\n        logger.info(\"[reduceBalance] 当前 XID: {}\", RootContext.getXID());\n\n        // 检查余额\n        checkBalance(userId, price);\n\n        logger.info(\"[reduceBalance] 开始扣减用户 {} 余额\", userId);\n        // 扣除余额\n        int updateCount = accountDao.reduceBalance(price);\n        // 扣除成功\n        if (updateCount == 0) {\n            logger.warn(\"[reduceBalance] 扣除用户 {} 余额失败\", userId);\n            throw new Exception(\"余额不足\");\n        }\n        logger.info(\"[reduceBalance] 扣除用户 {} 余额成功\", userId);\n    }\n\n    private void checkBalance(Long userId, Integer price) throws Exception {\n        logger.info(\"[checkBalance] 检查用户 {} 余额\", userId);\n        Integer balance = accountDao.getBalance(userId);\n        if (balance < price) {\n            logger.warn(\"[checkBalance] 用户 {} 余额不足，当前余额:{}\", userId, balance);\n            throw new Exception(\"余额不足\");\n        }\n    }\n\n}\n","sourceCodeStart":15,"sourceCodeEnd":48,"githubUrl":"https://github.com/yudaocode/SpringBoot-Labs/blob/6c12efaed06d12907a0f40dd2ad1f7020aec8798/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#L15-L48","documentation":"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.","triggerScenarios":"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.","commonSituations":"Demo account data not reseeded after earlier runs; parallel requests draining the same account; price calculation differences between order and account services.","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."],"exampleFix":"// before\nint updateCount = accountDao.reduceBalance(price);\nif (updateCount == 0) {\n    throw new Exception(\"余额不足\");\n}\n\n// after — guarded SQL keyed by user, domain exception\nint updateCount = accountDao.reduceBalance(userId, price);\nif (updateCount == 0) {\n    throw new InsufficientBalanceException(userId, price);\n}","handlingStrategy":"validation","validationCode":"// Dubbo generic/pre-check before ordering\nInteger balance = accountService.getBalance(userId); // if exposed\nif (balance == null || balance < orderAmount) {\n    return Result.error(\"余额不足\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    orderService.create(userId, productId, amount);\n} catch (Exception e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"余额不足\")) {\n        return ResponseEntity.badRequest().body(\"insufficient balance\");\n    }\n    throw e; // Seata TM handles rollback\n}","preventionTips":["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."],"tags":["seata","dubbo","distributed-transaction","business-rule","sql-update"],"backgroundTag":null,"analyzedSha":"6c12efaed06d12907a0f40dd2ad1f7020aec8798","analyzedAt":"2026-08-14T13:06:31.500Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}