news 2026/5/23 7:35:12

JAVA审计日志表被外层事务回滚问题分析与修复

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JAVA审计日志表被外层事务回滚问题分析与修复

文章目录

  • 审计日志被外层事务回滚问题分析与修复
    • 一、问题现象
    • 二、定位过程与误区
    • 三、根本原因
      • 3.1 事务边界与 @Async 的关系
      • 3.2 失败分支为何丢日志
      • 3.3 为什么 catch 看不到异常
    • 四、修复方案
      • 4.1 修复后的事务时序
      • 4.2 为什么不去修 @Async
    • 五、原理小结:Spring 事务传播行为

审计日志被外层事务回滚问题分析与修复

一、问题现象

在调用快递100下单接口失败时,本应记录到trade_delivery_kd100_api_log表的失败日志没有落库;而调用成功时同一段日志记录代码却能正常落库。

失败分支构造的日志对象示例:

DeliveryKd100ApiLogDO(orderId=019d22f3-1dd2-7c61-8508-d15a2bf27958,apiType=B_ORDER_OFFICIAL,requestResult=FAIL,responseBody=null,errorMessage=接口异常:[404NotFound]during[POST]to[...],executeTime=165)

调用代码:

apiLogMapper.insert(logDO);

代码没有报错(catch 被吞了 log),但数据库里查不到这条记录。

二、定位过程与误区

最初怀疑过两个方向,都被证据排除:

  1. 怀疑租户上下文丢失@Async切线程后TenantContextHolder取不到tenantId,导致多租户拦截器拒绝插入。

    • 反例:成功分支走的是同一个方法、同一种线程模型,如果上下文丢失,成功也应该插不进去。所以不是。
  2. 怀疑字段超长errorMessage中包含 Tomcat 整页 HTML(977 字节),可能超过VARCHAR(n)限制。

    • 查 DDL:error_messageTEXT,长度不是问题。

继续聚焦"成功能落、失败落不进"这唯一差异点,发现失败分支调用logApiCall之后还做了一件事:

}else{apiLogService.logApiCall(orderId,...,false);throwexception(newErrorCode(1_011_008_015,response.getMessage()));}

throw exception(...)抛了运行时异常,而外层deliveryOrder()方法上挂着@Transactional——回滚发生了。这才是真正的根因。

三、根本原因

3.1 事务边界与 @Async 的关系

@Async标注的方法在 Spring 默认情况下会被代理到TaskExecutor上的另一个线程,新线程默认开启自己的事务上下文,不会跟外层共享事务。

但前提是@Async真正生效。任何一个原因都会让@Async静默退化为同步调用:

  • 主启动类(或任一@Configuration)没有加@EnableAsync
  • 方法不是public
  • 同类自调用(this.xxx())绕过了代理
  • 方法返回了非void/ 非Future类型且被同步使用

一旦@Async没生效,apiLogService.logApiCall(...)就是同步在外层事务里执行的,insert只是把 SQL 发到当前事务的 connection 上,commit 还得等外层事务来决定。

3.2 失败分支为何丢日志

将本案的执行时序展开:

deliveryOrder() 开启事务 TX ├─ kd100OrderFeignClient.border() ← 远程返回 result=false └─ else 分支 ├─ apiLogService.logApiCall(..., FAIL) ← @Async 未生效,SQL 进入 TX └─ throw exception(...) ← TX 标记 rollback-only deliveryOrder() 退出 → Spring 检测到异常 → TX 回滚

成功分支没有throw,TX 正常提交,所以同一行apiLogMapper.insert能落库。
失败分支抛异常触发回滚,日志 insert 被一起撤销,于是"看起来没保存"。

这是审计日志最经典的事故:审计日志的命运不应当跟着业务事务走,但默认情况下它就是跟着走的。

3.3 为什么 catch 看不到异常

try{apiLogMapper.insert(logDO);}catch(Exceptione){log.error("记录API日志失败",e);}

insert发送 SQL 到 connection 后立即返回成功(PostgreSQL 在事务里不会立刻校验外键以外的约束),异常不在insert这一行抛出,而是在外层事务 commit/rollback 阶段才决定结局。catch 自然抓不到,从调用方视角看就是"静默失败"。

四、修复方案

logApiCall@Transactional(propagation = Propagation.REQUIRES_NEW),让审计日志在一个独立的新事务里提交,与外层业务事务完全解耦。

修改后的代码:

@Slf4j@Service@RequiredArgsConstructorpublicclassKd100ApiLogServiceImplimplementsKd100ApiLogService{privatefinalDeliveryKd100ApiLogMapperapiLogMapper;/** * 异步记录 API 日志。 * REQUIRES_NEW 保证日志在独立事务中提交, * 即使外层业务事务回滚,日志也不会丢——审计日志的标准做法。 */@Override@Async@Transactional(propagation=Propagation.REQUIRES_NEW)publicvoidlogApiCall(UUIDorderId,Kd100ApiTypeEnumapiType,StringrequestUrl,StringrequestParams,StringresponseBody,StringerrorMessage,longexecuteTime,booleansuccess){try{DeliveryKd100ApiLogDOlogDO=newDeliveryKd100ApiLogDO();apiLogMapper.insert(logDO);}catch(Exceptione){log.error("记录API日志失败",e);}}}

