乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 MyBatis概述与快速上手

    • 本章定位
    • MyBatis简介
    • 环境搭建
    • 第一个MyBatis程序
    • SqlSessionFactoryBuilder与openSession重载
    • SqlSessionFactory与SqlSession
    • SqlSession核心方法
    • 不使用 XML 构建 SqlSessionFactory
    • Mapper接口与映射方式
    • Java API 目录结构
  • 第2章 全局配置文件详解

    • 本章定位
    • properties
    • settings
    • typeAliases
    • typeHandlers
    • objectFactory
    • plugins
    • environments
    • transactionManager
    • dataSource
    • databaseIdProvider
    • mappers
    • 日志配置
  • 第3章 SQL映射文件基础

    • 本章定位
    • select
    • insert
    • update
    • delete
    • 参数传递与占位符
    • 主键生成策略
    • resultType
    • resultMap
    • 自动映射详解
    • sql片段
    • SQL 语句构建器
  • 第4章 动态SQL

    • 本章定位
    • if
    • choose、when、otherwise
    • where
    • set
    • foreach
    • trim
    • bind
    • script 元素:在注解映射器中启用动态 SQL
    • _databaseId 与动态 SQL 的多数据库支持
    • 动态 SQL 中插入脚本语言
  • 第5章 结果映射与关联查询

    • 本章定位
    • resultMap详解
    • association
    • collection
    • discriminator
    • N+1查询问题
    • 延迟加载
  • 第6章 MyBatis注解开发

    • 本章定位
    • @Select
    • @Insert
    • @Update
    • @Delete
    • @Param
    • @Options
    • @SelectKey
    • @Results
    • @Result
    • @One
    • @Many
    • @SelectProvider
  • 第7章 缓存与性能优化

    • 本章定位
    • 一级缓存
    • 二级缓存
    • 缓存配置详解
    • 自定义缓存
    • Executor执行器类型
    • 分页插件

日志配置

导学

本节学习目标:

  • 理解 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 可选值与对应实现:

可选值对应实现推荐场景
SLF4JSLF4J 门面,底层可接 Logback、Log4j 2 等生产首选,生态成熟,性能优异
LOG4J2Apache Log4j 2项目已统一使用 Log4j 2 时直接指定
JDK_LOGGINGjava.util.loggingJDK 内置,无第三方依赖,适合极简环境
COMMONS_LOGGINGApache 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 的日志输出遵循从粗到细的四级粒度:

  1. 接口级别:com.flywing.mapper.StudentMapper — 该接口下所有语句的日志汇总
  2. 包级别:com.flywing.mapper — 包下所有 Mapper 的日志
  3. 命名空间级别:与 Mapper XML 的 namespace 对应
  4. 语句级别: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);

当前数据状态:

idnameagemajorscore
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 更加简洁可读。接下来,我们将学习别名的注册方式与内置别名清单。

上一页
mappers