You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot中如何记录所有执行SQL及递归调用栈信息?

Spring Boot中记录SQL查询及完整调用链的实现方案

以下针对Spring Boot中最常用的两种持久层框架,提供具体实现方案:

一、基于MyBatis的实现方式

1. 自定义MyBatis拦截器拦截SQL执行

通过实现MyBatis的Interceptor接口,拦截SQL执行过程,获取最终执行的SQL语句,同时解析当前线程的调用栈生成业务层到持久层的调用链。

@Intercepts({@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class})})
@Component
public class SqlLoggingInterceptor implements Interceptor {

    private static final Logger logger = LoggerFactory.getLogger(SqlLoggingInterceptor.class);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 获取最终执行的SQL语句
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        BoundSql boundSql = statementHandler.getBoundSql();
        String sql = boundSql.getSql().trim();

        // 解析调用栈,提取Service和Mapper/Repository的调用节点
        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        List<String> callChain = new ArrayList<>();

        for (StackTraceElement element : stackTrace) {
            String className = element.getClassName();
            String methodName = element.getMethodName();
            // 根据项目包结构筛选业务层和持久层类,可自行调整匹配规则
            if (className.contains(".service.") || className.contains(".mapper.") || className.contains(".repository.")) {
                String simpleClassName = className.substring(className.lastIndexOf('.') + 1);
                callChain.add(simpleClassName + "." + methodName + "()");
            }
        }

        // 反转调用链(栈顺序是从当前方法往上,反转后得到Service -> Mapper的调用顺序)
        Collections.reverse(callChain);

        // 组装日志格式并输出
        StringBuilder logContent = new StringBuilder();
        for (String callNode : callChain) {
            logContent.append(callNode).append(" > ");
        }
        logContent.append(sql).append(";");

        logger.info(logContent.toString());
        // 继续执行原SQL逻辑
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // 可配置开关、过滤规则等参数
    }
}

2. 关键注意点

  • 包路径匹配规则:需根据自己项目的包结构调整className.contains的判断条件,确保只捕获业务层和持久层的调用节点。
  • 性能优化:获取调用栈会有一定性能开销,生产环境可通过配置项控制是否启用该拦截器,或者仅在调试阶段开启。
  • 动态SQL处理:MyBatis的BoundSql已经包含解析后的最终SQL,无需额外处理动态拼接的逻辑。

二、基于Spring Data JPA的实现方式

如果使用JPA作为持久层,可以通过Hibernate的拦截器捕获SQL,并结合调用栈生成调用链。

1. 自定义Hibernate拦截器

@Component
public class JpaSqlLoggingInterceptor extends EmptyInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(JpaSqlLoggingInterceptor.class);

    @Override
    public String onPrepareStatement(String sql) {
        // 解析调用栈,提取Service和Repository的调用节点
        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        List<String> callChain = new ArrayList<>();

        for (StackTraceElement element : stackTrace) {
            String className = element.getClassName();
            String methodName = element.getMethodName();
            if (className.contains(".service.") || className.contains(".repository.")) {
                String simpleClassName = className.substring(className.lastIndexOf('.') + 1);
                callChain.add(simpleClassName + "." + methodName + "()");
            }
        }

        // 反转调用链并组装日志
        Collections.reverse(callChain);
        StringBuilder logContent = new StringBuilder();
        for (String callNode : callChain) {
            logContent.append(callNode).append(" > ");
        }
        logContent.append(sql).append(";");

        logger.info(logContent.toString());
        return super.onPrepareStatement(sql);
    }
}

2. 配置Hibernate启用拦截器

在application.yml中添加配置:

spring:
  jpa:
    properties:
      hibernate.ejb.interceptor: com.yourpackage.JpaSqlLoggingInterceptor

3. 关键注意点

  • 版本兼容:不同版本的Hibernate拦截器API可能略有差异,需根据项目使用的版本调整。
  • SQL覆盖范围:该拦截器可捕获JPA原生SQL查询和JPQL解析后的最终SQL。

通用优化建议

  • 调用栈过滤:可以进一步过滤掉Spring框架自身的方法(如org.springframework开头的类),避免调用链中混入无关节点。
  • SQL格式化:如果需要格式化SQL,可引入第三方库(如com.github.vertical-blank:sql-formatter)对SQL进行格式化后再输出。
  • 开关控制:通过@Value注入配置项,动态开启或关闭日志功能,避免影响生产环境性能。

内容的提问来源于stack exchange,提问作者Miladro

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 21:45:34