首页 百科大全文章正文

Spring Boot3整合MyBatis的3种 SQL 语句记录方案

百科大全 2025年09月09日 13:48 1 admin
Spring Boot3整合MyBatis的3种 SQL 语句记录方案

作为一名资深 Java 开发工程师,我曾在一个电商项目上线前遭遇过一次 “惊魂时刻”:生产环境中订单支付模块突然出现数据不一致问题,但由于未配置 SQL 日志记录,无法快速定位是哪条语句执行异常,最终花费了 4 个小时才排查出是 MyBatis 动态 SQL 拼接时的参数绑定错误。这件事让我深刻意识到,在 Spring Boot 整合 MyBatis 的开发过程中,SQL 语句记录不仅是调试的 “利器”,更是线上问题排查的 “救命稻草”

本文将针对 Spring Boot3 与 MyBatis 整合场景,详细拆解 3 种主流的 SQL 记录方案,从适合新手的快速配置到适合进阶开发的自定义拦截器,再到企业级项目常用的第三方组件,每一种方案都附带完整代码示例和避坑要点,帮助互联网软件开发同行高效解决 SQL 调试难题。

基础方案:日志框架配置法(新手首选)

日志配置是实现 SQL 记录最便捷的方式,无需编写额外代码,仅通过简单的配置即可实现 SQL 语句、参数及执行时间的输出。Spring Boot3 默认支持 Logback、Log4j2 等主流日志框架,以下分别介绍两种最常用的配置方式。

1.1 快速控制台输出:MyBatis 原生日志配置

MyBatis 内置了 stdout 日志实现,适合开发初期的快速调试。只需在application.yml(或application.properties)中添加一行配置,即可在控制台直接打印 SQL 语句。

配置代码

mybatis:  configuration:    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 启用MyBatis原生控制台日志    map-underscore-to-camel-case: true # 可选:开启下划线转驼峰映射

效果展示

启动项目后执行数据库操作,控制台会输出类似日志:

==>  Preparing: SELECT id, user_name, age FROM t_user WHERE id = ? ==> Parameters: 1(Integer)<==    Columns: id, user_name, age<==        Row: 1, zhangsan, 25<==      Total: 1

避坑要点

该方案仅适合本地开发调试,严禁在生产环境使用,否则会导致日志输出过多,影响系统性能并泄露敏感数据;

若项目中同时配置了其他日志框架(如 Log4j2),可能会出现日志冲突,此时建议优先使用日志框架的级别配置方案。

1.2 灵活日志级别控制:基于 SLF4J 的包级别配置

在实际开发中,我们通常需要更灵活的日志控制(如只输出某一 Mapper 接口的 SQL,或调整日志输出格式),此时可通过配置日志框架的级别来实现。Spring Boot3 默认集成 SLF4J+Logback,以下以常用的application.yml配置为例。

配置代码

logging:  level:    com.example.demo.mapper: debug # Mapper接口所在包路径设为debug级别    org.springframework.web: info # 其他组件设为info级别,减少日志冗余  pattern:    console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n" # 自定义日志格式

核心原理

MyBatis 的 SQL 日志仅在debug级别下输出,因此只需将 Mapper 接口所在的包级别设置为debug,即可精准捕获该包下所有接口的 SQL 操作,而不影响其他组件的日志输出。

进阶配置

若需将 SQL 日志输出到文件(方便后期排查),可增加日志文件配置:

logging:  file:    name: logs/sql-debug.log # 日志文件路径  logback:    rollingpolicy:      max-file-size: 10MB # 单个文件最大10MB      max-history: 7 # 保留7天日志

进阶方案:自定义 MyBatis 拦截器(按需扩展)

当日志配置无法满足个性化需求(如 SQL 语句加密、执行耗时统计、异常报警等)时,可通过自定义 MyBatis 拦截器实现更灵活的 SQL 记录逻辑。MyBatis 提供了 Executor、StatementHandler 等拦截点,其中StatementHandler拦截器可直接获取最终执行的 SQL 语句(含绑定后的参数),实用性最强。

2.1 拦截器开发全流程

步骤 1:编写拦截器类

实现org.apache.ibatis.plugin.Interceptor接口,并重写核心方法,重点处理 SQL 语句提取和记录逻辑。

代码实现

