MyBatis 日志与调试技巧:让 SQL 执行过程完全透明
🔍 MyBatis 日志与调试技巧:让 SQL 执行过程完全透明
文章目录
- 🔍 MyBatis 日志与调试技巧:让 SQL 执行过程完全透明
- 🧠 一、MyBatis 日志体系概览
- 💡 MyBatis 日志模块架构
- 📊 日志级别与信息对应关系
- ⚙️ 二、日志工厂配置详解
- 💡 日志工厂配置方式
- 🔧 各日志框架配置示例
- 🔍 三、SQL 打印与参数还原
- 💡 开启完整SQL日志输出
- 🎯 日志输出效果对比
- 📊 四、性能追踪与慢SQL监控
- 💡 慢SQL检测配置
- 🔧 慢SQL监控实现
- 📈 SQL性能监控配置
- 🛠️ 五、实战调试技巧
- 💡 常见问题排查指南
- 🔧 高级调试技巧
- 📊 调试工具对比
- 💡 六、总结与最佳实践
- 📚 日志配置最佳实践
- 🚀 性能与可读性平衡建议
- 🛡️ 生产环境建议
- 🔧 快速调试清单
🧠 一、MyBatis 日志体系概览
💡 MyBatis 日志模块架构
MyBatis 日志核心特点:
- 🔌 插件化架构:支持多种日志框架
- 📝 分级输出:不同级别显示不同信息
- 🔍 参数可视化:支持SQL参数占位符替换
- ⚡ 性能可控:避免日志输出影响性能
📊 日志级别与信息对应关系
日志级别 | 输出信息 | 适用场景 |
---|---|---|
DEBUG | 完整SQL语句、参数、结果 | 开发调试 |
TRACE | 最详细信息,包括缓存操作 | 深度排查 |
INFO | 关键操作日志 | 生产环境 |
WARN | 警告信息 | 问题预警 |
ERROR | 错误信息 | 错误排查 |
⚙️ 二、日志工厂配置详解
💡 日志工厂配置方式
MyBatis 支持多种日志实现,以下是常见配置方式:
1. mybatis-config.xml 配置:
<configuration><settings><!-- 设置日志实现 --><setting name="logImpl" value="SLF4J"/></settings>
</configuration>
2. Spring Boot 配置:
# application.yml
mybatis:configuration:log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
3. 代码配置:
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);org.apache.ibatis.logging.LogFactory.useSlf4jLogging();
🔧 各日志框架配置示例
SLF4J + Logback 配置:
<!-- logback.xml -->
<configuration><!-- MyBatis SQL日志单独配置 --><logger name="com.example.mapper" level="DEBUG" additivity="false"><appender-ref ref="SQL_APPENDER"/></logger><appender name="SQL_APPENDER" class="ch.qos.logback.core.ConsoleAppender"><encoder><pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern></encoder></appender><root level="INFO"><appender-ref ref="SQL_APPENDER"/></root>
</configuration>
Log4J2 配置:
<!-- log4j2.xml -->
<Configuration><Loggers><!-- MyBatis Mapper层日志 --><Logger name="com.example.mapper" level="debug" additivity="false"><AppenderRef ref="SqlConsole"/></Logger><Root level="info"><AppenderRef ref="Console"/></Root></Loggers><Appenders><Console name="SqlConsole" target="SYSTEM_OUT"><PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/></Console></Appenders>
</Configuration>
🔍 三、SQL 打印与参数还原
💡 开启完整SQL日志输出
# application.properties
# 开启MyBatis SQL日志
logging.level.com.example.mapper=DEBUG# 显示SQL参数(Log4J2特有)
logging.level.org.apache.ibatis.transaction.jdbc.JdbcTransaction=DEBUG
logging.level.org.apache.ibatis.executor.BaseExecutor=DEBUG
🎯 日志输出效果对比
默认输出(参数占位符):
DEBUG: ==> Preparing: SELECT * FROM users WHERE id = ? AND status = ?
DEBUG: ==> Parameters: 1(Integer), 1(Integer)
DEBUG: <== Total: 1
参数替换后输出(需要额外配置):
DEBUG: Executing SQL: SELECT * FROM users WHERE id = 1 AND status = 1
DEBUG: Execution time: 45ms
🔧 配置SQL参数美化输出
// 自定义SQL日志拦截器
@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 SqlLogInterceptor implements Interceptor {private static final Logger logger = LoggerFactory.getLogger("SQL_LOGGER");@Overridepublic Object intercept(Invocation invocation) throws Throwable {MappedStatement ms = (MappedStatement) invocation.getArgs()[0];Object parameter = invocation.getArgs()[1];BoundSql boundSql = ms.getBoundSql(parameter);String sql = boundSql.getSql();Object parameterObject = boundSql.getParameterObject();// 美化SQL输出String formattedSql = formatSql(sql, parameterObject);long start = System.currentTimeMillis();try {return invocation.proceed();} finally {long cost = System.currentTimeMillis() - start;logger.debug("SQL: {} | Time: {}ms", formattedSql, cost);}}private String formatSql(String sql, Object parameter) {// 简单的参数替换逻辑if (parameter instanceof Map) {Map<?, ?> paramMap = (Map<?, ?>) parameter;for (Map.Entry<?, ?> entry : paramMap.entrySet()) {String key = ":" + entry.getKey();String value = entry.getValue() != null ? entry.getValue().toString() : "NULL";sql = sql.replace(key, "'" + value + "'");}}return sql.replaceAll("\\s+", " ").trim();}
}
📊 四、性能追踪与慢SQL监控
💡 慢SQL检测配置
# Spring Boot Actuator 监控配置
management:endpoints:web:exposure:include: health,metrics,prometheusendpoint:health:show-details: alwaysmetrics:distribution:percentiles:mybatis.sql.timer: 0.5,0.95,0.99# 数据源监控
spring:datasource:hikari:metrics-tracker: true
🔧 慢SQL监控实现
// 慢SQL检测拦截器
@Component
@Slf4j
public class SlowSqlInterceptor implements Interceptor {private static final long SLOW_SQL_THRESHOLD = 1000; // 1秒@Overridepublic Object intercept(Invocation invocation) throws Throwable {long startTime = System.currentTimeMillis();try {return invocation.proceed();} finally {long costTime = System.currentTimeMillis() - startTime;if (costTime > SLOW_SQL_THRESHOLD) {// 记录慢SQLlog.warn("Slow SQL detected: {}ms", costTime);// 获取SQL信息MappedStatement ms = (MappedStatement) invocation.getArgs()[0];Object parameter = invocation.getArgs()[1];BoundSql boundSql = ms.getBoundSql(parameter);log.warn("Slow SQL: {}", boundSql.getSql());log.warn("Parameters: {}", boundSql.getParameterObject());// 发送告警(可选)alertSlowSql(boundSql.getSql(), costTime);}// 记录指标Metrics.timer("sql.execution.time").record(costTime, TimeUnit.MILLISECONDS);}}private void alertSlowSql(String sql, long costTime) {// 集成告警系统,如发送邮件、短信等}
}
📈 SQL性能监控配置
// Micrometer SQL监控
@Configuration
public class MetricsConfig {@Beanpublic MeterRegistryCustomizer<MeterRegistry> metricsCustomizer() {return registry -> {// 监控SQL执行时间Timer.builder("sql.execution.time").description("SQL execution time").register(registry);// 监控SQL错误率Counter.builder("sql.error.count").description("SQL error count").register(registry);};}
}
🛠️ 五、实战调试技巧
💡 常见问题排查指南
问题现象 | 可能原因 | 解决方案 |
---|---|---|
看不到SQL日志 | 日志级别配置错误 | 检查logging.level.com.example.mapper=DEBUG |
参数显示为? | 日志框架不支持参数美化 | 使用自定义拦截器或P6Spy |
日志输出太杂乱 | 日志范围太广 | 精确配置Mapper包路径 |
性能开销大 | 日志级别过高 | 生产环境使用INFO级别 |
🔧 高级调试技巧
1. 使用P6Spy进行SQL监控:
# 使用P6Spy数据源
spring:datasource:url: jdbc:p6spy:mysql://localhost:3306/testdriver-class-name: com.p6spy.engine.spy.P6SpyDriver# p6spy.properties
modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
logMessageFormat=com.p6spy.engine.spy.appender.CustomLineFormat
customLogMessageFormat=%(currentTime)|%(executionTime)|%(category)|%(sql)
- MyBatis原生参数打印:
<!-- 在mybatis-config.xml中开启详细日志 -->
<settings><setting name="logPrefix" value="MYBATIS_DEBUG."/>
</settings>
- 动态调整日志级别:
// 运行时动态调整日志级别
@RestController
public class LogLevelController {@PostMapping("/admin/log-level")public String changeLogLevel(@RequestParam String level) {LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();// 修改MyBatis Mapper的日志级别loggerContext.getLogger("com.example.mapper").setLevel(Level.valueOf(level));return "Log level changed to: " + level;}
}
📊 调试工具对比
工具 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
MyBatis内置日志 | 无需额外依赖 | 功能有限 | 简单调试 |
P6Spy | 功能强大,支持格式化 | 需要更改数据源配置 | 深度调试 |
自定义拦截器 | 完全可控,定制性强 | 需要开发工作量 | 特定需求 |
APM工具 | 全链路监控,生产级 | 需要额外部署 | 生产环境 |
💡 六、总结与最佳实践
📚 日志配置最佳实践
开发环境配置:
# application-dev.yml
logging:level:com.example.mapper: DEBUGorg.apache.ibatis: TRACEpattern:console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
生产环境配置:
# application-prod.yml
logging:level:com.example.mapper: WARNorg.apache.ibatis: ERRORfile:path: /app/logsname: /app/logs/app.log
🚀 性能与可读性平衡建议
🛡️ 生产环境建议
- 避免过度日志:生产环境使用WARN级别,避免性能影响
- 日志分离:将SQL日志单独输出到文件,便于分析
- 监控告警:设置慢SQL告警阈值(如:>1秒)
- 定期审计:定期分析SQL日志,优化性能瓶颈
🔧 快速调试清单
- ✅ 检查日志级别配置
- ✅ 确认Mapper包路径正确
- ✅ 验证日志框架依赖
- ✅ 测试参数显示是否正常
- ✅ 确认慢SQL监控生效