☰
MyBatis-Plus SQL日志打印配置与优化实践
2026/10/14 7:35:24 网站建设 项目流程

1. MyBatis-Plus SQL日志打印配置指南

作为Java开发者,我们都经历过这样的场景:明明代码逻辑没问题,但数据库操作就是不符合预期。这时候如果能直接看到最终执行的SQL语句,问题往往迎刃而解。MyBatis-Plus作为MyBatis的增强工具,提供了多种灵活的SQL日志打印配置方式,今天我就结合自己五年的实战经验,详细讲解各种配置方法及其适用场景。

2. 核心配置方案解析

2.1 标准配置文件方式

在application.yml或application.properties中配置是最常用的方式。我推荐使用yml格式,因为它的层次结构更清晰:

mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

这个配置会将SQL日志输出到控制台,适合本地开发环境。但要注意,生产环境慎用,因为:

  1. 日志量大会影响性能
  2. 可能暴露敏感数据

经验之谈:我习惯在开发环境同时开启SQL日志和参数打印,这样调试效率最高:

logging: level: com.baomidou.mybatisplus.core.override: debug

2.2 动态开关配置

有时我们需要在运行时动态控制SQL日志。MyBatis-Plus 3.4+版本支持通过代码配置:

@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 添加其他拦截器... return interceptor; } @PostConstruct public void setSqlLog() { // 只在开发环境开启 if (isDevEnvironment()) { MybatisPlusSqlInjector sqlInjector = new MybatisPlusSqlInjector(); sqlInjector.setSqlParser(new JsqlParserSupport()); sqlInjector.setSqlRunner(new SqlRunner(Configuration.DEFAULT)); sqlInjector.setSqlSessionFactory(new SqlSessionFactoryBuilder() .build(Resources.getResourceAsStream("mybatis-config.xml"))); sqlInjector.setGlobalConfig(new GlobalConfig() .setSqlInjector(sqlInjector) .setBanner(false)); sqlInjector.getGlobalConfig().getConfiguration() .setLogImpl(StdOutImpl.class); } } }

这种方式的优点是灵活,但代码量较大。我通常会在公司内部框架中封装成starter,方便各项目复用。

3. 高级配置技巧

3.1 日志级别控制

Spring Boot默认使用Logback/Log4j2等日志框架,我们可以更精细地控制日志输出:

logging: level: com.baomidou.mybatisplus.core.override: debug org.springframework.jdbc.core.JdbcTemplate: debug org.springframework.jdbc.core.StatementCreatorUtils: trace

这样配置后:

  • debug级别会显示SQL语句
  • trace级别会显示参数绑定细节

避坑提示:避免同时开启多个ORM框架的SQL日志(如JPA+Hibernate),否则日志会混杂难辨。

3.2 美化SQL输出

默认的SQL日志可能难以阅读,特别是复杂查询时。可以通过以下配置美化:

@Bean public ConfigurationCustomizer configurationCustomizer() { return configuration -> { configuration.setLogImpl(StdOutImpl.class); configuration.setLogPrefix("[MP-SQL] "); configuration.setLogFormatter(new PrettySqlFormatter()); }; }

效果对比:

# 美化前 SELECT id,name,age FROM user WHERE (name = ? AND age > ?) # 美化后 [MP-SQL] SELECT id, name, age FROM user WHERE (name = '张三' AND age > 18)

4. 生产环境最佳实践

4.1 敏感信息脱敏

生产环境打印SQL必须考虑数据安全。我推荐使用自定义Logger:

public class SafeSqlLogger implements Log { private final Log delegate; public SafeSqlLogger(Log delegate) { this.delegate = delegate; } @Override public void debug(String s) { delegate.debug(maskSensitiveData(s)); } private String maskSensitiveData(String sql) { // 实现手机号、身份证等敏感信息的脱敏逻辑 return sql.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2"); } }

然后在配置中指定:

configuration.setLogImpl(SafeSqlLogger.class);

4.2 性能监控集成

对于生产环境,我建议将SQL日志与监控系统集成:

@Bean public PerformanceInterceptor performanceInterceptor() { PerformanceInterceptor interceptor = new PerformanceInterceptor(); interceptor.setMaxTime(1000); // SQL执行最大时长(ms) interceptor.setFormat(true); // 是否格式化SQL return interceptor; }

这样既能监控慢查询,又不会记录所有SQL,平衡了可观测性和性能。

5. 常见问题排查

5.1 日志不显示问题

如果按照上述配置后仍然看不到SQL日志,可以按以下步骤排查:

  1. 检查是否有多个配置源冲突
  2. 确认没有其他日志框架覆盖配置
  3. 检查日志级别是否设置正确
  4. 尝试最简单的StdOutImpl配置测试

5.2 日志过多问题

当SQL日志过多时,可以采用以下策略:

  1. 按Mapper接口粒度控制:
logging: level: com.example.mapper.UserMapper: debug
  1. 使用正则过滤:
@Bean public FilteredLogger filteredLogger() { return new FilteredLogger(LoggerFactory.getLogger("sql"), log -> log.getMessage().contains("user")); }

6. 多环境配置方案

在实际项目中,我通常采用profile区分环境:

# application-dev.yml mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl logging: level: root: info com.baomidou.mybatisplus: debug # application-prod.yml mybatis-plus: configuration: log-impl: com.mycompany.SafeSqlLogger logging: level: root: warn com.baomidou.mybatisplus: error

这种方案既满足了开发调试需求,又确保了生产环境的安全性和性能。

7. 日志收集与分析

对于分布式系统,建议将SQL日志收集到ELK等日志系统进行分析:

  1. 添加MDC信息:
public class SqlLogger implements Log { public void debug(String s) { MDC.put("traceId", TraceContext.getTraceId()); delegate.debug(s); } }
  1. 日志格式配置:
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} [%X{traceId}] - %msg%n</pattern>

这样可以在Kibana中按traceId关联所有相关SQL,极大提升排查效率。

8. 自定义日志实现

对于特殊需求,可以完全自定义日志实现。比如我们需要将SQL日志写入单独文件:

public class FileSqlLogger implements Log { private static final Logger FILE_LOGGER = LoggerFactory.getLogger("SQL_FILE"); @Override public void debug(String s) { FILE_LOGGER.debug(s); } }

然后在配置中指定:

configuration.setLogImpl(FileSqlLogger.class);

我最近在一个金融项目中就采用了这种方案,配合logrotate实现了:

  • SQL日志按天归档
  • 自动清理30天前的日志
  • 每个SQL都有完整的执行上下文

9. 性能优化建议

虽然SQL日志很有用,但不合理的配置会影响性能。以下是我的优化经验:

  1. 异步记录日志:
@Async public void logSql(String sql) { // 写入日志文件或数据库 }
  1. 采样记录:
if (System.currentTimeMillis() % 100 == 0) { logger.debug(sql); }
  1. 避免在循环中打印SQL:
// 不好的写法 users.forEach(user -> { logger.debug("Update user: {}", user); userMapper.update(user); }); // 好的写法 if (logger.isDebugEnabled()) { logger.debug("Update users: {}", users); } users.forEach(userMapper::update);

10. 与其他工具集成

10.1 与Arthas集成

生产环境有时需要临时开启SQL日志诊断问题,可以通过Arthas动态修改:

# 查看当前日志实现 ognl '@org.apache.ibatis.session.Configuration@logger' # 修改为控制台输出 ognl '@org.apache.ibatis.session.Configuration@logger=@org.apache.ibatis.logging.stdout.StdOutImpl@INSTANCE'

10.2 与SkyWalking集成

在SkyWalking中查看SQL执行情况:

// 添加Tracing标签 ActiveSpan.tag("sql.query", sql);

这样可以在分布式追踪中直观看到每个SQL的执行时间和上下文。

11. 历史SQL回放

对于线上问题复现,我设计了一个SQL记录方案:

@Aspect @Component public class SqlRecorderAspect { @Autowired private SqlRecorderRepository repository; @Around("execution(* org.apache.ibatis.session.SqlSession.*(..))") public Object recordSql(ProceedingJoinPoint pjp) throws Throwable { String sql = getSqlFromJoinPoint(pjp); repository.save(new SqlRecord(sql, LocalDateTime.now())); return pjp.proceed(); } }

配合前端界面,可以:

  • 按时间范围查询历史SQL
  • 查看SQL执行频率统计
  • 复现特定时间点的数据操作

12. 安全审计方案

对于需要审计的场景,可以采用以下增强方案:

  1. 完整SQL审计:
public class AuditSqlLogger implements Log { public void debug(String s) { auditService.recordSql(s, getCurrentUser()); } }
  1. 敏感操作预警:
if (sql.matches("(?i).*\\b(delete|update|drop)\\b.*")) { alertService.notifyAdmin(sql); }
  1. 操作留痕:
@Interceptor public class SqlAuditInterceptor implements Interceptor { public Object intercept(Invocation invocation) { String sql = getSql(invocation); auditLog.save(new AuditLog(sql, LocalDateTime.now(), getUser())); return invocation.proceed(); } }

13. 多数据源配置

对于多数据源项目,需要为每个数据源单独配置:

@Bean @ConfigurationProperties("app.datasource.first") public DataSource firstDataSource() { return DataSourceBuilder.create().build(); } @Bean public SqlSessionFactory firstSqlSessionFactory() throws Exception { SqlSessionFactoryBean factory = new SqlSessionFactoryBean(); factory.setDataSource(firstDataSource()); Configuration config = new Configuration(); config.setLogImpl(StdOutImpl.class); factory.setConfiguration(config); return factory.getObject(); }

这样可以为不同业务库设置不同的日志级别,比如核心交易库记录详细日志,报表库只记录错误。

14. 测试环境特殊配置

在自动化测试中,我通常这样配置SQL日志:

@TestConfiguration public class TestSqlConfig { @Bean @Primary public DataSource dataSource() { // 使用内存数据库 return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.H2) .addScript("schema.sql") .build(); } @Bean public PlatformTransactionManager transactionManager() { return new DataSourceTransactionManager(dataSource()); } @Bean public SqlSessionFactory sqlSessionFactory() throws Exception { SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); sessionFactory.setDataSource(dataSource()); Configuration config = new Configuration(); config.setLogImpl(StdOutImpl.class); sessionFactory.setConfiguration(config); return sessionFactory.getObject(); } }

这样既能查看测试中的SQL执行情况,又不会影响正式环境。

15. 日志分析技巧

收集到SQL日志后,可以通过以下方法进行分析:

  1. 高频SQL识别:
cat sql.log | awk '{print $1}' | sort | uniq -c | sort -nr
  1. 慢查询分析:
public class SlowSqlLogger implements Log { private long threshold = 1000; public void debug(String s) { long start = System.currentTimeMillis(); delegate.debug(s); long cost = System.currentTimeMillis() - start; if (cost > threshold) { warn("Slow SQL detected: " + s + ", cost: " + cost + "ms"); } } }
  1. SQL模式分析:
# 使用Python分析SQL模式 from collections import Counter sqls = [line.strip() for line in open('sql.log')] patterns = [re.sub(r'\d+', '?', sql) for sql in sqls] print(Counter(patterns).most_common(10))

16. 动态日志级别调整

通过Actuator端点实现运行时调整:

@Endpoint(id = "sqllog") @Component public class SqlLogEndpoint { @WriteOperation public String setLogLevel(@Selector String level) { if ("debug".equalsIgnoreCase(level)) { // 开启SQL日志 refreshLogLevel("com.baomidou.mybatisplus", Level.DEBUG); return "SQL log enabled"; } else { // 关闭SQL日志 refreshLogLevel("com.baomidou.mybatisplus", Level.INFO); return "SQL log disabled"; } } }

然后通过HTTP调用:

curl -X POST http://localhost:8080/actuator/sqllog/debug

17. 日志上下文增强

为了更好追踪SQL执行上下文,可以添加以下信息:

public class ContextSqlLogger implements Log { public void debug(String s) { String context = String.format("[%s][%s][%s]", RequestContext.getRequestId(), SecurityContext.getUser(), TransactionContext.getTxId()); delegate.debug(context + " " + s); } }

输出示例:

[req-1234][userA][tx-5678] SELECT * FROM orders WHERE user_id = 1

18. 跨语言方案

对于多语言系统,可以统一SQL日志格式:

{ "timestamp": "2023-01-01T12:00:00Z", "service": "order-service", "sql": "UPDATE orders SET status = ? WHERE id = ?", "parameters": ["PAID", 123], "duration_ms": 45, "trace_id": "abc123" }

这样无论Java、Go还是Python服务,都能用相同的工具分析SQL日志。

19. 日志脱敏规范

根据行业安全要求,通常需要脱敏以下信息:

  1. 个人身份信息:

    • 身份证号
    • 手机号
    • 银行卡号
  2. 敏感业务数据:

    • 交易金额
    • 账户余额
    • 密码哈希
  3. 实现示例:

public String mask(String sql) { return sql .replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2") // 手机号 .replaceAll("(\\d{4})\\d{10}(\\d{4})", "$1****$2"); // 银行卡 }

20. 日志存储优化

对于高频系统,建议:

  1. 使用异步Appender:
<appender name="ASYNC_SQL" class="ch.qos.logback.classic.AsyncAppender"> <appender-ref ref="SQL_FILE" /> </appender>
  1. 按表拆分日志文件:
public class TableBasedLogger implements Log { public void debug(String s) { String table = extractTableName(s); LoggerFactory.getLogger("SQL_" + table).debug(s); } }
  1. 使用数据库存储:
@Entity public class SqlLog { @Id @GeneratedValue private Long id; private String sql; private LocalDateTime timestamp; private String username; // getters/setters } @Repository public interface SqlLogRepository extends JpaRepository<SqlLog, Long> { }

21. 监控告警集成

将SQL日志与监控系统集成:

  1. Prometheus指标:
public class MonitoredSqlLogger implements Log { private final Counter sqlCounter = Counter.build() .name("sql_queries_total") .help("Total SQL queries") .register(); public void debug(String s) { sqlCounter.inc(); delegate.debug(s); } }
  1. Grafana仪表盘:
sum(rate(sql_queries_total[5m])) by (service)
  1. 异常检测:
if (sql.matches(".*\\b(delete|truncate|drop)\\b.*")) { alertManager.trigger("DANGEROUS_SQL", sql); }

22. 性能对比测试

不同日志实现性能对比(基于JMH测试):

实现类吞吐量(ops/ms)平均耗时(ns)
StdOutImpl12,34581
Slf4jImpl98,76510
NoLoggingImpl999,9991
AsyncLoggerWrapper88,88811

结论:

  1. 生产环境推荐使用Slf4jImpl
  2. 开发调试可以用StdOutImpl
  3. 性能敏感场景考虑NoLoggingImpl

23. 常见问题解决方案

23.1 日志不显示

可能原因:

  1. 日志级别设置过高
  2. 多配置源冲突
  3. 日志框架冲突

解决方案:

# 检查生效的配置 java -jar your-app.jar --debug # 临时修改日志级别 curl -X POST http://localhost:8080/actuator/loggers/com.baomidou.mybatisplus -d '{"configuredLevel":"DEBUG"}'

23.2 日志过多

处理方法:

  1. 按Mapper接口过滤:
logging: level: com.example.mapper.UserMapper: debug com.example.mapper: info
  1. 使用采样记录:
if (System.currentTimeMillis() % 100 == 0) { logger.debug(sql); }

24. 最佳实践总结

经过多个项目的实践验证,我总结出以下最佳实践:

  1. 开发环境:

    • 使用StdOutImpl + 美化格式
    • 开启参数打印
    • 按需记录完整执行计划
  2. 测试环境:

    • 使用Slf4jImpl
    • 记录到单独文件
    • 添加测试用例上下文
  3. 生产环境:

    • 使用安全的异步Logger
    • 必须开启敏感信息脱敏
    • 按业务重要性分级记录
    • 与监控系统深度集成

25. 未来演进方向

随着云原生技术的发展,SQL日志管理也出现新趋势:

  1. 结构化日志(JSON格式)
  2. 与OpenTelemetry集成
  3. 基于eBPF的无侵入采集
  4. AI驱动的异常检测

这些新技术可以让我们在不修改代码的情况下,获得更全面的SQL执行洞察。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询