![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0gTMx81dsQWZ4lmZf1GLlpXazVmcvwFciV2dsQXYtJ3bm9CX9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cGcq5yN0AzMyMGM0MTYhVTMxEWZyYzX0MDMxQTMzAzLchDMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.jpg)
配置列印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類的靜态代碼塊中可以看到加載順序
在LogFactory的靜态代碼塊中,會初始化logConstructor這個屬性,後續擷取log的實作時直接通過構造函數傳回即可那這個Constructor是哪個類的呢?就需要不斷的嘗試,按照上面的順序依次加載日志實作類然後調用構造函數,如果構造函數成功執行,則将其構造函數賦到logConstructor,并停止嘗試。如果執行失敗,則接着嘗試
但是第三方日志都有各自的log級别,mybatis用擴充卡模式統一提供了trace,debug,warn,error四個級别
org.apache.ibatis.logging.Log接口有多個實作類,實作類即Mybatis提供的擴充卡
,例如Log4jImpl,Log4j2Impl等,一種實作類提供一個擴充卡,UML類圖如下
上圖中Logger對象是org.apache.log4j.Logger,即通過Log4jImpl的适配将對Log接口的操作轉化為Logger對象的操作
擴充卡模式設計的幾個角色如下
- 目标接口(Target):調用者能夠直接使用的接口,即Log接口
- 需要适配的類(Adaptee):Adaptee類中有真正的邏輯,但是不能被調用者直接使用,即Logger對象
- 擴充卡(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類的父類,繼承關系如下圖
ConnectionLogger:負責列印連接配接資訊和SQL語句,并建立PreparedStatementLogger
PreparedStatementLogger:負責列印參數資訊,并建立ResultSetLogger
StatementLogger:負責列印參數資訊,并建立ResultSetLogger
ResultSetLogger:負責列印資料結果資訊
4個類實作的思路是一樣的,這裡隻分析一下ConnectionLogger
當日志級别為debug時,傳回的是被代理後的Connection對象,否則就是正常的Connection對象
org.apache.ibatis.executor.BaseExecutor#getConnection
ConnectionLogger對象實作了InvocationHandler接口,傳回了代理後的Connection對象
org.apache.ibatis.logging.jdbc.ConnectionLogger#newInstance
具體的代理邏輯如下
如果是Object類的方法則直接調用,并且對prepareStatement,prepareCall,createStatement則三個方法進行了增強,傳回了代理後的PreparedStatement或者Statement,而這些代理後的對象中會列印sql相關的日志org.apache.ibatis.logging.jdbc.PreparedStatementLogger#invoke