Mybatis 調(diào)試輸出SQL語句,到底是如何實(shí)現(xiàn)的呢?
Java 開發(fā)中常用的幾款日志框架有很多種,并且這些日志框架來源于不同的開源組織,給用戶暴露的接口也有很多不同之處,所以很多開源框架會(huì)自己定義一套統(tǒng)一的日志接口,兼容上述第三方日志框架,供上層使用。
一般實(shí)現(xiàn)的方式是使用適配器模式,將各個(gè)第三方日志框架接口轉(zhuǎn)換為框架內(nèi)部自定義的日志接口。MyBatis 也提供了類似的實(shí)現(xiàn),這里我們就來簡(jiǎn)單了解一下。
適配器模式是什么?
簡(jiǎn)單來說,適配器模式主要解決的是由于接口不能兼容而導(dǎo)致類無法使用的問題,這在處理遺留代碼以及集成第三方框架的時(shí)候用得比較多。其核心原理是:通過組合的方式,將需要適配的類轉(zhuǎn)換成使用者能夠使用的接口。
日志模塊
MyBatis 自定義的 Log 接口位于 org.apache.ibatis.logging 包中,相關(guān)的適配器也位于該包中。
首先是 LogFactory 工廠類,它負(fù)責(zé)創(chuàng)建 Log 對(duì)象,在 LogFactory 類中有一段靜態(tài)代碼塊,其中會(huì)依次加載各個(gè)第三方日志框架的適配器。
static {
tryImplementation(LogFactory::useSlf4jLogging);
tryImplementation(LogFactory::useCommonsLogging);
tryImplementation(LogFactory::useLog4J2Logging);
tryImplementation(LogFactory::useLog4JLogging);
tryImplementation(LogFactory::useJdkLogging);
tryImplementation(LogFactory::useNoLogging);
}
以 JDK Logging 的加載流程(useJdkLogging() 方法)為例,其具體代碼實(shí)現(xiàn)和注釋如下:
/**
* 首先會(huì)檢測(cè) logConstructor 字段是否為空,
* 1.如果不為空,則表示已經(jīng)成功確定當(dāng)前使用的日志框架,直接返回;
* 2.如果為空,則在當(dāng)前線程中執(zhí)行傳入的 Runnable.run() 方法,嘗試確定當(dāng)前使用的日志框架
*/
private static void tryImplementation(Runnable runnable) {
if (logConstructor == null) {
try {
runnable.run();
} catch (Throwable t) {
// ignore
}
}
}
public static synchronized void useJdkLogging() {
setImplementation(org.apache.ibatis.logging.jdk14.Jdk14LoggingImpl.class);
}
private static void setImplementation(Class<? extends Log> implClass) {
try {
// 獲取適配器的構(gòu)造方法
Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
// 嘗試加載適配器,加載失敗會(huì)拋出異常
Log log = candidate.newInstance(LogFactory.class.getName());
// 加載成功,則更新logConstructor字段,記錄適配器的構(gòu)造方法
logConstructor = candidate;
} catch (Throwable t) {
throw new LogException("Error setting Log implementation. Cause: " + t, t);
}
}
打印SQL語句
如何開啟打印
這里演示Mybatis在運(yùn)行時(shí)怎么輸出SQL語句,具體分析見原理章節(jié)。
單獨(dú)使用Mybatis
在mybatis.xml配置文件中添加如下配置:
<setting name="logImpl" value="STDOUT_LOGGING" />
和SpringBoot整合
有兩種方式,第一種也是利用StdOutImpl實(shí)現(xiàn)類去實(shí)現(xiàn)打印,在application.yml配置文件填寫如下:
#mybatis配置
mybatis:
# 控制臺(tái)打印sql日志
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
其次我們還可以通過指定日志級(jí)別來輸出SQL語句:
SpringBoot默認(rèn)使用的SL4J(日志門面)+Logback(具體實(shí)現(xiàn))的日志組合
logging:
level:
xx包名: debug
簡(jiǎn)單分析原理
這里我們直接看到org.apache.ibatis.executor.BaseExecutor#getConnection方法,了解Mybatis的應(yīng)該都知道Mybatis在執(zhí)行sql操作的時(shí)候會(huì)去獲取數(shù)據(jù)庫(kù)連接
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
// 判斷日志級(jí)別是否為Debug,是的話返回代理對(duì)象
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
可以看到我注釋的那行,它通過判斷日志級(jí)別來判斷是否返回ConnectionLogger代理對(duì)象,那么我們前面提到 Log 接口的實(shí)現(xiàn)類中StdOutImpl它的isDebugEnabled其實(shí)是永遠(yuǎn)返回 true,代碼如下:
并且它直接用的 System.println去輸出的SQL信息
public class StdOutImpl implements Log {
// ...省略無關(guān)代碼
@Override
public boolean isDebugEnabled() {
return true;
}
@Override
public boolean isTraceEnabled() {
return true;
}
@Override
public void error(String s, Throwable e) {
System.err.println(s);
e.printStackTrace(System.err);
}
@Override
public void error(String s) {
System.err.println(s);
}
// ...省略無關(guān)代碼
}
到這里起碼你知道了為什么我們通過配置 MyBatis 所用日志的具體實(shí)現(xiàn) logImpl就可以實(shí)現(xiàn)日志輸出到控制臺(tái)的效果了。
那么我們還可以深究一下 statementLog 是在什么時(shí)候變成 StdOutImpl的,在解析Mybatis配置文件的時(shí)候,會(huì)去讀取我們配置的logImpl屬性,然后通過LogFactory.useCustomLogging方法先指定好適配器的構(gòu)造方法
// org.apache.ibatis.builder.xml.XMLConfigBuilder#loadCustomLogImpl
private void loadCustomLogImpl(Properties props) {
Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
configuration.setLogImpl(logImpl);
}
public void setLogImpl(Class<? extends Log> logImpl) {
if (logImpl != null) {
this.logImpl = logImpl;
LogFactory.useCustomLogging(this.logImpl);
}
}
然后在構(gòu)建MappedStatement的時(shí)候就已經(jīng)將日志對(duì)象初始化好了
每個(gè)MappedStatement對(duì)應(yīng)了我們自定義Mapper接口中的一個(gè)方法,它保存了開發(fā)人員編寫的SQL語句、參數(shù)結(jié)構(gòu)、返回值結(jié)構(gòu)、Mybatis對(duì)它的處理方式的配置等細(xì)節(jié)要素,是對(duì)一個(gè)SQL命令是什么、執(zhí)行方式的完整定義。
public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) {
// ...省略無關(guān)代碼
mappedStatement.statementLog = LogFactory.getLog(logId);
mappedStatement.lang = configuration.getDefaultScriptingLanguageInstance();
}
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);
}
}
最后SpringBoot的就不概述了
第一種方式其實(shí)也是同理 第二種方式是通過修改了日志級(jí)別,然后使 isDebugEnabled返回true,去返回代理對(duì)象,然后去輸出SQL語句。
感興趣的還可以看看SQL語句的輸出是怎么輸出的,具體在 ConnectionLogger的invoke方法中,你會(huì)發(fā)現(xiàn)熟悉的Preparing: "和"Parameters: "。
來源:blog.csdn.net/qq_39809458/article/
details/121793630
【END】 其他優(yōu)質(zhì)好項(xiàng)目 一鍵生成Spring Boot +Vue項(xiàng)目!接私活縮短一半工期... 推薦一款牛逼的接私活項(xiàng)目,微服務(wù)也能搞定! 推薦一套開源通用后臺(tái)管理系統(tǒng) 12 個(gè)適合做外包項(xiàng)目的開源后臺(tái)管理系統(tǒng) 基于 SpringBoot + MyBatis 前后端分離實(shí)現(xiàn)的在線辦公系統(tǒng) 分享70套 Java 項(xiàng)目+實(shí)戰(zhàn)課 Java項(xiàng)目精選讀者群正式開發(fā),先到先得 嘿,你在看嗎?

