1. 为什么需要打印MyBatis的SQL执行信息
在开发基于MyBatis的Java应用时,SQL语句的执行情况是我们最需要关注的调试信息之一。但默认情况下,MyBatis框架并不会在控制台输出完整的SQL执行日志,这给日常开发和问题排查带来了诸多不便。
想象这样一个场景:你在测试环境发现某个查询接口返回了错误数据,但代码里只有Mapper接口的方法调用,看不到最终执行的SQL语句。此时如果没有SQL日志,你只能靠猜测来排查问题——是参数绑定错了?还是SQL拼接有问题?或者是MyBatis的缓存机制在作怪?这种"盲调"状态会极大降低开发效率。
打印SQL执行信息的核心价值体现在三个方面:
- 调试辅助:能看到最终发送到数据库的真实SQL,包括参数值替换后的完整语句
- 性能分析:通过执行时间可以快速定位慢查询
- 审计追踪:记录所有数据库操作,便于后续分析
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. MyBatis日志模块的工作原理
2.1 MyBatis的日志体系结构
MyBatis没有重新发明轮子,而是采用了适配器模式整合了主流日志框架。其核心接口org.apache.ibatis.logging.Log定义了日志输出的基本方法,底层实际委托给以下日志实现:
- SLF4J
- Log4j2
- Log4j
- JDK logging
- Commons Logging
- No Logging(关闭日志)
这种设计使得MyBatis可以无缝集成到项目现有的日志体系中。当同时存在多个日志框架时,MyBatis会按照上述顺序选择第一个可用的实现。
2.2 SQL日志的触发时机
MyBatis在执行SQL语句的过程中,会在关键节点触发日志记录:
- 准备阶段:记录执行的方法名和参数
- SQL构建:显示替换参数前的带
?的语句 - 参数绑定:打印每个参数的类型和值
- 执行阶段:记录发送到数据库的完整SQL
- 结果处理:输出执行耗时和返回结果条数
3. 配置MyBatis打印SQL日志的四种方式
3.1 通过Log4j配置(推荐)
这是最灵活的方式,适合大多数项目。假设使用Log4j2,需要在log4j2.xml中添加:
xml复制<Loggers>
<Logger name="org.mybatis" level="DEBUG"/>
<Logger name="java.sql.Connection" level="DEBUG"/>
<Logger name="java.sql.Statement" level="DEBUG"/>
<Logger name="java.sql.PreparedStatement" level="DEBUG"/>
<Logger name="java.sql.ResultSet" level="DEBUG"/>
<Root level="INFO">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
关键点说明:
org.mybatis包控制MyBatis核心日志java.sql相关包捕获JDBC层面的执行信息- 生产环境建议将级别调整为
INFO避免日志量过大
3.2 在mybatis-config.xml中设置
如果你使用MyBatis独立的配置文件,可以添加:
xml复制<configuration>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
</configuration>
这种方式简单但不够灵活,适合快速调试场景。缺点是:
- 只能输出到控制台
- 无法按包路径细粒度控制
- 会打印大量冗余信息
3.3 使用Spring Boot的application.yml
对于Spring Boot项目,可以在配置文件中指定:
yaml复制logging:
level:
org.mybatis: DEBUG
java.sql.Connection: DEBUG
java.sql.Statement: DEBUG
java.sql.PreparedStatement: DEBUG
Spring Boot会自动适配底层日志实现(通常为Logback),这是目前最简洁的配置方式。
3.4 编程式设置(不推荐)
在代码中硬编码日志级别:
java复制@Configuration
public class MyBatisConfig {
@PostConstruct
public void setLogLevel() {
org.apache.ibatis.logging.LogFactory.useLog4J2Logging();
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
Configuration config = ctx.getConfiguration();
LoggerConfig loggerConfig = config.getLoggerConfig("org.mybatis");
loggerConfig.setLevel(Level.DEBUG);
ctx.updateLoggers();
}
}
这种方式耦合度高,不利于维护,仅建议在特殊场景下使用。
4. 解读典型的SQL执行日志
配置成功后,控制台会输出类似这样的日志:
code复制2023-08-20 14:30:45.456 DEBUG 12345 --- [nio-8080-exec-1] o.m.s.t.SpringManagedTransaction : JDBC Connection [HikariProxyConnection@12345678] will be managed by Spring
2023-08-20 14:30:45.457 DEBUG 12345 --- [nio-8080-exec-1] c.e.m.UserMapper.selectById : ==> Preparing: SELECT * FROM user WHERE id = ?
2023-08-20 14:30:45.458 DEBUG 12345 --- [nio-8080-exec-1] c.e.m.UserMapper.selectById : ==> Parameters: 1(Integer)
2023-08-20 14:30:45.460 DEBUG 12345 --- [nio-8080-exec-1] c.e.m.UserMapper.selectById : <== Total: 1
2023-08-20 14:30:45.461 DEBUG 12345 --- [nio-8080-exec-1] o.m.s.t.SpringManagedTransaction : JDBC Connection [HikariProxyConnection@12345678] will not be managed by Spring
日志结构解析:
- Preparing:显示带占位符的SQL模板
- Parameters:参数类型和实际值
- Total:返回结果集大小
- 时间戳和线程信息有助于分布式追踪
5. 高级技巧与性能优化
5.1 只打印SQL不显示结果集
当处理大数据量查询时,结果集日志可能刷屏。可以通过调整日志级别过滤:
xml复制<Logger name="java.sql.ResultSet" level="INFO"/>
5.2 测量SQL执行时间
MyBatis本身不直接提供执行时间统计,但可以通过以下方式实现:
方案一:使用拦截器
java复制@Intercepts({
@Signature(type= StatementHandler.class, method="query", args={Statement.class, ResultHandler.class}),
@Signature(type= StatementHandler.class, method="update", args={Statement.class})
})
public class SqlCostInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
long start = System.currentTimeMillis();
Object result = invocation.proceed();
long end = System.currentTimeMillis();
System.out.println("执行耗时: " + (end - start) + "ms");
return result;
}
}
方案二:利用Log4j的PatternLayout
xml复制<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n 耗时: %X{executionTime}ms"/>
5.3 敏感数据脱敏
对于包含密码等敏感字段的SQL,建议添加脱敏处理器:
java复制public class SensitiveParamLogger extends BaseJdbcLogger {
@Override
public void setColumn(Object column, Object value) {
if(column.toString().contains("password")) {
super.setColumn(column, "******");
} else {
super.setColumn(column, value);
}
}
}
6. 常见问题排查指南
6.1 日志不输出的可能原因
- 日志级别设置过高:确认是DEBUG而非INFO
- 包路径不匹配:检查
org.mybatis和java.sql是否正确 - 日志框架冲突:排除依赖冲突,如同时存在Log4j和Logback
- 异步日志缓冲:尝试添加
immediateFlush=true
6.2 日志格式混乱的解决
在Spring Boot中遇到控制台乱码时:
yaml复制logging:
charset:
console: UTF-8
对于IDEA用户,还需检查:
- File → Settings → Editor → General → Console
- 勾选"Default Encoding"为UTF-8
6.3 MyBatis-Plus的特殊配置
如果使用MyBatis-Plus,需要额外配置:
yaml复制mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
7. 生产环境的最佳实践
-
日志分级存储:
- DEBUG级别日志写入单独文件
- 设置合理的滚动策略和保留天数
-
敏感信息过滤:
xml复制<Logger name="java.sql.PreparedStatement" level="DEBUG"> <Filter type="regex" regex="password=.*?" replacement="password=****"/> </Logger> -
性能监控集成:
- 将SQL执行时间指标接入Prometheus
- 设置慢查询阈值告警
-
日志采样:
java复制// 仅记录10%的查询日志 if(Math.random() < 0.1) { log.debug("Sampled SQL: {}", sql); }
8. 扩展思路:更强大的SQL监控方案
对于企业级应用,可以考虑:
-
Druid监控台:
java复制@Bean public ServletRegistrationBean<StatViewServlet> druidServlet() { return new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*"); } -
SkyWalking分布式追踪:
yaml复制skywalking: plugin: mybatis: trace_sql_parameters: true -
自定义注解监控:
java复制@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface SqlMonitor { String value() default ""; }
在实际项目中,我通常会根据团队的技术栈选择组合方案。对于中小型项目,Log4j2配置已经足够;而在微服务架构下,结合SkyWalking等APM工具能提供更全面的监控视角。
