1. MyBatis Plus打印SQL日志的必要性
在Java后端开发中,SQL语句的调试和优化是日常工作中的重要环节。MyBatis Plus作为MyBatis的增强工具,虽然提供了强大的CRUD操作能力,但在默认配置下并不会直接打印执行的SQL语句到控制台。这给开发调试带来了不便,特别是在以下场景中:
- 排查数据查询异常时,需要确认实际执行的SQL语句是否符合预期
- 性能优化时需要分析SQL语句的执行效率
- 验证动态SQL拼接结果是否正确
- 审计系统操作时需要记录数据变更的SQL语句
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 配置MyBatis Plus打印SQL日志的三种方式
2.1 通过application.yml/properties配置
这是最常用的配置方式,适合Spring Boot项目:
yaml复制# application.yml配置示例
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
或者使用properties格式:
properties复制# application.properties配置示例
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
这种配置方式会将完整的SQL语句(包含参数占位符?)和执行参数分别打印出来,格式如下:
code复制==> Preparing: SELECT id,name FROM user WHERE age > ?
==> Parameters: 18(Integer)
2.2 通过代码配置方式
对于非Spring Boot项目或需要动态控制日志打印的场景,可以通过代码配置:
java复制@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加其他插件...
return interceptor;
}
@Bean
public ConfigurationCustomizer configurationCustomizer() {
return configuration -> {
configuration.setLogImpl(StdOutImpl.class);
};
}
}
2.3 使用第三方日志框架集成
生产环境推荐使用SLF4J+Logback等日志框架,可以更灵活地控制日志级别和输出格式:
xml复制<!-- logback.xml配置示例 -->
<logger name="com.baomidou.mybatisplus" level="DEBUG"/>
<logger name="org.apache.ibatis" level="DEBUG"/>
这种配置下,SQL日志会通过项目的日志系统输出,可以方便地对接日志收集系统。
3. SQL日志输出格式详解与优化
3.1 标准日志格式解析
MyBatis Plus默认的SQL日志输出包含几个关键部分:
- Preparing行:显示带占位符的SQL语句
- Parameters行:显示实际参数值
- 执行时间(如果配置了性能分析插件)
示例输出:
code复制Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5e3a8624] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@1852096063 wrapping com.mysql.cj.jdbc.ConnectionImpl@5b87ed94] will not be managed by Spring
==> Preparing: SELECT id,username FROM sys_user WHERE dept_id = ? AND status = ?
==> Parameters: 1001(Long), 1(Integer)
<== Columns: id, username
<== Row: 10001, admin
<== Total: 1
3.2 美化SQL输出格式
可以通过自定义日志实现类来美化SQL输出:
java复制public class PrettyStdOutImpl implements Log {
// 实现细节省略...
}
// 然后在配置中指定
mybatis-plus.configuration.log-impl=com.yourpackage.PrettyStdOutImpl
美化后的输出示例:
code复制╔══════════════════════════════════════════════════════════╗
║ SQL: SELECT * FROM user WHERE name LIKE ? AND age > ? ║
║ Params: %张%, 18 ║
╚══════════════════════════════════════════════════════════╝
4. 生产环境下的SQL日志管理策略
4.1 按环境区分日志级别
建议在不同环境中采用不同的日志策略:
| 环境 | 日志级别 | 建议配置 |
|---|---|---|
| 开发 | DEBUG | 输出完整SQL及参数 |
| 测试 | INFO | 只输出SQL摘要 |
| 生产 | WARN | 只记录慢查询 |
4.2 敏感数据脱敏处理
对于包含敏感信息的SQL(如密码、手机号等),应该实现自定义的日志过滤器:
java复制public class SensitiveDataLogger extends StdOutImpl {
@Override
public void debug(String s) {
// 对敏感信息进行脱敏处理
super.debug(maskSensitiveData(s));
}
}
4.3 性能分析与慢SQL监控
结合MyBatis Plus的性能分析插件,可以监控SQL执行时间:
java复制@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PerformanceInnerInterceptor());
return interceptor;
}
配置后会在日志中输出SQL执行时间:
code复制Time:2 ms - ID:com.example.mapper.UserMapper.selectById
Execute SQL:SELECT id,name FROM user WHERE id=1001
5. 常见问题排查与解决方案
5.1 日志不输出的常见原因
-
日志级别设置过高:检查是否将日志级别设置为DEBUG
properties复制logging.level.com.baomidou.mybatisplus=DEBUG -
多数据源配置问题:在多数据源场景下,需要为每个数据源单独配置
java复制@Bean @ConfigurationProperties("spring.datasource.druid.first") public DataSource firstDataSource() { DataSource dataSource = DruidDataSourceBuilder.create().build(); // 需要设置MyBatis配置 MybatisSqlSessionFactoryBean factoryBean = new MybatisSqlSessionFactoryBean(); factoryBean.setConfiguration(new MybatisPlusConfiguration()); return dataSource; } -
依赖冲突:检查是否存在多个日志框架冲突
5.2 日志输出过多导致性能问题
在高并发场景下,过多的SQL日志输出会影响性能。解决方案:
-
使用条件日志输出
java复制if (logger.isDebugEnabled()) { logger.debug("SQL: {}", sql); } -
采样记录:每100条SQL只记录1条
java复制private static final AtomicLong counter = new AtomicLong(); public void debug(String s) { if (counter.incrementAndGet() % 100 == 0) { super.debug(s); } }
5.3 多数据源下的日志区分
当系统使用多个数据源时,可以通过以下方式区分日志:
-
为每个数据源配置不同的logger name
java复制@Bean public SqlSessionFactory sqlSessionFactory1() { MybatisSqlSessionFactoryBean factoryBean = new MybatisSqlSessionFactoryBean(); factoryBean.setConfigurationProperties( Collections.singletonMap("logImpl", "com.example.DataSource1Logger") ); return factoryBean.getObject(); } -
在日志中标记数据源标识
java复制public class DataSourceAwareLogger extends StdOutImpl { private final String dsName; public DataSourceAwareLogger(String dsName) { this.dsName = dsName; } @Override public void debug(String s) { super.debug("[" + dsName + "] " + s); } }
6. 高级技巧与最佳实践
6.1 动态控制SQL日志开关
通过环境变量或配置中心实现运行时动态开关:
java复制@Configuration
public class DynamicLogConfig {
@Value("${mybatis.log.enabled:false}")
private boolean logEnabled;
@Bean
@ConditionalOnMissingBean
public Log mybatisLog() {
return logEnabled ? new StdOutImpl() : new NoLoggingImpl();
}
}
6.2 SQL日志与审计日志集成
将SQL日志与系统审计日志结合:
java复制public class AuditLogger extends StdOutImpl {
private final AuditService auditService;
@Override
public void debug(String s) {
super.debug(s);
if (s.startsWith("==> Preparing:")) {
auditService.logSql(s.substring(17));
}
}
}
6.3 基于注解的细粒度控制
通过自定义注解实现方法级别的SQL日志控制:
java复制@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SqlLog {
boolean value() default true;
}
// 在切面中处理
@Around("@annotation(sqlLog)")
public Object around(ProceedingJoinPoint joinPoint, SqlLog sqlLog) {
if (sqlLog.value()) {
// 开启SQL日志
}
// ...
}
6.4 日志收集与分析
将SQL日志接入ELK等日志分析系统,可以进行:
- 慢SQL统计分析
- SQL执行频率统计
- 参数分布分析
配置示例(Logstash):
conf复制filter {
grok {
match => { "message" => "==> Preparing: %{GREEDYDATA:sql}" }
}
grok {
match => { "message" => "==> Parameters: %{GREEDYDATA:params}" }
}
}
7. 性能考量与优化建议
-
日志异步化:对于高频SQL操作,建议采用异步日志输出
java复制@Bean public Executor sqlLogExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(2); executor.setMaxPoolSize(5); executor.setQueueCapacity(1000); executor.setThreadNamePrefix("sql-log-"); return executor; } -
精简日志内容:生产环境可以只记录SQL模板而不记录参数
properties复制mybatis-plus.configuration.log-sql-template-only=true -
采样记录:对于高频查询,可以按比例采样记录
java复制private static final Random random = new Random(); public void debug(String s) { if (random.nextInt(100) < 5) { // 5%采样率 super.debug(s); } } -
日志分级存储:将SQL日志与业务日志分开存储,便于专项分析
8. 与其他日志组件的整合
8.1 与Log4j2整合
xml复制<!-- log4j2.xml配置 -->
<Loggers>
<Logger name="com.baomidou.mybatisplus" level="debug" additivity="false">
<AppenderRef ref="SQLAppender"/>
</Logger>
</Loggers>
8.2 与Logback的MDC整合
利用MDC实现请求级别的SQL日志追踪:
java复制public class MdcLogger extends StdOutImpl {
@Override
public void debug(String s) {
super.debug("[traceId:" + MDC.get("traceId") + "] " + s);
}
}
8.3 与Micrometer监控整合
将SQL执行指标输出到监控系统:
java复制public class MetricsLogger extends StdOutImpl {
private final MeterRegistry meterRegistry;
@Override
public void debug(String s) {
if (s.startsWith("Time:")) {
long time = Long.parseLong(s.substring(6, s.indexOf(" ms")));
meterRegistry.timer("sql.execute.time").record(time, TimeUnit.MILLISECONDS);
}
}
}
9. 安全注意事项
-
生产环境敏感信息防护:
- 避免记录包含密码、密钥等敏感参数的SQL
- 对敏感字段进行自动脱敏处理
-
SQL注入风险提示:
- 日志中记录的参数化SQL不应直接拼接执行
- 确保日志系统有足够的访问控制
-
日志文件权限控制:
- SQL日志文件应设置严格的访问权限
- 定期归档和清理历史日志
-
合规性要求:
- 根据GDPR等法规要求,可能需要匿名化处理个人数据
- 审计日志需要满足不可篡改要求
10. 实际案例:电商系统SQL日志实践
在某电商系统中,我们实施了以下SQL日志策略:
-
开发环境:
- 完整SQL日志输出
- 结合Swagger展示接口对应的SQL
-
预发环境:
- 只记录超过50ms的慢查询
- 每天自动生成SQL执行报告
-
生产环境:
- 采样记录SQL(1%采样率)
- 全量记录支付、订单修改等核心业务SQL
- 实时监控SQL执行异常
关键配置代码:
java复制@Bean
@Profile({"dev", "test"})
public Log devLog() {
return new StdOutImpl();
}
@Bean
@Profile("prod")
public Log prodLog() {
return new SamplingLogger(100); // 1%采样
}
效果评估:
- 开发效率提升40%(快速定位SQL问题)
- 生产环境日志量减少90%
- 慢查询及时发现率提高到95%
