1.3 mybatis打印sql日志
mybatis支持使用多种日志框架来打印sql,包括:slf4j
、commons-logging
、log4j
、log4j2
、jdk logging
、stdout
、no logging
等。因此在打印日志时,我们首要确定自己使用的日志框架是什么,然后进行相应的配置。
对于从本教程刚刚开始学习mybatis的读者,可以在项目中引入log4j的依赖,然后在classpath下新增配置文件log4j.properties
,即可打印出sql,内容如下:
# 设置root logger日志打印级别为INFO,日志输出到STDOUT这个appender中
log4j.rootLogger=DEBUG,STDOUT
# 定义stdout这个STDOUT,其实现类为ConsoleAppender.表示日志输出到控制台中,读者可以使用其他appender,如DailyRollingFileAppender
log4j.appender.STDOUT=org.apache.log4j.ConsoleAppender
log4j.appender.STDOUT.layout=org.apache.log4j.PatternLayout
log4j.appender.STDOUT.layout.ConversionPattern=%d [%t] %-5p %c %x - %m%n
下面的内容,主要针对已经有一定mybatis使用经验,但是被打印sql的问题所困扰的用户。笔者在2.1、2.2、2.3小节中分别列出了log4j、log4j2、loback的配置方式 。但是还是希望读者按照顺序阅读,笔者将会通过深入的讲解,让你彻底掌握这个问题。
在使用mybatis打印sql日志时需要注意的几点,笔者至于让读者彻底掌握不同版本的mybatis如何打印日志。
- 打印sql日志的logger一定要是
debug
级别的(注意这里说的不是root logger’),在其他级别下不论如何配置,mybatis也不会打印sql日志。 - mybatis 3.0.6,3.1.0,3.2.0版本前后打印日志的配置方式是不同的,这也是我们经常在网上照搬一些配置,但是依然打印不了sql的原因。
- 当应用中存在多种日志框架jar包的依赖时,如果没有进行合适的配置,也是无法打印sql的。例如slf4j和commons-logging都是facade设计模式的实现,用于统一各种日志框架,底层依赖于具体的日志框架实现如log4j、logback、log4j2、jdk logging,并且需要引入相应的桥接jar依赖。
- mybatis 版本
>=
3.2.0之后,<settings>
元素中提供了logPrefix
和logImpl
配置项来帮助配置日志框架,这也是笔者建议的mybatis日志打印方式
1 不同版本的mybatis的日志实现
mybatis打印日志是通过org.apache.ibatis.logging.jdbc包下面的ConnectionLogger
、StatementLogger
、PreparedStatementLogger
和ResultSetLogger
进行的。在mybatis 3.0.6,3.1.0,3.2.0之后,这几个类的实现方式各不相同,导致打印sql日志的配置方式也发生了变化。
下面分别介绍不同的mybatis版本中,实现的区别。
1.1 不同版本logger实现的区别
以PreparedStatementLogger为例,不同版本的实现如下所示:
mybatis版本<=3.0.6
3.0.6<mybatis版本<3.2.0
这两个版本之间只发布了3.1.0和3.1.1两个版本,以3.1.1版本为例,PreparedStatementLogger实现如下:
mybatis>=3.2.0
1.2 不同版本的日志打印效果演示
不同版本实现的区别在于logger的名称不同,以log4j为例:
1.2.1 mybatis版本<=3.0.6
logger的名字都以java.sql
为前缀。我们可以按照如下方式配置log4j.properties
# 设置root logger日志打印级别为INFO,日志输出到STDOUT这个appender中
log4j.rootLogger=INFO,STDOUT
# 定义stdout这个STDOUT,其实现类为ConsoleAppender.表示日志输出到控制台中,读者可以使用其他appender,如DailyRollingFileAppender
log4j.appender.STDOUT=org.apache.log4j.ConsoleAppender
log4j.appender.STDOUT.layout=org.apache.log4j.PatternLayout
log4j.appender.STDOUT.layout.ConversionPattern=%d [%t] %-5p %c %x - %m%n
#设置mybatis打印sql日志
log4j.logger.java.sql=DEBUG
#等价于以下四行配置
#log4j.logger.java.sql.Connection=DEBUG
#log4j.logger.java.sql.Statement=DEBUG
#log4j.logger.java.sql.PreparedStatement=DEBUG
#log4j.logger.java.sql.ResultSet=DEBUG
此时打印的日志效果如下所示:
2017-11-27 22:18:58,128 [main] DEBUG java.sql.Connection - ooo Connection Opened
2017-11-27 22:18:58,240 [main] DEBUG java.sql.PreparedStatement - ==> Executing: select id,name,age from user where id= ?
2017-11-27 22:18:58,241 [main] DEBUG java.sql.PreparedStatement - ==> Parameters: 1(Integer)
2017-11-27 22:18:58,270 [main] DEBUG java.sql.ResultSet - <== Columns: id, name, age
2017-11-27 22:18:58,270 [main] DEBUG java.sql.ResultSet - <== Row: 1, tianshouzhi, 26
2017-11-27 22:18:58,272 [main] DEBUG java.sql.Connection - xxx Connection Closed
1.2.2 mybatis>=3.2.0
默认logger的名字为namespace.id
。其中namespace指的是mapper映射文件的namespace属性,id指的是配置的sql的id属性。
此时我们可以按照如下方式配置log4j.properties
# 设置root logger日志打印级别为INFO,日志输出到STDOUT这个appender中
log4j.rootLogger=INFO,STDOUT
# 定义stdout这个STDOUT,其实现类为ConsoleAppender.表示日志输出到控制台中,读者可以使用其他appender,如DailyRollingFileAppender
log4j.appender.STDOUT=org.apache.log4j.ConsoleAppender
log4j.appender.STDOUT.layout=org.apache.log4j.PatternLayout
log4j.appender.STDOUT.layout.ConversionPattern=%d [%t] %-5p %c %x - %m%n
#设置mybatis打印sql日志,其中com.tianshouzhi.mybatis是所有mapper映射文件namespace属性的公共前缀
log4j.logger.com.tianshouzhi.mybatis=DEBUG
这里配置了一个logger,名字为com.tianshouzhi.mybatis,其是所有mapper映射文件namespace属性的公共前缀。
此时打印效果如下:
2017-11-27 22:58:30,816 [main] DEBUG com.tianshouzhi.mybatis.quickstart.mapper.UserMapper.testResultMap - ooo Using Connection [com.mysql.jdbc.JDBC4Connection@5c533a2]
2017-11-27 22:58:30,819 [main] DEBUG com.tianshouzhi.mybatis.quickstart.mapper.UserMapper.testResultMap - ==> Preparing: select id,name,age from user where id= ?
2017-11-27 22:58:30,911 [main] DEBUG com.tianshouzhi.mybatis.quickstart.mapper.UserMapper.testResultMap - ==> Parameters: 1(Integer)
这种方式的好处是,一个sql执行过程中,经历的ConnectionLogger、StatementLogger、PreparedStatementLogger和ResultSetLogger内部在打印日志时,内部实际上引用的都是同一个底层logger实例。而通过namespace.sqlId作为logger的名称,在查看日志时,很容易串联起来一个sql从获取连接–>执行–>结果封装的整个过程。
特别的,mybatis 3.2.0版本中,我们可以在mybatis-config.xml文件中的在settings
元素中可以配置logPrefix
和logImpl
配置项。例如,如果我们的项目中,mapper映射文件的namespace属性值各不相同,此时我们可以为所有的logger的名字加上一个公共的前缀,如:
<settings>
<!--logger公共前缀,value值随意,不过记得加上一个"."-->
<setting name="logPrefix" value="mybatis."/>
</settings>
此时修改log4.properties配置如下:
# 设置root logger日志打印级别为INFO,日志输出到STDOUT这个appender中
log4j.rootLogger=INFO,STDOUT
# 定义stdout这个STDOUT,其实现类为ConsoleAppender.表示日志输出到控制台中,读者可以使用其他appender,如DailyRollingFileAppender
log4j.appender.STDOUT=org.apache.log4j.ConsoleAppender
log4j.appender.STDOUT.layout=org.apache.log4j.PatternLayout
log4j.appender.STDOUT.layout.ConversionPattern=%d [%t] %-5p %c %x - %m%n
# 设置mybatis打印sql日志,注意这里的mybatis与logPrefix相匹配
log4j.logger.mybatis=DEBUG
此时sql的打印效果如下:
2017-11-27 22:59:21,622 [main] DEBUG mybatis.com.tianshouzhi.mybatis.quickstart.mapper.UserMapper.testResultMap - ooo Using Connection [com.mysql.jdbc.JDBC4Connection@66869e50]
2017-11-27 22:59:21,625 [main] DEBUG mybatis.com.tianshouzhi.mybatis.quickstart.mapper.UserMapper.testResultMap - ==> Preparing: select id,name,age from user where id= ?
2017-11-27 22:59:21,676 [main] DEBUG mybatis.com.tianshouzhi.mybatis.quickstart.mapper.UserMapper.testResultMap - ==> Parameters: 1(Integer)
可以看到,所有的logger的名字前面,都有一个mybatis
前缀。
1.2.3 3.0.6<mybatis版本<3.2.0
这两个版本之间,mybatis日志实现处于过度阶段,因此同时兼容以上两种配置。需要注意的是,logImpl和logPrefix从3.2.0版本才开始支持,因此如果3.0.6<mybatis版本<3.2.0,配置这两个属性会报错。
2 不同日志框架的配置
2.1 log4j配置
pom.xml中引入以下依赖
<!--log4j依赖-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!--slf4j依赖-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<!--桥接-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
其中slf4j
和slf4j-log4j12
是用于桥接用的,可以不引入。但是如果想用log4j作为日志框架,且项目中依赖了slf4j,就一定要引入slf4j-log4j12。
log4j.properties
# 设置root logger日志打印级别为INFO,日志输出到STDOUT这个appender中
log4j.rootLogger=INFO,STDOUT
# 定义stdout这个STDOUT,其实现类为ConsoleAppender.表示日志输出到控制台中,读者可以使用其他appender,如DailyRollingFileAppender
log4j.appender.STDOUT=org.apache.log4j.ConsoleAppender
log4j.appender.STDOUT.layout=org.apache.log4j.PatternLayout
log4j.appender.STDOUT.layout.ConversionPattern=%d [%t] %-5p %c %x - %m%n
# 设置mybatis打印sql日志############## mybatis<=3.0.6 ###############
# logger名称以java.sql为前缀
log4j.logger.java.sql=DEBUG
################### 3.0.6<mybatis<3.2.0 #######################
# 1、兼容3.0.6之间的配置
# 2、且支持logger名称由namespace.id组成,这里的"com.tianshouzhi.mybatis"是namespace的公共前缀,读者自行修改
# log4j.logger.com.tianshouzhi.mybatis=DEBUG
################### mybatis>3.2.0 #######################
# 1、支持logger名称由namespace.id组成
# 2、支持在<settings>元素中配置logPrefix,例如这里配置了logPrefix的值为"mybatis.",则可以按照如下方式配置logger
# log4j.logger.mybatis=DEBUG
2.2 log4j2配置
mybatis在3.2.3之前,并不直接支持log4j2作为日志框架,必须通过slf4j或者commons-logging来做桥接。
3.2.3之后,支持直接使用log4j2,也就是可以在<settings>
元素中通过配置logImpl值为LOG4J2
,不过这种方式存在一个bug,直到mybatis 3.2.8版本才修复,参见:https://github.com/mybatis/mybatis-3/issues/234。
为了避免这些问题,在使用log4j2作为日志框架的话,建议直接使用slf4j做桥接。
<!--slf4j依赖-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<!--桥接-->
<dependency>
<!-- 桥接:告诉Slf4j使用Log4j2 -->
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.3</version>
</dependency>
<!--log4j2依赖-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.3</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.3</version>
</dependency>
log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<!--配置mybatis打印sql日志-->
<!-- mybatis < 3.2.0 name可以设置为java.sql-->
<!-- mybatis > 3.1.0 name可以设置为java.sql 或者namespace属性值的前缀-->
<!-- mybatis >= 3.2.0 name可以设置为通过logPrefix设置的前缀-->
<Logger name="com.tianshouzhi" level="debug" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<Root level="error">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
log4j2官方配置文档:http://logging.apache.org/log4j/2.x/manual/configuration.html
2.3 logback配置
mybatis并不支持直接使用logback作为日志框架, 如果要使用logback的话,必须使用slf4j做桥接。pom中引入如下依赖:
<dependency>
<!--slf4j依赖-->
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<!--logback依赖-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.1.7</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.7</version>
</dependency>
logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
<Target>System.out</Target>
<encoder>
<pattern>%d-[TS] %p %t %c - %m%n</pattern>
</encoder>
</appender>
<!--配置mybatis打印sql日志-->
<!-- mybatis < 3.2.0 name可以设置为java.sql-->
<!-- mybatis > 3.1.0 name可以设置为java.sql 或者namespace属性值的前缀-->
<!-- mybatis >= 3.2.0 name可以设置为通过logPrefix设置的前缀-->
<logger name="java.sql" additivity="false">
<level value="debug"/>
<appender-ref ref="Console"/>
</logger>
<root level="info">
<appender-ref ref="Console"/>
</root>
</configuration>
2.4 jdk logging
笔者在实际项目开发中,并没有直接使用过jdk logging,其他三种都使用过。jdk logging是jdk自带的日志框架实现,位于java.util.logging包中。读者可以自行参照前面几种配置方式来配置jdk logging。
3 项目中存在多种日志框架的情况
有的读者可能会发现,即使按照上面的配置无误了,依然无法打印出日志。这是可能是项目中存在多种日志框架的jar包依赖,导致冲突。
默认情况下,mybatis按照如下方式检测需要使用的日志框架实现:
slf4j–>commons-logging–>log4j2–>log4j–>jdk logging(jul)–>no logging
在3.4.0 mybatis的org.apache.ibatis.logging.LogFactory类的源码中体现了上面的描述:
其中slf4j
,commons-logging
都是facade
设计模式的实现,底层需要依赖具体的日志框架,如log4j
、log4j2
、logback
等。并且还要引入相应的桥接jar包依赖。如果项目中有多种日志框架jar包依赖,可能会出现打印不了sql的情况,举例来说:
1、项目中有了slf4j依赖,那么mybatis就使用slf4j来打印日志,但是slf4j需要依赖具体的日志框架实现和桥接jar包,如果缺少,则不能正常工作。如项目中有了slf4j依赖,有了log4j依赖,但是缺少slf4j-log4j12
桥接jar包,即使log4.properties配置正确也是无法打印sql日志的,读者可以自行尝试。
2、slf4j在检测底层日志框架实现时,最优先使用的是logback,如果项目中同时存在了logback的依赖,和log4j的依赖,即使用户正确配置了log4j.properties文件,由于slf4j选择使用了logback来打印日志,因此也无法打印出sql,读者可以自行尝试。
对于这种日志框架jar包依赖缺少或者冲突的情况,mybatis从3.2.0提供了一个终极解决方案,通过logImpl
配置来强制指定使用哪个日志框架,使用方式如下所示:
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
logImpl的取值范围:SLF4J
| LOG4J
| LOG4J2
| JDK_LOGGING
| COMMONS_LOGGING
| STDOUT_LOGGING
| NO_LOGGING
其中,STDOUT_LOGGING表示打印日志到控制台。NO_LOGGING表示不打印日志。
下面这张图列表出了slf4j在和各种底层日志框架如何整合,以及相应的需要引入的桥接jar包,这张图位于slf4j官网上:https://www.slf4j.org/manual.html
4 mybatis打印SQL日志最佳实践
一般在生产环境中应用系统,日志级别调整为INFO
或者WARN
以避免过多的输出日志。但某些时候,需要跟踪具体问题,那么就得打开DEBUG
日志。但是如果设置root logger
为DEBUG
级别,则需要的信息就会淹没在日志的海洋中。
此时,需要单独指定某个或者某些logger(如mybatis打印sql的logger)的日志级别为DEBUG,而root logger保持INFO或WARN不变。
在和各种底层日志框架如何整合,以及相应的需要引入的桥接jar包,这张图位于slf4j官网上:https://www.slf4j.org/manual.html
原文:http://www.tianshouzhi.com/api/tutorials/mybatis/356