必须拦截StatementHandler,因其在SQL最终定型前(prepare()调用前)可安全解析AST并注入租户条件;Executor拦截太晚,BoundSql已固化,硬改将引发语法错误或漏过滤。

直接在 Executor 层拦截 query/update 方法,是最稳妥、语义清晰且能拿到完整上下文的方式。它不改 SQL,只做观测和判断,天然适配审计与慢 SQL 场景。
为什么选 Executor 而不是 StatementHandler?
StatementHandler 虽然能最早拿到原始 SQL,但此时参数尚未绑定,BoundSql 中的 SQL 还带占位符(?),无法还原真实执行语句;而 Executor 的 query 和 update 方法调用时,MappedStatement 已准备好,BoundSql 可直接获取已解析的 SQL 字符串,且参数对象完整可用——这对脱敏、拼接、耗时统计都至关重要。
另外,Executor 拦截覆盖所有执行路径(含缓存命中、批量操作等),避免 StatementHandler 因代理链跳过导致漏监控。
如何获取带参数的真实 SQL(非占位符)?
核心是用 DefaultParameterHandler 渲染参数值,再替换 SQL 中的 #{} 或 ${} 占位符。关键步骤如下:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
立即学习“Java免费学习笔记(深入)”;
- 从
MappedStatement获取BoundSql boundSql = ms.getBoundSql(parameter) - 提取原始 SQL:
String sql = boundSql.getSql() - 获取参数映射列表:
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings() - 借助
Configuration创建DefaultParameterHandler,调用setParameters(PreparedStatement)并捕获实际设参过程(或用反射读取boundSql.getAdditionalParameter()) - 对每个
#{prop},用MetaObject从parameter中取值,按类型格式化(字符串加引号、数字不加、null 写为NULL),再正则替换 SQL 中对应位置 - 敏感字段(如
id_card、phone)需提前识别并脱敏,例如替换成'138****1234'
怎么判断慢 SQL 并记录告警?
在 intercept() 中用纳秒级计时包裹 invocation.proceed(),阈值建议设为 500ms(可配置)。示例逻辑:
- 开始前记下
long start = System.nanoTime() - 执行原逻辑:
Object result = invocation.proceed() - 结束后计算耗时:
long costMs = (System.nanoTime() - start) / 1_000_000 - 若
costMs > threshold,则打印 WARN 日志,内容包括:
• 执行时间(毫秒)
• 渲染后的完整 SQL(已脱敏)
• 对应的 Mapper 方法全名(ms.getId())
• 当前线程与用户标识(如有上下文)
审计日志该包含哪些关键字段?
一份合规、可追溯的 SQL 审计日志至少应记录:
-
时间戳:精确到毫秒,使用
Instant.now() -
执行人/租户 ID:从 ThreadLocal 或 Spring Security Context 提取,多租户系统必须带上
tenant_id -
Mapper 方法 ID:
MappedStatement.getId(),例如com.example.mapper.UserMapper.selectById - 完整 SQL(已渲染+脱敏):避免日志泄露敏感数据
-
影响行数:query 返回
List.size(),update 直接取返回值 - 执行耗时(ms):用于性能分析
- 数据库连接信息(可选):如数据源名称、JDBC URL 片段,便于定位库实例
建议将审计日志单独输出到文件或发往 ELK/Splunk,不要混入业务日志流。


















