catch块仅负责记录待补偿任务并移交,真正的补偿是独立、幂等、可追溯的异步动作;需先查状态、用乐观锁更新,按异常类型决定是否启动补偿。

catch块本身不负责补偿,而是触发补偿的起点。真正的补偿必须是独立、幂等、可追溯的异步动作,不能依赖try-catch的同步执行流。
补偿不是在catch里“undo操作”
常见误区是:在catch中直接写“库存加回”“订单取消”这类反向逻辑。这不可靠,因为:
- 原操作可能已部分成功(如消息已发、日志已记、第三方接口已调用),无法精确回退
- catch执行时上下文可能已丢失(如事务已提交、线程已切换)
- 若补偿代码再抛异常,会覆盖原始错误,掩盖根因
正确的补偿入口:记录+移交
catch中唯一该做的事,是可靠地记录待补偿任务,并交由专用机制驱动:
- 将关键信息(bizId、操作类型、原始参数、当前时间、重试次数)插入独立补偿表,使用本地事务保证记录与主业务原子性
- 通过消息队列(如RocketMQ事务消息)或定时扫描触发后续补偿,避免阻塞主流程
- 绝不在此处调用远程服务或执行耗时逻辑——只做轻量落库或发消息
补偿逻辑自身必须幂等
后续执行的补偿方法(如compensateOrderClose(bizId))需满足:
- 先查状态:确认订单是否真处于“待关闭但未关”状态,已是CLOSED则直接返回
- 用乐观锁或版本号更新:update order set status='CLOSED' where id=? and status='PROCESSING'
- 失败时记录失败原因并增加重试计数,超限后告警人工介入
区分异常类型决定是否启动补偿
不是所有异常都该补偿:
- 可重试异常(如SocketTimeoutException):优先走重试模板,失败后再进补偿队列
- 业务拒绝异常(如InsufficientBalanceException):属正常业务流,无需补偿,直接返回用户
- 系统级异常(如SQLException因连接池耗尽):需补偿,同时触发熔断或降级
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