package com.example.demo.interceptor;import org.apache.ibatis.executor.statement.StatementHandler;import org.apache.ibatis.plugin.*;import org.apache.ibatis.reflection.MetaObject;import org.apache.ibatis.reflection.SystemMetaObject;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import java.sql.Connection;import java.util.Properties;/** * 自定义MyBatis SQL记录拦截器 */@Intercepts({@Signature(        type = StatementHandler.class, // 拦截StatementHandler接口        method = "prepare", // 拦截prepare方法(SQL预处理阶段)        args = {Connection.class, Integer.class} // 方法参数)})public class SqlRecordInterceptor implements Interceptor {    private static final Logger logger = LoggerFactory.getLogger(SqlRecordInterceptor.class);    @Override    public Object intercept(Invocation invocation) throws Throwable {        // 1. 获取StatementHandler对象        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();        // 2. 通过MetaObject反射获取目标对象的属性(MyBatis提供的工具类)        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);        // 3. 提取SQL语句(boundSql.sql属性存储最终执行的SQL)        String sql = (String) metaObject.getValue("boundSql.sql");        // 4. 提取Mapper接口方法名(mappedStatement.id属性)        String methodName = (String) metaObject.getValue("mappedStatement.id");                // 5. 自定义记录逻辑:如打印SQL、统计耗时、存储到数据库等        long startTime = System.currentTimeMillis();        try {            // 执行原方法(继续SQL预处理流程)            return invocation.proceed();        } finally {            // 计算执行耗时            long costTime = System.currentTimeMillis() - startTime;            logger.info("【SQL执行记录】方法:{} | SQL:{} | 耗时:{}ms",                         methodName, sql.replaceAll("\\s+", " "), costTime);            // 此处可扩展:如耗时超过1000ms时发送报警通知            if (costTime > 1000) {                logger.warn("【SQL慢查询警告】方法:{} 耗时超过1秒,需优化", methodName);                // sendAlarm(methodName, sql, costTime); // 自定义报警方法            }        }    }    @Override    public Object plugin(Object target) {        // 包装目标对象,返回代理对象        return Plugin.wrap(target, this);    }    @Override    public void setProperties(Properties properties) {        // 可通过配置文件传递参数(如慢查询阈值)        String slowThreshold = properties.getProperty("slowThreshold", "1000");        logger.info("SQL慢查询阈值设置为:{}ms", slowThreshold);    }}

步骤 2:注册拦截器

在 Spring Boot3 中,通过@Configuration类将拦截器注册到 MyBatis 中,有两种注册方式:

方式一:通过 MyBatis 配置类注册

package com.example.demo.config;import com.example.demo.interceptor.SqlRecordInterceptor;import org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configurationpublic class MyBatisConfig {    @Bean    public ConfigurationCustomizer configurationCustomizer() {        return configuration -> {            // 注册自定义拦截器            SqlRecordInterceptor interceptor = new SqlRecordInterceptor();            // 可选:设置拦截器参数            Properties properties = new Properties();            properties.setProperty("slowThreshold", "800"); // 慢查询阈值设为800ms            interceptor.setProperties(properties);            configuration.addInterceptor(interceptor);        };    }}

方式二:通过@MapperScan注解注册(简化版)

若项目中使用@MapperScan扫描 Mapper 接口,可直接通过sqlSessionFactoryRef关联配置,但推荐使用方式一,更便于参数配置。

2.2 拦截器核心优势与避坑点

核心优势

  • 可获取绑定参数后的完整 SQL 语句(日志配置输出的 SQL 含?占位符,需结合参数对应);
  • 支持自定义业务逻辑,如慢查询报警、SQL 语法校验、敏感字段脱敏等;
  • 不侵入业务代码,通过 AOP 思想实现功能扩展。

避坑要点

  • 拦截器会影响系统性能,生产环境需谨慎使用,建议仅对核心业务接口启用;
  • 避免拦截Executor接口的query/update方法,因为该阶段 SQL 尚未绑定参数,需手动解析参数,复杂度较高;
  • 若项目中使用多个拦截器,需通过@Order注解指定执行顺序,避免逻辑冲突。

企业级方案:使用第三方组件 datasource-proxy

对于中大型项目,推荐使用成熟的第三方组件datasource-proxy实现 SQL 记录。该组件通过代理数据源的方式拦截 SQL 操作,支持 MyBatis、JPA、JDBC 等多种持久层框架,功能强大且稳定性高。

3.1 集成步骤

步骤 1:添加依赖

在pom.xml(Maven)或build.gradle(Gradle)中添加datasource-proxy依赖:

<!-- datasource-proxy 核心依赖 --><dependency>    <groupId>net.ttddyy</groupId>    <artifactId>datasource-proxy</artifactId>    <version>1.10.0</version> <!-- 兼容Spring Boot3的最新版本 --></dependency>

步骤 2:配置代理数据源

编写配置类,将 Spring Boot 默认的数据源包装为datasource-proxy的代理数据源,并配置 SQL 记录器。

代码实现

package com.example.demo.config;import net.ttddyy.dsproxy.listener.logging.Slf4JLogLevel;import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.jdbc.datasource.DataSourceTransactionManager;import org.springframework.transaction.PlatformTransactionManager;import javax.sql.DataSource;@Configurationpublic class DataSourceProxyConfig {    /**     * 配置代理数据源     */    @Bean    public DataSource dataSource(DataSourceProperties dataSourceProperties) {        // 1. 获取Spring Boot自动配置的数据源(HikariCP/Druid等)        DataSource originalDataSource = dataSourceProperties.initializeDataSourceBuilder().build();                // 2. 包装为代理数据源,配置SQL记录器        return ProxyDataSourceBuilder.create(originalDataSource)                .name("SQL-Proxy-DataSource") // 数据源名称,用于日志区分                .logQueryBySlf4j(Slf4JLogLevel.DEBUG) // 查询SQL日志级别设为DEBUG                .logUpdateBySlf4j(Slf4JLogLevel.INFO) // 更新SQL日志级别设为INFO                .logSlowQueryBySlf4j(Slf4JLogLevel.WARN, 1000) // 慢查询(>1000ms)设为WARN                .formatSql(true) // 格式化SQL语句,便于阅读                .multilineSql(true) // 多行显示SQL                .build();    }    /**     * 配置事务管理器(必须使用代理数据源)     */    @Bean    public PlatformTransactionManager transactionManager(DataSource dataSource) {        return new DataSourceTransactionManager(dataSource);    }}

3.2 高级功能与实战场景

功能 1:SQL 语句格式化与脱敏

datasource-proxy支持自定义 SQL 格式化器和数据脱敏处理器,例如对手机号、身份证号等敏感字段进行脱敏:

// 添加数据脱敏处理器import net.ttddyy.dsproxy.proxy.ParameterSetOperation;import java.util.List;public class SensitiveDataProcessor {    public void processParameters(List<ParameterSetOperation> parameters) {        for (ParameterSetOperation param : parameters) {            Object value = param.getValue();            if (value instanceof String) {                String strValue = (String) value;                // 手机号脱敏:138****1234                if (strValue.matches("^1[3-9]\\d{9}$")) {                    param.setValue(strValue.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2"));                }            }        }    }}// 在代理数据源中注册处理器return ProxyDataSourceBuilder.create(originalDataSource)        .beforeQuery((execInfo, param) -> new SensitiveDataProcessor().processParameters(param))        .beforeUpdate((execInfo, param) -> new SensitiveDataProcessor().processParameters(param))        .build();

功能 2:SQL 执行监控与埋点

结合 Prometheus+Grafana 可实现 SQL 执行指标监控,datasource-proxy提供了MetricsListener接口,支持自定义指标收集:

import io.micrometer.core.instrument.MeterRegistry;import net.ttddyy.dsproxy.listener.MetricsListener;@Beanpublic MetricsListener metricsListener(MeterRegistry meterRegistry) {    MetricsListener listener = new MetricsListener(meterRegistry, "sql.execution");    listener.setQueryThresholdForSlowQuery(1000);    return listener;}// 注册到代理数据源return ProxyDataSourceBuilder.create(originalDataSource)        .listener(metricsListener(meterRegistry))        .build();

企业级优势

  • 与 Spring Boot3 生态无缝集成,支持主流数据源(HikariCP、Druid、C3P0 等);
  • 提供完善的 SQL 监控、慢查询报警、数据脱敏功能,无需重复造轮子;
  • 性能损耗低(官方测试损耗率 < 5%),适合生产环境大规模使用。

三种方案对比与选型建议

为帮助大家根据项目场景快速选型,以下从适用场景、实现成本、功能扩展性三个维度进行对比:

方案类型

适用场景

实现成本

功能扩展性

生产环境推荐度

日志框架配置

本地开发调试、简单 SQL 记录

低(0 代码)

★☆☆☆☆

自定义拦截器

个性化需求(如定制报警)

中(需开发)

★★★☆☆

datasource-proxy

中大型项目、企业级监控

低(配置化)

极强

★★★★★

选型建议

  1. 开发阶段:优先使用 “日志框架配置法”,快速实现 SQL 调试;
  2. 测试阶段:启用 “自定义拦截器”,统计核心接口 SQL 执行耗时,提前发现慢查询;
  3. 生产阶段:采用 “datasource-proxy”,结合监控平台实现全方位 SQL 管控。

总结

本文介绍的三种 SQL 记录方案,覆盖了从开发到生产的全流程需求。在实际项目中,建议采用 “分层策略”:开发环境用日志配置,测试环境加拦截器监控,生产环境部署 datasource-proxy + 监控平台。

最后分享两个实战避坑经验:

  1. 无论采用哪种方案,生产环境都需控制日志输出级别,避免 SQL 日志过多导致磁盘占满;
  2. 对于核心业务 SQL(如支付、订单),建议额外存储 SQL 执行记录到专门的日志表,便于事后追溯(需注意数据脱敏,避免敏感信息泄露)。

如果大家在实践过程中遇到拦截器注册失败、datasource-proxy 与 Druid 兼容性问题等,欢迎在评论区留言讨论,我会第一时间为大家解答!

发表评论

泰日号Copyright Your WebSite.Some Rights Reserved. 网站地图 备案号:川ICP备66666666号 Z-BlogPHP强力驱动