Mybatis日志源码探究

发布时间:2026/10/21 10:35:02
Mybatis日志源码探究 源码仓库Gitee · GitHub一.项目搭建1.pom.xmldependencies dependency groupIdlog4j/groupId artifactIdlog4j/artifactId version1.2.12/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-context/artifactId version4.3.20.RELEASE/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-core/artifactId version4.3.20.RELEASE/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-beans/artifactId version4.3.20.RELEASE/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version3.4.6/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version1.3.2/version /dependency !-- spring连接池 -- dependency groupIdorg.springframework/groupId artifactIdspring-jdbc/artifactId version4.3.20.RELEASE/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version5.1.30/version /dependency /dependencies2.配置类import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.jdbc.datasource.DriverManagerDataSource; import javax.sql.DataSource; Configuration ComponentScan(com.hrh.mybatis) MapperScan(com.hrh.mybatis.mapper) public class MyBatisConfig { Bean public SqlSessionFactoryBean sqlSessionFactory() throws Exception { SqlSessionFactoryBean factoryBean new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource()); factoryBean.setConfigLocation(new DefaultResourceLoader().getResource(classpath:mybatis-config.xml));//内置日志工厂配置 return factoryBean; } Bean public DataSource dataSource(){ DriverManagerDataSource driverManagerDataSource new DriverManagerDataSource(); driverManagerDataSource.setDriverClassName(com.mysql.jdbc.Driver); driverManagerDataSource.setUsername(xxx); driverManagerDataSource.setPassword(xxx); driverManagerDataSource.setUrl(jdbc:mysql://localhost:3306/xxx?useUnicodetruecharacterEncodingutf-8autoReconnecttrue); return driverManagerDataSource; } }3.pojo类、dao类和service类import org.springframework.stereotype.Repository; Repository public class Person { private int id; private String name; private int age; public int getId() { return id; } public void setId(int id) { this.id id; } public String getName() { return name; } public void setName(String name) { this.name name; } public int getAge() { return age; } public void setAge(int age) { this.age age; } Override public String toString() { return Person{ id id , name name \ , age age }; } }import com.hrh.mybatis.bean.Person; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; import java.util.List; Mapper public interface PersonMapper { Select(select *from tab_person;) ListPerson list(); }import com.hrh.mybatis.bean.Person; import com.hrh.mybatis.mapper.PersonMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; Service public class PersonService { Autowired PersonMapper personMapper; public ListPerson getList() { return personMapper.list(); } }4.测试类public static void main(String[] args) throws Exception { AnnotationConfigApplicationContext context new AnnotationConfigApplicationContext(MyBatisConfig.class); PersonService bean context.getBean(PersonService.class); ListPerson list bean.getList(); for (Person p : list) { System.out.println(p.toString()); } }二.日志输出以下内容是参考官网实现的mybatis – MyBatis 3 | Logging、mybatis – MyBatis 3 | 日志1.使用Mybatis的内置日志工厂输出Mybatis 通过使用内置的日志工厂提供日志功能。内置日志工厂将会把日志工作委托给下面的实现之一SLF4JApache Commons LoggingLog4j 2Log4jJDK loggingMyBatis 内置日志工厂会基于运行时检测信息选择日志委托实现。它会按上面罗列的顺序使用第一个查找到的实现。当没有找到这些实现时将会禁用日志功能。不少应用服务器如 Tomcat 和 WebShpere的类路径中已经包含 Commons Logging。注意在这种配置环境下MyBatis 会把 Commons Logging 作为日志工具。这就意味着在诸如 WebSphere 的环境中由于提供了 Commons Logging 的私有实现你的 Log4J 配置将被忽略。这个时候你就会感觉很郁闷看起来 MyBatis 将你的 Log4J 配置忽略掉了其实是因为在这种配置环境下MyBatis 使用了 Commons Logging 作为日志实现。如果你的应用部署在一个类路径已经包含 Commons Logging 的环境中而你又想使用其它日志实现你可以通过在 MyBatis 配置文件 mybatis-config.xml 里面添加一项 setting 来选择其它日志实现。可选的值有SLF4J、LOG4J、LOG4J2、JDK_LOGGING、COMMONS_LOGGING、STDOUT_LOGGING、NO_LOGGING或者是实现了 org.apache.ibatis.logging.Log 接口且构造方法以字符串为参数的类完全限定名。mybatis-config.xml ?xml version1.0 encodingUTF-8 ? !DOCTYPE configuration PUBLIC -//mybatis.org//DTD Config 3.0//EN http://mybatis.org/dtd/mybatis-3-config.dtd configuration settings !-- 打印sql日志 -- setting namelogImpl valueSTDOUT_LOGGING / /settings /configuration在 MyBatisConfig 配置类中或xml中添加 mybatis-config.xml 配置Bean public SqlSessionFactoryBean sqlSessionFactory() throws Exception { SqlSessionFactoryBean factoryBean new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource()); factoryBean.setConfigLocation(new DefaultResourceLoader().getResource(classpath:mybatis-config.xml));//内置日志工厂配置 return factoryBean; }bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property nameconfigLocation valueclasspath:mybatis-config.xml/property /beanPS经测试测试背景有 log4j、logback其实不用 mybatis-config.xml 这个配置也可以实现 log4j 或 logback 打印出 SQL 日志2.在实现方法中调用指定日志你应该在调用其它 MyBatis 方法之前调用以上的某个方法。另外仅当运行时类路径中存在该日志实现时日志实现的切换才会生效。如果你的环境中并不存在 Log4J你却试图调用了相应的方法MyBatis 就会忽略这一切换请求并将以默认的查找顺序决定使用的日志实现。org.apache.ibatis.logging.LogFactory.useSlf4jLogging(); org.apache.ibatis.logging.LogFactory.useLog4JLogging(); org.apache.ibatis.logging.LogFactory.useLog4J2Logging(); org.apache.ibatis.logging.LogFactory.useJdkLogging(); org.apache.ibatis.logging.LogFactory.useCommonsLogging(); org.apache.ibatis.logging.LogFactory.useStdOutLogging();PS经测试测试背景有 log4j、log4j2、logback 等发现org.apache.ibatis.logging.LogFactory.useXXXX 语句无效无法实现指定日志打印出 SQL3.log4j的其他设置当你想打印 SQL 的详细信息比如执行查询时控制台输出包括查询语句、字段名称、参数、每条数据都打印出来和总数时可以在 log4j.properties 配置文件中添加如下配置log4j.logger.com.hrh.mybatis.mapper.ManagerMapperTRACE其中 com.hrh.mybatis.mapper.ManagerMapper 是 ManagerMapper 接口的详细路径对应XML的命名空间当配置完上面的信息执行 ManagerMapper 接口的方法时会输出详细的信息也可以只针对特定方法进行详细输出配置是log4j.logger.com.hrh.mybatis.mapper.ManagerMapper.listTRACE以此类推当配置下面信息时会对 mapper 包下的所有类进行详细输出log4j.logger.com.hrh.mybatis.mapperTRACE某些查询可能会返回庞大的结果集。这时你可能只想查看 SQL 语句而忽略返回的结果集。为此SQL 语句将会在 DEBUG 日志级别下记录JDK 日志则为 FINE。返回的结果集则会在 TRACE 日志级别下记录JDK 日志则为 FINER)。因此只要将日志级别调整为 DEBUG 即可log4j.logger.com.hrh.mybatis.mapper.ManagerMapperDEBUG三.源码探究我们从依赖 mybatis-3.4.6.jar 的logging包可以看出它提供了很多日志框架依赖的实现类那么它是怎么确定使用哪种日志呢我们可以从 LogFactory 这个类来探究下它的实现原理。从 LogFactory 类我们可以看到它有一些静态代码这表示当 Spring Framework 加载这个类时会执行这些静态代码一个个按顺序执行同样这些静态代码验证了前面日志输出的第一点讲到了使用Mybatis的内置日志工厂输出时的日志查找顺序Mybatis 默认使用 Slf4jpublic final class LogFactory { /** * Marker to be used by logging implementations that support markers */ public static final String MARKER MYBATIS; private static Constructor? extends Log logConstructor;//全局变量重要 static { tryImplementation(new Runnable() { Override public void run() { useSlf4jLogging(); } }); tryImplementation(new Runnable() { Override public void run() { useCommonsLogging(); } }); tryImplementation(new Runnable() { Override public void run() { useLog4J2Logging(); } }); tryImplementation(new Runnable() { Override public void run() { useLog4JLogging(); } }); tryImplementation(new Runnable() { Override public void run() { useJdkLogging(); } }); tryImplementation(new Runnable() { Override public void run() { useNoLogging(); } }); } ......... }下面我们看 tryImplementation 方法第一次进来肯定等于空表示 runnable 会执行 run 方法后面会执行 useXXLogging() 方法当执行 useXXLogging() 方法后 logConstructor 变量就不等于空了后面的tryImplementation 不会再执行 run 方法了private static void tryImplementation(Runnable runnable) { if (logConstructor null) { try { runnable.run(); } catch (Throwable t) { // ignore } } }接下来我们看看 useXXLogging() 方法从中我们可以看到它是给 setImplementation 方法传递了一个日志实现类的class对象然后通过获取 class 的构造方法再通过构造方法创建出日志实现类的实例给 logConstructor 这个全局变量如果创建实例失败则抛出异常然后再执行后面的 tryImplementation 方法后面通过 LogFactory#getLog() 方法就可以得到 Log 对象private static void setImplementation(Class? extends Log implClass) { try { Constructor? extends Log candidate implClass.getConstructor(String.class); Log log candidate.newInstance(LogFactory.class.getName()); if (log.isDebugEnabled()) {//判断是否允许输出debug信息jul永远为false不是debug级别jul的配置不能修改当日志是log4j时则可在配置文件中定义日志级别 log.debug(Logging initialized using implClass adapter.); } logConstructor candidate; } catch (Throwable t) { throw new LogException(Error setting Log implementation. Cause: t, t); } }LogFactory#getLog() public static Log getLog(Class? aClass) { return getLog(aClass.getName()); } public static Log getLog(String logger) { try { return logConstructor.newInstance(logger); } catch (Throwable t) { throw new LogException(Error creating logger for logger logger . Cause: t, t); } }当如果项目依赖了 log4j这时我们通过 debug 可以得知项目是经过 useCommonsLogging() 加载了 jcl (commons-logging.jar从前文Spring笔记(10) - 日志体系可以得知 Spring Framework 是包含jcl日志的案例项目中使用的是 Spring Framework 4.xSpring Framework 5.x 不支持 Log4j。因此 MyBatisConfig#sqlSessionFactory() 中的 SqlSessionFactoryBean 的 logger 对象是public class SqlSessionFactoryBean implements FactoryBeanSqlSessionFactory, InitializingBean, ApplicationListenerApplicationEvent { private static final Log LOGGER LogFactory.getLog(SqlSessionFactoryBean.class); }Spring Framework 4.x 的日志框架默认为 jul其中 log4jjul所以此时控制台打印的是 log4j 日志框架的日志信息。当然我们可以通过mybatis-config.xml 配置文件对 logConstructor 变量进行再赋值比如配置信息是 setting namelogImpl valueLOG4J /而且配置文件也加载到了项目中这时虽然 logConstructor 的值因Spring Framework 加载而是 jul但当加载执行到 MyBatisConfig#sqlSessionFactory() 的factoryBean.setConfigLocation(new DefaultResourceLoader().getResource(classpath:mybatis-config.xml));语句时会重新进入 setImplementation 给 logConstructor 重新赋值如下图所示当然你同样可以通过如下配置来实现指定日志框架推荐使用Bean public SqlSessionFactoryBean sqlSessionFactory() throws Exception { SqlSessionFactoryBean factoryBean new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource()); org.apache.ibatis.session.Configuration configuration new org.apache.ibatis.session.Configuration(); configuration.setLogImpl(Log4jImpl.class); factoryBean.setConfiguration(configuration); return factoryBean; }前文的 isDebugEnabled 链路是log.isDebugEnabled() - org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl#isDebugEnabled() - org.apache.commons.logging.LogAdapter.JavaUtilLog#isDebugEnabled()private static class JavaUtilLog implements Log, Serializable { private String name; private transient java.util.logging.Logger logger; public JavaUtilLog(String name) { this.name name; this.logger java.util.logging.Logger.getLogger(name); } public boolean isDebugEnabled() { return this.logger.isLoggable(java.util.logging.Level.FINE);//FINE是500jul底层默认800500800所以永远是false } }从上面代码可以看出Mybatis 的 jul 包含 JavaUtilLogJavaUtilLog中又包含了 jul且 isDebugEnabled 永远是 false。那么如何来修改使其 debug 下也会打印日志呢可以通过扩展来实现通过实现 org.apache.ibatis.logging.Log 接口重写它的方法然后将接口实现加入到 Spring 容器中package com.hrh.mybatis; import java.util.logging.Logger; public class MyLog implements org.apache.ibatis.logging.Log { Logger log; public MyLog(String name) { this.log java.util.logging.Logger.getLogger(name); } Override public boolean isDebugEnabled() { return true;//设为true } Override public boolean isTraceEnabled() { return false; } Override public void error(String s, Throwable e) { } Override public void error(String s) { } Override public void debug(String s) { log.info(s);//重写debug方法 } Override public void trace(String s) { } Override public void warn(String s) { } }加入到容器中LogFactory.useCustomLogging(MyLog.class); AnnotationConfigApplicationContext context new AnnotationConfigApplicationContext(MyBatisConfig.class); PersonServiceImpl bean context.getBean(PersonServiceImpl.class); ListPerson list bean .getList();容器开始加载代码经过几次 tryImplementation() 方法后 logConstructor 的值为 JakartaCommonsLoggingImpl然后再执行 LogFactory.useCustomLogging(MyLog.class); 这行代码这时 logConstructor 值发生了变化变成了 MyLog 实例对象四.问题探究前面中讲到在实现方法中调用指定日志实现日志切换但却发现失效无法实现这是为什么呢这时我们可以从下图得知由此当代码变为下面时就可以实现日志切换了org.apache.ibatis.logging.LogFactory.useStdOutLogging(); AnnotationConfigApplicationContext context new AnnotationConfigApplicationContext(MyBatisConfig.class); PersonService bean context.getBean(PersonService.class); ListPerson list bean.getList();