日志配置
导学
本节学习目标:
- 理解 MyBatis 内置日志工厂的自动发现机制与优先级策略
- 掌握通过
settings手动指定日志实现的方法 - 能够独立完成 SLF4J + Logback 的整合配置
- 理解日志粒度(接口 → 包 → 命名空间 → 语句)与日志级别(DEBUG / TRACE)的协作关系
- 能够在开发与生产环境配置差异化的日志策略,并快速排查"日志不输出""SQL 看不到"等常见问题
定义
MyBatis 在运行过程中需要记录大量内部状态:SQL 语句的生成与执行、参数绑定、结果集映射、缓存命中与失效、事务提交与回滚等。这些信息的输出依赖于日志框架。MyBatis 本身不提供日志实现,而是通过**日志工厂(LogFactory)**自动适配项目中已有的日志框架,将内部事件转换为开发者可读的日志内容。
合理的日志配置是日常开发与线上排障的"显微镜":开发时打开 SQL DEBUG 输出,可实时验证语句是否符合预期;生产时调高级别,可避免 I/O 与磁盘开销。
适用位置与核心属性
日志实现通过 mybatis-config.xml 中 settings 元素的 logImpl 属性指定。
<settings>
<setting name="logImpl" value="SLF4J"/>
</settings>
| 属性名 | 默认值 | 说明 |
|---|---|---|
logImpl | 自动探测 | 显式指定日志实现类,覆盖自动发现逻辑 |
logImpl 可选值与对应实现:
| 可选值 | 对应实现 | 推荐场景 |
|---|---|---|
SLF4J | SLF4J 门面,底层可接 Logback、Log4j 2 等 | 生产首选,生态成熟,性能优异 |
LOG4J2 | Apache Log4j 2 | 项目已统一使用 Log4j 2 时直接指定 |
JDK_LOGGING | java.util.logging | JDK 内置,无第三方依赖,适合极简环境 |
COMMONS_LOGGING | Apache Commons Logging | 老旧项目兼容 |
STDOUT_LOGGING | 直接输出到 System.out | 临时调试,无外部框架时快速验证 |
NO_LOGGING | 关闭所有日志 | 性能测试或完全禁止日志输出的特殊场景 |
若
logImpl未显式配置,MyBatis 将按固定优先级自动探测 classpath 中存在的日志框架。
核心原理
日志工厂自动发现优先级
MyBatis 启动时,LogFactory 会依次尝试加载以下日志实现,一旦成功即停止探测:
关键源码逻辑:org.apache.ibatis.logging.LogFactory 在静态代码块中依次调用 useSlf4jLogging()、useCommonsLogging()、useLog4J2Logging()、useJdkLogging()、useStdOutLogging()。每个方法通过 Class.forName() 探测对应门面或实现类是否存在。若 logImpl 被显式设置,则直接调用 setImplementation() 跳过自动探测。
日志粒度与级别
MyBatis 的日志输出遵循从粗到细的四级粒度:
- 接口级别:
com.flywing.mapper.StudentMapper— 该接口下所有语句的日志汇总 - 包级别:
com.flywing.mapper— 包下所有 Mapper 的日志 - 命名空间级别:与 Mapper XML 的
namespace对应 - 语句级别:
com.flywing.mapper.StudentMapper.findById— 单条 SQL 语句的日志
在 Logback 等实现中,通过 <logger> 标签的 name 属性即可控制不同粒度的日志开关。
MyBatis 内部约定:
- DEBUG 级别:输出 SQL 语句(
Preparing:)、参数(Parameters:)、执行耗时等 - TRACE 级别:输出结果集详情(每行数据的列值映射过程)
因此,日常开发建议将 Mapper 命名空间设为 DEBUG;若需要查看结果集映射细节,则临时调整为 TRACE。
完整示例
场景说明
乐途公司学员管理系统使用 MySQL 5.7 + MyBatis 3.5.x。开发团队希望在本地开发环境完整打印 SQL 与参数,便于调试;在生产环境仅保留 WARN 及以上级别,避免日志膨胀。本节演示完整的 SLF4J + Logback 整合配置。
操作前的状态
沿用 student 表:
CREATE TABLE student (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20),
age INT,
major VARCHAR(20),
score DECIMAL(5,2)
);
INSERT INTO student (name, age, major, score) VALUES
('大翔', 22, '计算机科学', 95.5),
('白歌', 21, '软件工程', 88.0),
('小崔', 20, '计算机科学', 92.0),
('黄俪', 21, '信息安全', 90.5),
('李眉', 22, '软件工程', 87.0);
当前数据状态:
| id | name | age | major | score |
|---|---|---|---|---|
| 1 | 大翔 | 22 | 计算机科学 | 95.50 |
| 2 | 白歌 | 21 | 软件工程 | 88.00 |
| 3 | 小崔 | 20 | 计算机科学 | 92.00 |
| 4 | 黄俪 | 21 | 信息安全 | 90.50 |
| 5 | 李眉 | 22 | 软件工程 | 87.00 |
完整配置代码
1. mybatis-config.xml(显式指定 SLF4J)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="db.properties"/>
<settings>
<!-- 显式指定日志实现为 SLF4J,覆盖自动探测 -->
<setting name="logImpl" value="SLF4J"/>
<!-- 开启驼峰自动映射,简化查询结果处理 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/StudentMapper.xml"/>
</mappers>
</configuration>
2. logback.xml(开发环境配置)
将文件置于 src/main/resources 下:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 控制台输出 appender -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 根日志级别为 INFO,输出到控制台 -->
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
<!--
MyBatis Mapper 命名空间级别日志:DEBUG
输出 SQL 语句与参数
-->
<logger name="com.flywing.mapper.StudentMapper" level="DEBUG" additivity="false">
<appender-ref ref="CONSOLE"/>
</logger>
<!--
若希望查看结果集映射细节,可临时开启 TRACE
<logger name="com.flywing.mapper.StudentMapper" level="TRACE" additivity="false">
<appender-ref ref="CONSOLE"/>
</logger>
-->
</configuration>
3. 生产环境差异化配置(logback-prod.xml)
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/application.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/application.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 生产环境根级别设为 WARN,减少常规输出 -->
<root level="WARN">
<appender-ref ref="FILE"/>
</root>
<!-- 生产环境关闭 MyBatis SQL 明细,仅保留 ERROR -->
<logger name="com.flywing.mapper" level="ERROR" additivity="false">
<appender-ref ref="FILE"/>
</logger>
</configuration>
4. Mapper 接口与映射文件
package com.flywing.mapper;
import com.flywing.entity.Student;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface StudentMapper {
Student findById(Integer id);
List<Student> findByMajor(@Param("major") String major);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.flywing.mapper.StudentMapper">
<select id="findById" resultType="com.flywing.entity.Student">
SELECT id, name, age, major, score
FROM student
WHERE id = #{id}
</select>
<select id="findByMajor" resultType="com.flywing.entity.Student">
SELECT id, name, age, major, score
FROM student
WHERE major = #{major}
</select>
</mapper>
5. Java 测试代码
package com.flywing.test;
import com.flywing.entity.Student;
import com.flywing.mapper.StudentMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.InputStream;
import java.util.List;
public class LoggingDemo {
public static void main(String[] args) throws Exception {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
try (SqlSession session = factory.openSession()) {
StudentMapper mapper = session.getMapper(StudentMapper.class);
// 场景一:按 ID 查询
Student s = mapper.findById(1);
System.out.println("查询结果:" + s.getName() + ",分数:" + s.getScore());
// 场景二:按专业查询
List<Student> list = mapper.findByMajor("计算机科学");
System.out.println("计算机科学专业共 " + list.size() + " 人");
}
}
}
实际效果/结果
控制台输出(开发环境,DEBUG 级别)
2024-01-15 10:23:45.123 [main] DEBUG com.flywing.mapper.StudentMapper.findById - ==> Preparing: SELECT id, name, age, major, score FROM student WHERE id = ?
2024-01-15 10:23:45.145 [main] DEBUG com.flywing.mapper.StudentMapper.findById - ==> Parameters: 1(Integer)
2024-01-15 10:23:45.167 [main] DEBUG com.flywing.mapper.StudentMapper.findById - <== Total: 1
查询结果:大翔,分数:95.5
2024-01-15 10:23:45.168 [main] DEBUG com.flywing.mapper.StudentMapper.findByMajor - ==> Preparing: SELECT id, name, age, major, score FROM student WHERE major = ?
2024-01-15 10:23:45.169 [main] DEBUG com.flywing.mapper.StudentMapper.findByMajor - ==> Parameters: 计算机科学(String)
2024-01-15 10:23:45.170 [main] DEBUG com.flywing.mapper.StudentMapper.findByMajor - <== Total: 2
计算机科学专业共 2 人
效果验证:
Preparing:行显示最终发送到 MySQL 5.7 的预编译 SQL 语句Parameters:行显示实际绑定的参数值与类型Total:行显示返回结果集行数- 若将
logback.xml中com.flywing.mapper.StudentMapper的级别改为TRACE,还会输出每行结果列值的映射过程(如Columns: id=1, name=大翔 ...)
易错场景/常见误区
| 误区 | 错误表现 | 正解 |
|---|---|---|
| 日志不输出,控制台一片空白 | 误以为 MyBatis 没有日志功能 | 首先检查 classpath 是否存在日志实现(如 logback-classic);其次检查 logImpl 是否写错大小写(应为 SLF4J 而非 slf4j);最后检查 logger 的 name 是否与 Mapper 命名空间完全一致 |
| SQL 语句看不到,只有业务日志 | 日志框架配置中 logger 名称写错 | MyBatis 的 logger name 等于 Mapper 接口全限定名(如 com.flywing.mapper.StudentMapper),而非 org.apache.ibatis;确保 <logger> 的 name 与 namespace 一致 |
| 日志级别配错为 INFO | 只能看到 Total: 行,看不到 Preparing: 和 Parameters: | SQL 语句与参数属于 DEBUG 级别,结果集详情属于 TRACE 级别;开发环境至少设为 DEBUG |
| 同时引入多个日志实现导致冲突 | 控制台出现 "Multiple SLF4J bindings" 或日志格式混乱 | 通过 mvn dependency:tree 排查,只保留一个日志实现(如仅保留 logback-classic,排除 log4j-slf4j-impl) |
| 生产环境未关闭 DEBUG 日志 | 磁盘迅速打满,I/O 飙升影响数据库查询性能 | 生产环境将 Mapper 包级别日志设为 WARN/ERROR,或完全关闭;通过 -Dlogback.configurationFile=logback-prod.xml 指定生产配置 |
使用 STDOUT_LOGGING 上线 | 日志无法归档、无法分级、无法异步化 | STDOUT_LOGGING 仅用于临时单元测试;生产环境务必使用 SLF4J + Logback/Log4j 2 等成熟方案 |
面试考点
Q1:MyBatis 日志工厂的自动发现优先级是什么?如果项目中同时存在 SLF4J 和 Log4j 2,最终会使用哪一个?
A:优先级为:SLF4J → Apache Commons Logging → Log4j 2 → JDK logging → STDOUT_LOGGING。若同时存在 SLF4J 和 Log4j 2,由于 SLF4J 排在第一位,MyBatis 会优先使用 SLF4J。即使 Log4j 2 通过
log4j-slf4j-impl桥接到了 SLF4J,最终走的仍是 SLF4J 门面,不影响这一选择结果。
Q2:logImpl 设置为 SLF4J 后,是否还需要在项目中引入具体的日志实现(如 logback-classic)?
A:需要。SLF4J 是日志门面(Facade),本身不提供输出能力,必须在运行时 classpath 中存在一个绑定实现(如
logback-classic、slf4j-log4j12、log4j-slf4j-impl等)。若只有slf4j-api而没有绑定,MyBatis 初始化时会因找不到实现而回退到NO_LOGGING,导致日志静默失效。
Q3:为什么 MyBatis 的 SQL 语句日志是 DEBUG 级别,而结果集日志是 TRACE 级别?
A:这是 MyBatis 内部的约定设计。SQL 语句(
Preparing:)和参数(Parameters:)是日常调试中最常关注的信息,频率适中,设为 DEBUG;结果集逐行映射(TRACE级别下的列值输出)数据量极大,频繁输出会淹没日志并带来性能开销,因此设为更细的 TRACE,仅在排查映射问题时临时开启。
Q4:开发环境正常输出 SQL,但打包到生产服务器后日志消失,可能的原因有哪些?
A:常见原因有三:① 生产环境的
logback.xml(或对应配置)中 logger 级别被设为 INFO 及以上,过滤了 DEBUG 输出;② 生产包通过 Maven Profile 排除了logback.xml,导致日志框架使用默认配置;③ 生产环境引入了多个 SLF4J 绑定,导致冲突静默失败;④mybatis-config.xml中logImpl写错或该文件未被正确打包到 classpath。
小结
日志配置是 MyBatis 项目中最容易被忽视、却最能提升排障效率的环节。理解 LogFactory 的自动发现优先级、掌握 logImpl 的手动指定方式、熟练搭配 SLF4J + Logback 的级别与粒度控制,是区分"能用 MyBatis"与"用好 MyBatis"的关键标志。开发环境保持 DEBUG 输出 SQL 与参数,生产环境收紧级别并归档到文件,是业界通行的最佳实践。
下一章引子
全局配置中的 settings 与日志已就绪,但 MyBatis 映射文件中频繁出现的全限定类名(如 com.flywing.entity.Student)仍显冗长。MyBatis 提供了 typeAliases 机制,允许为 Java 类型注册简短别名,让 XML 更加简洁可读。接下来,我们将学习别名的注册方式与内置别名清单。