1. Mybatis 調(diào)試輸出SQL語句,到底是如何實(shí)現(xiàn)的呢?

        共 7400字,需瀏覽 15分鐘

         ·

        2022-06-18 17:35


        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ā),先到先得 

        嘿,你在看嗎?

        瀏覽 30
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 豆花无码视频 | 国产精品深夜福利 | 黑人巨大精品欧美一区二区三区 | 看操美女逼网 | 欧美二区视频 |