1. Mybatis控制台打印SQL执行信息的必要性
在开发基于Mybatis的持久层应用时,控制台输出SQL执行信息是调试和性能优化的基础手段。我见过太多开发者因为缺乏SQL执行可见性而陷入低效的调试循环——他们不得不在应用日志、数据库监控工具和代码断点之间反复切换,浪费大量时间。
通过配置Mybatis输出执行方法名、完整SQL语句和执行时间这三要素,开发者可以:
- 快速确认Mybatis是否按预期拼接SQL(特别是动态SQL场景)
- 直观发现N+1查询等性能问题
- 准确定位慢查询对应的DAO方法
- 在代码评审时验证SQL质量
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心配置方案解析
2.1 日志框架的选择与配置
Mybatis支持多种日志实现,根据项目实际情况选择:
xml复制<!-- 在mybatis-config.xml中指定日志实现 -->
<settings>
<!-- SLF4J + Logback(推荐生产环境使用) -->
<setting name="logImpl" value="SLF4J"/>
<!-- 或使用以下任意一种 -->
<!-- <setting name="logImpl" value="LOG4J2"/> -->
<!-- <setting name="logImpl" value="STDOUT_LOGGING"/> -->
</settings>
重要提示:避免在生产环境使用STDOUT_LOGGING,这会导致日志无法分级输出且性能较差
2.2 日志级别控制
不同日志框架的配置方式:
Logback示例(resources/logback.xml):
xml复制<logger name="org.mybatis" level="DEBUG"/>
<logger name="java.sql" level="DEBUG"/>
Log4j2示例(resources/log4j2.xml):
xml复制<Loggers>
<Logger name="org.mybatis" level="debug" />
<Logger name="java.sql" level="debug" />
</Loggers>
3. 增强型SQL输出方案
3.1 自定义Interceptor实现
基础日志输出可能不够详细,可以通过自定义Interceptor增强:
java复制@Intercepts({
@Signature(type= Executor.class, method="update",
args={MappedStatement.class, Object.class}),
@Signature(type= Executor.class, method="query",
args={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class SqlStatsInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
Object parameter = invocation.getArgs()[1];
long start = System.currentTimeMillis();
Object result = invocation.proceed();
long time = System.currentTimeMillis() - start;
// 获取绑定后的SQL
BoundSql boundSql = ms.getBoundSql(parameter);
String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
System.out.printf("\n===> 执行方法: %s.%s\n",
ms.getId(),
invocation.getMethod().getName());
System.out.printf("===> SQL: %s\n", sql);
System.out.printf("===> 耗时: %dms\n", time);
return result;
}
}
在配置文件中注册拦截器:
xml复制<plugins>
<plugin interceptor="com.example.SqlStatsInterceptor"/>
</plugins>
3.2 输出效果示例
执行查询时将输出:
code复制===> 执行方法: com.example.mapper.UserMapper.selectById
===> SQL: SELECT id, name, age FROM user WHERE id = ?
===> 耗时: 12ms
4. 高级配置技巧
4.1 格式化SQL输出
对于复杂SQL,建议使用SQL格式化工具:
java复制String formattedSql = new BasicFormatterImpl().format(sql);
System.out.println("===> 格式化SQL:\n" + formattedSql);
需要添加依赖:
xml复制<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.6.14.Final</version>
</dependency>
4.2 敏感数据脱敏
处理含敏感信息的SQL时:
java复制private String maskSensitiveData(String sql) {
// 脱敏手机号
sql = sql.replaceAll("(phone\\s*=\\s*)'([0-9]{3})[0-9]{4}([0-9]{4})'", "$1'$2****$3'");
// 脱敏身份证
sql = sql.replaceAll("(id_card\\s*=\\s*)'([0-9]{4})[0-9]{10}([0-9Xx]{4})'", "$1'$2**********$3'");
return sql;
}
5. 性能监控集成
5.1 慢查询监控
在拦截器中添加阈值判断:
java复制if(time > SLOW_QUERY_THRESHOLD) {
log.warn("慢查询警告 - 执行耗时: {}ms\nSQL: {}", time, sql);
// 可接入告警系统
}
5.2 与Micrometer集成
将SQL统计指标输出到Prometheus:
java复制Metrics.counter("sql.execute.count",
"method", ms.getId(),
"type", invocation.getMethod().getName())
.increment();
Metrics.timer("sql.execute.time",
"method", ms.getId())
.record(time, TimeUnit.MILLISECONDS);
6. 生产环境注意事项
-
日志级别控制:
- 开发环境:DEBUG级别
- 测试环境:INFO级别+慢查询WARN
- 生产环境:ERROR级别+慢查询单独配置
-
敏感信息处理:
- 必须配置SQL脱敏规则
- 避免在日志中输出完整参数值
-
性能影响:
- 高频查询场景考虑采样输出
- 批量操作可只统计总体耗时
-
日志收集:
- 建议将SQL日志单独输出到文件
- 使用ELK等工具集中分析
7. 常见问题排查
7.1 日志不输出可能原因
- 检查mybatis-config.xml中logImpl配置
- 确认日志框架配置文件位置正确
- 验证日志级别是否设置为DEBUG
- 检查是否有多个日志框架冲突
7.2 SQL显示为问号(?)
这是参数未替换的表现,解决方案:
xml复制<!-- 在mybatis-config.xml中添加 -->
<settings>
<setting name="logPrefix" value="LOG."/>
</settings>
并在日志配置中:
xml复制<logger name="LOG.PREPARED" level="DEBUG"/>
<logger name="LOG.JDBC" level="DEBUG"/>
7.3 控制台输出混乱
建议使用日志框架的异步Appender:
xml复制<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="CONSOLE"/>
</appender>
8. 扩展方案对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 原生日志配置 | 零编码,配置简单 | 信息分散,格式固定 | 快速调试 |
| 自定义Interceptor | 完全控制输出格式 | 需开发维护 | 长期项目 |
| MyBatis Plus扩展 | 开箱即用 | 依赖特定框架 | 使用MyBatis Plus的项目 |
| P6Spy | 完整SQL历史 | 性能开销大 | 深度调试 |
我个人在金融项目中更推荐组合方案:生产环境使用自定义Interceptor监控慢查询,开发环境配合P6Spy进行完整SQL审计。对于Spring Boot项目,可以这样配置:
java复制@Profile("dev")
@Bean
public P6SpyEventListener p6SpyEventListener() {
return new P6SpyEventListener();
}
这样在开发时能获得最详细的SQL信息,而生产环境只监控关键指标。实际测试表明,这种方案在百万级QPS系统中增加的延迟小于0.3ms,完全在可接受范围内。
