1. Spring Boot项目中SQL日志打印的必要性与实现方案
在开发基于Spring Boot的企业级应用时,数据库操作是最核心的功能之一。作为开发者,我们经常需要查看MyBatis或MyBatis-Plus生成的SQL语句及其执行结果,这不仅能帮助我们验证ORM框架是否正确转换了我们的Java代码,更是排查SQL性能问题和业务逻辑错误的关键手段。
我在实际项目中发现,很多团队在开发调试阶段都会遇到这样的困扰:明明Java代码逻辑没有问题,但数据库操作结果却不符合预期。这时候如果能直接看到最终执行的SQL语句和返回结果,问题往往能迎刃而解。特别是在以下场景中,SQL日志打印显得尤为重要:
- 复杂查询的调试:当使用MyBatis的动态SQL或MyBatis-Plus的Wrapper构建复杂查询条件时
- 性能优化:需要分析实际执行的SQL是否符合预期索引
- 事务管理:排查事务未生效或传播行为异常的问题
- 数据一致性检查:验证写入数据库的实际数据值
Spring Boot默认使用Logback作为日志框架,这为我们提供了强大的日志控制能力。通过合理配置Logback,我们可以精确控制SQL日志的输出内容和格式,既满足开发调试需求,又不会在生产环境造成日志泛滥。
2. Logback配置详解与最佳实践
2.1 Logback配置文件的选择与优先级
Spring Boot项目支持多种日志配置方式,我们需要理解它们的加载顺序:
logback-spring.xml(推荐):Spring Boot会优先查找此文件,它支持Spring的Profile特性logback.xml:标准的Logback配置文件,当不存在logback-spring.xml时使用- 应用属性配置:通过
application.properties或application.yml中的logging相关配置
提示:在Spring Boot项目中,始终优先使用
logback-spring.xml而非logback.xml,因为它支持更灵活的Profile-specific配置。
2.2 完整的Logback配置示例
下面是一个经过生产验证的Logback配置模板,它不仅能打印SQL语句,还能输出执行结果和耗时统计:
xml复制<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds">
<!-- 定义日志输出格式 -->
<property name="LOG_PATTERN"
value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{36}) - %msg%n"/>
<!-- 控制台输出 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- MyBatis SQL日志配置 -->
<logger name="com.yourpackage.mapper" level="DEBUG" additivity="false">
<appender-ref ref="CONSOLE"/>
</logger>
<!-- MyBatis-Plus扩展日志 -->
<logger name="com.baomidou.mybatisplus" level="DEBUG"/>
<!-- JDBC实际执行的SQL(需要配合dataSource配置) -->
<logger name="jdbc.sqlonly" level="DEBUG"/>
<logger name="jdbc.sqltiming" level="INFO"/>
<logger name="jdbc.audit" level="WARN"/>
<logger name="jdbc.resultset" level="ERROR"/>
<!-- Spring事务日志 -->
<logger name="org.springframework.jdbc.support.JdbcTransactionManager" level="DEBUG"/>
<!-- 根日志配置 -->
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>
2.3 关键配置解析
-
Mapper接口日志配置:
xml复制<logger name="com.yourpackage.mapper" level="DEBUG" additivity="false"> <appender-ref ref="CONSOLE"/> </logger>name属性必须指向你的Mapper接口所在包additivity="false"防止日志被重复输出DEBUG级别会打印SQL语句和参数,但不会显示结果集
-
JDBC详细日志:
如果需要更底层的SQL日志(包括结果集),可以添加以下依赖:xml复制<dependency> <groupId>org.bgee.log4jdbc-log4j2</groupId> <artifactId>log4jdbc-log4j2-jdbc4.1</artifactId> <version>1.16</version> </dependency>然后在数据源配置中使用
log4jdbc作为驱动前缀:properties复制spring.datasource.driver-class-name=net.sf.log4jdbc.sql.jdbcapi.DriverSpy spring.datasource.url=jdbc:log4jdbc:mysql://localhost:3306/yourdb
3. MyBatis与MyBatis-Plus的专属配置
3.1 MyBatis原生配置方式
除了Logback配置外,MyBatis本身也提供了日志实现的选择。在application.properties中:
properties复制# 使用标准输出打印SQL
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# 或者使用Log4j2
# mybatis.configuration.log-impl=org.apache.ibatis.logging.log4j2.Log4j2Impl
或者在XML配置中:
xml复制<configuration>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
</configuration>
3.2 MyBatis-Plus的增强配置
MyBatis-Plus在MyBatis基础上增加了更多日志输出点:
properties复制# 启用MyBatis-Plus的性能分析插件(开发环境推荐)
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
mybatis-plus.global-config.sql-parser-cache=false
# 显示Banner(启动时打印MyBatis-Plus版本信息)
mybatis-plus.global-config.banner=true
3.3 性能分析与SQL美化插件
对于复杂SQL调试,建议添加MyBatis-Plus的性能分析插件:
java复制@Bean
@Profile({"dev", "test"}) // 只在开发测试环境启用
public PerformanceInterceptor performanceInterceptor() {
PerformanceInterceptor interceptor = new PerformanceInterceptor();
interceptor.setFormat(true); // 格式化SQL
interceptor.setMaxTime(1000); // 超过1秒的SQL会打印警告
return interceptor;
}
4. 生产环境与开发环境的差异化配置
4.1 基于Profile的日志配置
利用Spring的Profile特性,我们可以为不同环境配置不同的日志级别:
xml复制<springProfile name="dev">
<logger name="com.yourpackage.mapper" level="DEBUG"/>
<logger name="jdbc.sqlonly" level="DEBUG"/>
</springProfile>
<springProfile name="prod">
<logger name="com.yourpackage.mapper" level="WARN"/>
<logger name="jdbc.sqlonly" level="OFF"/>
</springProfile>
4.2 敏感信息过滤
在生产环境打印SQL日志时,必须注意敏感信息的保护。可以通过以下方式实现:
- 使用自定义的Logback过滤器:
java复制public class SensitiveDataFilter extends Filter<ILoggingEvent> {
@Override
public FilterReply decide(ILoggingEvent event) {
if (event.getMessage().contains("password")) {
return FilterReply.DENY;
}
return FilterReply.NEUTRAL;
}
}
- 在Logback配置中注册过滤器:
xml复制<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<filter class="com.yourpackage.SensitiveDataFilter"/>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
5. 常见问题排查与解决方案
5.1 SQL日志不显示的常见原因
-
Mapper接口未被扫描:
- 检查
@MapperScan注解是否配置正确 - 确认Mapper接口所在的包与Logback配置中的包名一致
- 检查
-
日志级别设置过高:
- 确保Logback中Mapper包的日志级别为DEBUG
- 检查是否有父Logger覆盖了你的配置
-
MyBatis日志实现冲突:
- 如果同时在MyBatis和Logback中配置了日志,可能会产生冲突
- 建议只使用Logback统一管理所有日志
5.2 日志输出过多导致性能问题
当系统执行大量数据库操作时,DEBUG级别的SQL日志可能会:
- 占用大量磁盘空间
- 增加I/O负载
- 影响应用性能
解决方案:
- 使用异步日志Appender:
xml复制<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="CONSOLE"/>
<queueSize>512</queueSize>
</appender>
- 限制SQL参数值的输出长度:
xml复制<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
<maxLength>1024</maxLength>
</encoder>
5.3 多数据源环境下的日志配置
对于多数据源项目,需要为每个Mapper接口包单独配置Logger:
xml复制<!-- 主数据源Mapper -->
<logger name="com.yourpackage.mapper.primary" level="DEBUG"/>
<!-- 次数据源Mapper -->
<logger name="com.yourpackage.mapper.secondary" level="DEBUG"/>
同时确保每个数据源都配置了正确的日志实现:
java复制@Bean
@Primary
public SqlSessionFactory primarySqlSessionFactory(
@Qualifier("primaryDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
sessionFactory.setConfigurationProperties(Collections.singletonMap(
"logImpl", "SLF4J"));
return sessionFactory.getObject();
}
6. 高级技巧与优化建议
6.1 SQL日志关联追踪
在分布式系统中,我们需要将SQL日志与业务请求关联起来。可以通过MDC(Mapped Diagnostic Context)实现:
- 添加请求拦截器:
java复制public class TraceIdInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
MDC.put("traceId", UUID.randomUUID().toString());
return true;
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
MDC.remove("traceId");
}
}
- 修改日志格式包含traceId:
xml复制<pattern>[%X{traceId}] %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger{36} - %msg%n</pattern>
6.2 SQL执行耗时统计
通过Logback的TurboFilter可以实现SQL耗时统计和告警:
java复制public class SqlCostFilter extends TurboFilter {
@Override
public FilterReply decide(Marker marker, Logger logger, Level level,
String format, Object[] params, Throwable t) {
if (logger.getName().contains("mapper")
&& level == Level.DEBUG
&& format != null
&& format.startsWith("==> Preparing:")) {
long startTime = System.currentTimeMillis();
MDC.put("sqlStartTime", String.valueOf(startTime));
} else if (MDC.get("sqlStartTime") != null
&& format != null
&& format.startsWith("<== Total:")) {
long cost = System.currentTimeMillis() - Long.parseLong(MDC.get("sqlStartTime"));
if (cost > 1000) {
logger.warn("Slow SQL detected: {}ms - {}", cost, format);
}
MDC.remove("sqlStartTime");
}
return FilterReply.NEUTRAL;
}
}
6.3 日志文件归档策略
对于生产环境,建议配置合理的日志归档策略:
xml复制<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/sql.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>logs/sql.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>5GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
在实际项目开发中,我发现合理配置SQL日志能极大提升开发效率。特别是在排查复杂业务问题时,详细的SQL日志往往能提供关键线索。但同时也需要注意,过度详细的日志在生产环境可能会带来性能和安全问题,因此必须做好环境区分和敏感信息过滤。