咱們在使用mybatis
時,若是出現sql問題,通常會把mybatis
配置文件中的logging.level
參數改爲debug
,這樣就能在日誌中看到某個mapper
最終執行sql、入參和影響數據行數。咱們拿到sql和入參,手動拼接成完整的sql,而後將該sql在數據庫中執行一下,就基本能定位到問題緣由。mybatis
的日誌功能使用起來仍是很是方便的,你們有沒有想過它是如何設計的呢?程序員
咱們先看一下mybatis
的logging
目錄,該目錄的功能決定了mybatis
使用什麼日誌工具打印日誌。算法
logging
目錄結構以下:sql
它裏面除了jdbc
目錄,還包含了7個子目錄,每個子目錄表明一種日誌打印工具,目前支持6種日誌打印工具和1種非日誌打印工具。咱們用一張圖來總結一下數據庫
除了上面的8種日誌工具以外,它還抽象出一個Log
接口,全部的日誌打印工具必須實現該接口,後面能夠面向接口編程。定義了LogException
異常,該異常是日誌功能的專屬異常,若是你有看過mybatis
其餘源碼的話,不難發現,其餘功能也定義專屬異常,好比:DataSourceException
等,這是mybatis
的慣用手法,主要是爲了將異常細粒度的劃分,以便更快定位問題。此外,它還定義了LogFactory
日誌工廠,以便於屏蔽日誌工具實例的建立細節,讓用戶使用起來更簡單。編程
咱們按照上面目錄結構的介紹其實已經有一些思路:segmentfault
Log
接口,以便於統一抽象日誌功能,這8種日誌功能都實現Log
接口,而且重寫日誌打印方法。LogFactory
日誌工廠,它會根據咱們項目中引入的某個日誌打印工具jar包,建立一個具體的日誌打印工具實例。看起來,不錯。可是,再仔細想一想,LogFactory
中如何判斷項目中引入了某個日誌打印工具jar包才建立相應的實例呢?咱們第一個想到的多是用if...else
判斷不就好了,再想一想感受用if...else
很差,7種條件判斷太多了,並不是優雅的編程。這時候,你會想一些避免太長if...else
判斷的方法,可是mybatis
卻用了一個新的辦法。mybatis
Log
接口開始
它裏面抽象了日誌打印的5種方法和2種判斷方法。多線程
LogFactory
的代碼
它裏面定義了一個靜態的構造器logConstructor
,沒有用if...else
判斷,在static代碼塊中調用了6個tryImplementation
方法,該方法會啓動一個執行任務去調用了useXXXLogging
方法,建立日誌打印工具實例。app
固然tryImplementation
方法在執行前會判斷構造器logConstructor
爲空才容許執行任務中的run方法。下一步看看useXXXLogging
方法:ide
看到這裏,聰明的你可能會有這樣的疑問,從上圖能夠看出mybatis
定義了8種useXXXLogging
方法,可是在前面的static
靜態代碼塊中卻只調用了6種,這是爲何?
對比後發現:useCustomLogging
和 useStdOutLogging
前面是沒調用的。useStdOutLogging
它裏面使用了StdOutImpl
類
該類其實就是經過JDK
自帶的System
類的方法打印日誌的,無需引入額外的jar包,因此不參與static
代碼塊中的判斷。
而useCustomLogging
方法須要傳入一個實現了Log
接口的類,若是mybatis
默認提供的6種日誌打印工具不知足要求,以便於用戶本身擴展。
而這個方法是在Configuration
類中調用的,若是用戶有自定義logImpl
參數的話。
具體是在XMLConfigBuilder
類的settingsElement
方法中調用
再回到前面LogFactory
的setImplementation
方法
它會先找到實現了Log
接口的類的構造器,返回將該構造器賦值給全局的logConstructor
。
這樣一來,就能夠經過getLog
方法獲取到Log
實例。
而後在業務代碼中經過下面這種方式獲取Log
對象,調用它的方法打印日誌了。
梳理一下LogFactory的流程:
在這裏還分享一個知識點,若是某個工具類裏面都是靜態方法,那麼要把該工具類的構造方法定義成private
的,防止被疑問調用,LogFactory
就是這麼作的。
日誌模塊除了使用工廠模式
以外,仍是有了適配器模式
。
適配器模式會將所須要適配的類轉換成調用者可以使用的目標接口
涉及如下幾個角色:
mybatis是怎麼用適配器模式的?
上圖中標紅的類對應的是Adapter
角色,Log
是Target
角色。
而LogFactory
就是Adaptee
,它裏面的getLog
方法裏面包含是須要適配的對象。
從上面已經可以肯定使用哪一種日誌打印工具,但在sql執行的過程當中是如何打印日誌的呢?這就須要進一步分析logging
目錄下的jdbc
目錄了。
看看這幾個類的關係圖:
ConnectionLogger
、PreparedStatementLogger
、ResultSetLogger
和StatementLogger
都繼承了BaseJdbcLogger
類,而且實現了InvocationHandler
接口。從類名很是直觀的看出,這4種類對應的數據庫jdbc功能。
類名
對應功能
ConnectionLogger
Connection
PreparedStatementLogger
PreparedStatement
ResultSetLogger
ResultSet
StatementLogger
Statement
它們實現了InvocationHandler
接口意味着它用到了動態代理,真正起做用的是invoke
方法,咱們以ConnectionLogger
爲例:
若是調用了prepareStatement
方法,則會打印debug日誌。
上圖中傳入的original
參數裏面包含了nt
等分隔符,須要將分隔符替換成空格,拼接成一行sql
。
最終會在日誌中打印sql、入參和影響行數:
上圖中的sql語句是在ConnectionLogger類中打印的
那麼入參和影響行數呢?
入參在PreparedStatementLogger類中打印的
影響行數在ResultSetLogger類中打印的
你們須要注意的一個地方是:sql、入參和影響行數只打印了debug級別的日誌,其餘級別並沒打印。因此須要在mybatis
配置文件中的logging.level
參數配置成debug
,才能打印日誌。
不知道你們有沒有發現這樣一個問題:
在LogFactory
的代碼中定義了不少匿名的任務執行器
可是在實際調用時,卻沒有在線程中執行,而是直接調用的,這是爲何?
答案是爲了保證順序執行,若是全部的日誌工具jar包都有,加載優先級是:slf4j
》commonsLog
》log4j2
》log4j
》jdkLog
》NoLog
還有個問題,順序執行就能夠了,爲何要把匿名內部類定義成Runnable的呢?
這裏很是有迷惑性,由於它沒建立Thread類,並不會多線程執行。我我的認爲,這裏是mybatis的開發者的一種偷懶,否則須要定義一個新類代替這種執行任務的含義,還不如就用已有的。
原文:https://mp.weixin.qq.com/s/HV...
全網找不出第二份能把Mybatis講解這麼詳細且圖文並茂的PDF,開放分享
若是你以爲這篇內容對你還蠻有幫助,我想邀請你幫我三個小忙:
點贊,轉發,有大家的 『點贊和評論』,纔是我創造的動力。
關注公衆號 『 Java鬥帝 』,不按期分享原創知識。
同時能夠期待後續文章ing🚀