1.3 mybatis打印sql日志

mybatis支持使用多种日志框架来打印sql,包括:slf4jcommons-logginglog4jlog4j2jdk loggingstdoutno 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如何打印日志。

  1. 打印sql日志的logger一定要是debug级别的(注意这里说的不是root logger’),在其他级别下不论如何配置,mybatis也不会打印sql日志。
  2. mybatis 3.0.6,3.1.0,3.2.0版本前后打印日志的配置方式是不同的,这也是我们经常在网上照搬一些配置,但是依然打印不了sql的原因。
  3. 当应用中存在多种日志框架jar包的依赖时,如果没有进行合适的配置,也是无法打印sql的。例如slf4j和commons-logging都是facade设计模式的实现,用于统一各种日志框架,底层依赖于具体的日志框架实现如log4j、logback、log4j2、jdk logging,并且需要引入相应的桥接jar依赖。
  4. mybatis 版本>=3.2.0之后,<settings>元素中提供了logPrefixlogImpl配置项来帮助配置日志框架,这也是笔者建议的mybatis日志打印方式

1 不同版本的mybatis的日志实现

mybatis打印日志是通过org.apache.ibatis.logging.jdbc包下面的ConnectionLoggerStatementLoggerPreparedStatementLoggerResultSetLogger进行的。在mybatis 3.0.6,3.1.0,3.2.0之后,这几个类的实现方式各不相同,导致打印sql日志的配置方式也发生了变化。

下面分别介绍不同的mybatis版本中,实现的区别。

1.1 不同版本logger实现的区别

以PreparedStatementLogger为例,不同版本的实现如下所示:

mybatis版本<=3.0.6

mysql 日志 打印sql 怎么打印sql日志_sql

3.0.6<mybatis版本<3.2.0

这两个版本之间只发布了3.1.0和3.1.1两个版本,以3.1.1版本为例,PreparedStatementLogger实现如下:

mysql 日志 打印sql 怎么打印sql日志_java_02

mybatis>=3.2.0

mysql 日志 打印sql 怎么打印sql日志_mysql 日志 打印sql_03

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元素中可以配置logPrefixlogImpl配置项。例如,如果我们的项目中,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>

其中slf4jslf4j-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类的源码中体现了上面的描述:

mysql 日志 打印sql 怎么打印sql日志_sql_04

其中slf4jcommons-logging都是facade设计模式的实现,底层需要依赖具体的日志框架,如log4jlog4j2logback等。并且还要引入相应的桥接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

mysql 日志 打印sql 怎么打印sql日志_apache_05

4 mybatis打印SQL日志最佳实践

一般在生产环境中应用系统,日志级别调整为INFO或者WARN以避免过多的输出日志。但某些时候,需要跟踪具体问题,那么就得打开DEBUG日志。但是如果设置root loggerDEBUG级别,则需要的信息就会淹没在日志的海洋中。

此时,需要单独指定某个或者某些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