java应用中记录sql耗时日志的核心是捕获执行前后时间并结合sql、参数、结果输出结构化日志;可手动包装、用mybatis拦截器、jdbc动态代理或spring aop实现,需注意脱敏、阈值控制及生产环境性能影响。

在 Java 应用中记录 SQL 执行耗时日志,核心是捕获 SQL 语句执行前后的系统时间,并结合上下文(如 SQL 内容、参数、执行结果)输出结构化日志。不依赖框架时可手动包装;使用主流框架(如 MyBatis、JDBC 模板、JPA)时,优先利用其内置拦截/代理机制,避免侵入业务代码。
MyBatis 中通过拦截器记录 SQL 耗时
MyBatis 提供 Interceptor 接口,可拦截 StatementHandler 的 query 和 update 方法,在执行前后打点计时。
- 实现
Interceptor,重写intercept方法,用System.nanoTime()获取高精度起止时间 - 通过
StatementHandler.boundSql().getSql()获取预编译 SQL(含占位符) - 通过
StatementHandler.parameterHandler().getParameterObject()获取参数,建议用ToStringBuilder.reflectionToString()或轻量 JSON 序列化避免循环引用 - 捕获异常时也记录耗时和错误信息,便于定位慢 SQL 或失败原因
JDBC 层统一代理(适用于原生 JDBC 或多框架共存场景)
对 Connection、PreparedStatement 等对象做动态代理,在 execute* 方法调用前后统计耗时。
Java Maven 项目审查工作流的通用输入处理。当 Java Maven 项目以 ZIP压缩包或 GitLab 仓库 URL 形式提供且需要...时使用。
- 用
java.lang.reflect.Proxy包装PreparedStatement,在invoke中判断方法名是否为execute、executeQuery、executeUpdate - 记录原始 SQL(
toString()可能含实际参数,但需注意部分驱动不支持;更稳妥方式是解析PreparedStatement的parameterMetaData+ 参数值拼接) - 将耗时日志输出到 SLF4J,格式示例:
SQL[SELECT * FROM user WHERE id = ?] | Params[123] | Time[127ms] | Result[1 row]
Spring JDBC Template 配合 AOP 实现无侵入耗时记录
若项目使用 JdbcTemplate,可用 Spring AOP 切入 org.springframework.jdbc.core.JdbcTemplate 的公开方法。
- 定义切点匹配
query*、update*、execute*等方法 - 在
@Around通知中获取方法参数:SQL 字符串通常为第一个参数,参数数组/对象紧随其后 - 避免记录敏感字段(如密码、身份证),可在日志前对 SQL 或参数做关键词过滤或脱敏处理(如正则替换
password\s*=\s*['"]([^'"]*)['"]) - 设置阈值(如 > 500ms)只记录慢 SQL,减少日志噪音
生产环境注意事项
耗时日志虽重要,但高频记录可能影响性能和磁盘 I/O,需合理控制粒度。
- 禁用 DEBUG 日志级别以外的 SQL 日志(尤其不能在 PROD 开
logging.level.org.springframework.jdbc=DEBUG) - 使用异步日志框架(如 Logback 的
AsyncAppender)降低主线程阻塞风险 - 考虑集成分布式链路追踪(如 SkyWalking、Pinpoint),SQL 耗时自动作为子 Span 上报,比纯文本日志更易聚合分析
- 定期归档或对接 ELK,用 Kibana 做慢 SQL 趋势、Top N 统计
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