4.1 修复后的事务时序

deliveryOrder() 开启事务 TX1 ├─ kd100OrderFeignClient.border() ← result=false └─ else 分支 ├─ logApiCall(...) │ └─ REQUIRES_NEW → 挂起 TX1,开启 TX2 │ ├─ apiLogMapper.insert(logDO) │ └─ TX2 commit ✓ 日志已落库 │ ← 恢复 TX1 └─ throw exception(...) ← TX1 标记 rollback-only deliveryOrder() 退出 → TX1 回滚(与 TX2 无关)

业务回滚 = TX1 回滚;审计落库 = TX2 已提交。两者互不影响。

4.2 为什么不去修 @Async

直觉上"把 @Async 修好"也能切到新线程、新事务。但只靠 @Async 有两个隐患:

  1. @EnableAsync配置漂移、同类自调用等仍可能让它退化为同步;
  2. 异步线程脱离了租户上下文(TenantContextHolderThreadLocal),需要额外手工传递。

REQUIRES_NEW事务层面的强保证,无论是否异步、是否同线程,新事务一定独立。两者叠加最稳。

五、原理小结:Spring 事务传播行为

@Transactionalpropagation决定方法被调用时如何使用事务:

传播行为行为适用场景
REQUIRED(默认)有事务就加入,没有就新建普通业务方法
REQUIRES_NEW挂起外层事务,开启全新事务,新事务独立提交/回滚审计日志、操作记录、消息发送等"无论业务成败都要落"的旁路操作
NESTED在外层事务中开 savepoint,可独立回滚但跟随外层提交局部容错
SUPPORTS/NOT_SUPPORTED/MANDATORY/NEVER较少用,按需选择

判断要点:一段逻辑能不能跟主业务"同生共死"?

  • 能 →REQUIRED
  • 不能(日志/审计/告警/对外通知) →REQUIRES_NEW
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/23 7:31:47

Compose 事件分发:Initial、Main、Final

在传统 View 体系中,我们接触最多的就是 dispatchTouchEvent (父节点 → 子节点)└── onInterceptTouchEventonTouchEvent (子节点 → 父节点) 而在 Compose 中,事件分发…

作者头像 李华
网站建设 2026/5/23 7:31:09

区块链+AI+边缘计算:构建可信、高效的糖尿病风险预测系统

1. 项目概述与核心价值作为一名在医疗健康与人工智能交叉领域摸爬滚打了十多年的从业者,我见证过太多“概念很酷,落地很难”的项目。今天想和大家深入聊聊一个将区块链与人工智能结合,用于糖尿病预测的实战项目。这不仅仅是又一个“AI医疗”的…

作者头像 李华
网站建设 2026/5/23 7:25:56

深度剖析LiteOS-M内核队列:数据结构、算法与嵌入式IPC实践

1. 项目概述与核心价值最近在深度研究LiteOS-M内核源码,特别是其进程间通信(IPC)机制中的队列模块。队列作为一种基础且高效的异步通信方式,在资源受限的嵌入式系统中扮演着至关重要的角色。它不像消息队列那样承载复杂的业务数据…

作者头像 李华
网站建设 2026/5/23 7:24:41

XUnity Auto Translator:打破语言壁垒的Unity游戏翻译解决方案

XUnity Auto Translator:打破语言壁垒的Unity游戏翻译解决方案 【免费下载链接】XUnity.AutoTranslator 项目地址: https://gitcode.com/gh_mirrors/xu/XUnity.AutoTranslator 你是否曾经因为语言障碍而错过精彩的Unity游戏?面对日文、韩文或其他…

作者头像 李华
网站建设 2026/5/23 7:24:08

DownKyi终极教程:轻松下载B站8K超高清视频的完整指南

DownKyi终极教程:轻松下载B站8K超高清视频的完整指南 【免费下载链接】downkyi 哔哩下载姬downkyi,哔哩哔哩网站视频下载工具,支持批量下载,支持8K、HDR、杜比视界,提供工具箱(音视频提取、去水印等&#x…

作者头像 李华
网站建设 2026/5/23 7:22:34

Altium Designer CAD与布线实战:硬件工程师高效设计指南

1. 项目概述:为什么AD的CAD与布线是硬件工程师的“左右手”?干了十几年硬件设计,从最初的Protel 99 SE一路用到现在的Altium Designer,我越来越觉得,一个优秀的硬件工程师,必须像熟悉自己的左右手一样&…

作者头像 李华