天天看點

Mybatis源碼解析:Mybatis是如何相容這麼多日志架構的?

Mybatis源碼解析:Mybatis是如何相容這麼多日志架構的?

配置列印sql

在開發調試的過程中,我們經常需要檢視列印出來的sql來幫助我們排查問題。我們可以在mybatis-config.xml中settings标簽配置日志架構,可選的日志架構如下

SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING

<?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>
  <settings>
    <setting name="logImpl" value="SLF4J" />
  </settings>
</configuration>      

在logback.xml中配置dao包的日志級别為DEBUG,就能列印執行的SQL

<configuration>
    <appender name="STDOUT" 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>
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>logs/%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>

    <root level="debug">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="FILE" />
    </root>
    
    <!--這裡是mapper的包名-->
    <logger name="com.javashitang.blog.dao" level="DEBUG"/>
    
</configuration>      

如何相容這麼多日志架構的?

mybatis中支援多種日志架構,使用者如果不配置的話則會按如下順序加載第三方日志

slf4j > apache commons log > log4j2 > log4j > jul > 不列印log

從LogFactory類的靜态代碼塊中可以看到加載順序

Mybatis源碼解析:Mybatis是如何相容這麼多日志架構的?
Mybatis源碼解析:Mybatis是如何相容這麼多日志架構的?

在LogFactory的靜态代碼塊中,會初始化logConstructor這個屬性,後續擷取log的實作時直接通過構造函數傳回即可那這個Constructor是哪個類的呢?就需要不斷的嘗試,按照上面的順序依次加載日志實作類然後調用構造函數,如果構造函數成功執行,則将其構造函數賦到logConstructor,并停止嘗試。如果執行失敗,則接着嘗試

Mybatis源碼解析:Mybatis是如何相容這麼多日志架構的?
Mybatis源碼解析:Mybatis是如何相容這麼多日志架構的?

但是第三方日志都有各自的log級别,mybatis用擴充卡模式統一提供了trace,debug,warn,error四個級别

org.apache.ibatis.logging.Log接口有多個實作類,實作類即Mybatis提供的擴充卡

,例如Log4jImpl,Log4j2Impl等,一種實作類提供一個擴充卡,UML類圖如下

Mybatis源碼解析:Mybatis是如何相容這麼多日志架構的?

上圖中Logger對象是org.apache.log4j.Logger,即通過Log4jImpl的适配将對Log接口的操作轉化為Logger對象的操作

擴充卡模式設計的幾個角色如下

  1. 目标接口(Target):調用者能夠直接使用的接口,即Log接口
  2. 需要适配的類(Adaptee):Adaptee類中有真正的邏輯,但是不能被調用者直接使用,即Logger對象
  3. 擴充卡(Adapter):實作了Target接口,包裝了Adaptee對象

當然我們在項目中一般也不配置Mybatis适配後的Logger對象,因為級别實在是太少了,INFO級别都沒有

import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
public static final Log log = LogFactory.getLog(Test.class);      

直接配置slf4j的即可

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public static final Logger logger = LoggerFactory.getLogger(Test.class);      

當然mybatis内部的日志如JDBC子產品日志,它沒有和特定的日志架構綁定,用的是Mybatis适配後的Log對象,如想控制源碼内部用的日志架構,可以在mybatis配置檔案中配置一下

JDBC調試,列印不同類型的日志

在mybatis源碼logging子產品下有一個jdbc包,當日志級别設定為DEBUG級别時它通過動态代理的方式輸出很多實用資訊,如輸出SQL語句,使用者傳入的綁定參數,SQL語句影響的行數等資訊

你可能會想,為什麼要通過動态代理來列印debug級别的日志呢?用log.debug()不就行了,主要還是為了避免日志邏輯和正常邏輯耦合到一塊

BaseJdbcLogger是一個抽象類,它是jdbc包下其他Logger類的父類,繼承關系如下圖

Mybatis源碼解析:Mybatis是如何相容這麼多日志架構的?

ConnectionLogger:負責列印連接配接資訊和SQL語句,并建立PreparedStatementLogger

PreparedStatementLogger:負責列印參數資訊,并建立ResultSetLogger

StatementLogger:負責列印參數資訊,并建立ResultSetLogger

ResultSetLogger:負責列印資料結果資訊

4個類實作的思路是一樣的,這裡隻分析一下ConnectionLogger

當日志級别為debug時,傳回的是被代理後的Connection對象,否則就是正常的Connection對象

org.apache.ibatis.executor.BaseExecutor#getConnection

Mybatis源碼解析:Mybatis是如何相容這麼多日志架構的?

ConnectionLogger對象實作了InvocationHandler接口,傳回了代理後的Connection對象

org.apache.ibatis.logging.jdbc.ConnectionLogger#newInstance

Mybatis源碼解析:Mybatis是如何相容這麼多日志架構的?

具體的代理邏輯如下

Mybatis源碼解析:Mybatis是如何相容這麼多日志架構的?

如果是Object類的方法則直接調用,并且對prepareStatement,prepareCall,createStatement則三個方法進行了增強,傳回了代理後的PreparedStatement或者Statement,而這些代理後的對象中會列印sql相關的日志org.apache.ibatis.logging.jdbc.PreparedStatementLogger#invoke

Mybatis源碼解析:Mybatis是如何相容這麼多日志架構的?
Mybatis源碼解析:Mybatis是如何相容這麼多日志架構的?

參考部落格