Java中try-catch不执行补偿而只可靠触发补偿任务,应将补偿信息写入独立任务表或发事务消息,按异常类型区分是否补偿,补偿动作须幂等并前置校验,finally用于状态对齐而非抛异常。

Java 中的 try-catch 本身不执行补偿,而是确保补偿动作被可靠触发——它把异常“接住”,不让流程中断失控,再把补偿任务交出去。
捕获后只做轻量移交,不直接执行逆操作
在 catch 块里不要写“加回库存”“取消订单”这类反向逻辑。原因很实际:原操作可能已部分生效(比如消息发了、日志写了、第三方接口调了),上下文也早不在了,硬 undo 容易出错甚至掩盖真实问题。
- 正确做法是把 bizId、操作类型、原始参数、当前时间、重试次数等关键信息,插入一张独立的补偿任务表
- 这条插入必须和主业务共用同一个本地事务,保证“主成功则补偿记录必落库”,原子性由数据库事务兜底
- 也可以发一条事务消息(如 RocketMQ 半消息),由 MQ 的回查机制保障最终投递
按异常类型决定是否启动补偿
不是所有异常都该补偿。盲目补偿反而破坏一致性。
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
- 业务异常(如 InsufficientBalanceException、ParamInvalidException):属于预期内拒绝,Try 阶段没真正锁定资源,直接返回用户,不进补偿队列
- 系统异常(如 SocketTimeoutException、SQLException 因连接池耗尽):非预期中断,Try 可能已部分成功,必须触发补偿
- 流程异常(如状态机非法跳转、重复请求):应拦截审计,禁止走补偿,避免污染补偿链路
补偿动作自身必须幂等且带前置校验
补偿不是无脑执行,而是一次有依据的修复。
立即学习“Java免费学习笔记(深入)”;
- 每个补偿方法开头先查状态表,确认资源确实处于“已锁定未释放”状态。例如:查 inventory_lock 表中 orderId+skuId 是否存在且 status = 'LOCKED'
- 更新时用乐观锁:update order set status='CANCELLED' where id=? and status='PROCESSING',失败就记录原因并累加重试计数
- 补偿成功后立即写幂等标记(如 compensations_log 表),防止重复执行
finally 是状态对齐的最后防线
catch 负责移交,finally 负责兜底校验。它不是用来抛新异常的地方,而是强制检查关键状态是否一致。
- 在 finally 中检查 DB 记录是否存在、缓存是否已失效、MQ 消息是否已发出
- 若发现不一致(如 DB 写入成功但 Redis 缓存更新失败),可启动异步修复线程,或落库一条待调度任务
- 切记:finally 里不要 throw 异常,会覆盖原始错误;用日志 + 告警代替

















