<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      MyBatis日志模塊源碼分析

      MyBatis源碼的logging包下是日志模塊的相關實現,Mybatis日志模塊通過適配器模式和代理模式優雅的實現了SQL日志的輸出功能。

      一. 適配器模式實現了MyBatis對第三方日志框架的適配

      Mybatis內部沒有提供日志實現類,需要接入第三方的日志組件,但第三方組件都有自己的log級別,并且各不相同,Mybatis 在內部定義了Log接口統一提供了trace debug warn error四個日志級別。

      /**
       * 由于不同的日志框架,擁有不同的日志級別,所以MyBatis在內部定義了Log接口,
       * 用于讓其他日志框架適配MyBatis內部規定的日志級別
       */
      public interface Log {
      
        boolean isDebugEnabled();
      
        boolean isTraceEnabled();
      
        /**
         * -----------規定的日志級別-----------
         */
      
        void error(String s, Throwable e);
      
        void error(String s);
      
        void debug(String s);
      
        void trace(String s);
      
        void warn(String s);
      
      }
      
      

      Mybatis 使用適配器模式,在每個第三方日志廠商和自己的log之間都存在一個適配器,將第三方的日志級別適配為自己的log級別。

      在這里插入圖片描述

      其中日志適配器的繼承體系如下:

      在這里插入圖片描述

      我們以Jdk14LoggingImpl類(JDK日志框架的適配器)源碼為例,分析適配器所做的事情??梢钥吹皆贘DK自帶的日志框架中包含SEVEREWARNING、INFO、CONFIG、FINE等8個日志級別,而Mybatis只提供了error、debug、tracewarn四種日志日志級別,這就需要認為將兩者的日志級別對應起來。

      /**
       * JDK內置日志框架的適配器,用于適配MyBatis內部約定的Log接口
       * @author Clinton Begin
       */
      public class Jdk14LoggingImpl implements Log {
      
        private final Logger log;
      
        public Jdk14LoggingImpl(String clazz) {
          log = Logger.getLogger(clazz);
        }
      
        @Override
        public boolean isDebugEnabled() {
          return log.isLoggable(Level.FINE);
        }
      
        @Override
        public boolean isTraceEnabled() {
          return log.isLoggable(Level.FINER);
        }
      
        @Override
        public void error(String s, Throwable e) {
          log.log(Level.SEVERE, s, e);//JDK中SEVERE級別日志對應MyBatis中error
        }
      
        @Override
        public void error(String s) {
          log.log(Level.SEVERE, s);
        }
      
        @Override
        public void debug(String s) {
          log.log(Level.FINE, s);
        }
      
        @Override
        public void trace(String s) {
          log.log(Level.FINER, s);
        }
      
        @Override
        public void warn(String s) {
          log.log(Level.WARNING, s);
        }
      }
      

      可以看到使用適配器模式,我們解決第三方日志框架內部實現不一致的問題,但是這些日志適配器的使用就需要借助Mybatis提供的日志工廠類了(org.apache.ibatis.logging.LogFactory)。在該類擁有一個靜態代碼塊,用于動態加載項目中依賴的日志框架:

      static {
        //按照順序掃描當前項目中配置的日志框架。注:雙冒號是JDK8的語法,代表方法引用(語法糖)
        tryImplementation(LogFactory::useSlf4jLogging);
        tryImplementation(LogFactory::useCommonsLogging);
        tryImplementation(LogFactory::useLog4J2Logging);
        tryImplementation(LogFactory::useLog4JLogging);
        tryImplementation(LogFactory::useJdkLogging);
        tryImplementation(LogFactory::useNoLogging);
      }
      

      該類會按照slf4J → commonsLoging → Log4J2 → Log4J → JdkLog順序加載日志框架,如果加載成功一個,就不會加載后面的日志框架了。

      private static void tryImplementation(Runnable runnable) {
          //如果前面的適配器都沒有創建成功,則試圖創建當前類型的適配器
          if (logConstructor == null) {
            try {
              runnable.run();
            } catch (Throwable t) {
              // ignore
            }
          }
        }
      
      public static synchronized void useSlf4jLogging() {
          setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);
      }
      
      /**
         * 設置具體的日志實現類構造器
         *
         * @param implClass
         */
        private static void setImplementation(Class<? extends Log> implClass) {
          try {
            Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
            //這里調用目標日志框架的適配器中的構造器,試圖創建適配器對象,如果沒有該框架的依賴則會拋出異常
            Log log = candidate.newInstance(LogFactory.class.getName());
            if (log.isDebugEnabled()) {
              log.debug("Logging initialized using '" + implClass + "' adapter.");
            }
            logConstructor = candidate;
          } catch (Throwable t) {
            throw new LogException("Error setting Log implementation.  Cause: " + t, t);
          }
        }
      

      二. 代理模式實現了SQL日志輸出

      Mybatis 日志的使用優雅的嵌入到主體功能中(動態代理增強),接下來我們看一下是如何進行日志增強的。

      2.1日志輸出的體系結構

      org.apache.ibatis.logging.jdbc包中存放就是JDBC相關對象的增強類,它們就是MyBatis實現日志打印的核心:

      在這里插入圖片描述

      其中BaseJdbcLogger類保存著日志打印所需要的信息:

      /**
       * MyBatis用于輸出執行器SQL日志記錄的基類。
       *
       * @author Clinton Begin
       * @author Eduardo Macarron
       */
      public abstract class BaseJdbcLogger {
        //保存PreparedStatement中常用的set方法(用于占位符設值的方法)
        protected static final Set<String> SET_METHODS;
        //保存PreparedStatement中常用的執行SQL語句的方法
        protected static final Set<String> EXECUTE_METHODS = new HashSet<>();
      
        //保存PreparedStatement中set方法的鍵值對
        private final Map<Object, Object> columnMap = new HashMap<>();
      
        //保存PreparedStatement中set方法的key值
        private final List<Object> columnNames = new ArrayList<>();
        //保存PreparedStatement中set方法的value值
        private final List<Object> columnValues = new ArrayList<>();
      
        //用于打印日志的對象
        protected final Log statementLog;
      

      ConnectionLogger、PreparedStatementLogger、ResultSetLogger、StatementLogger這幾個類都實現了InvocationHandler接口,這也證明了它們分別是對ConnecitonPreparedStatement、ResultSet、Statemtent類的加強。

      2.2 探尋入口

      org.apache.ibatis.executor.BaseExecutor是MyBatis中的執行器,是Mybatis實際操作數據庫的類,該類中的getConnection()方法返回的就是經過加強的連接對象,也就是:ConnectionLogger

      protected Connection getConnection(Log statementLog) throws SQLException {
        Connection connection = transaction.getConnection();
        if (statementLog.isDebugEnabled()) {
          return ConnectionLogger.newInstance(connection, statementLog, queryStack);
        } else {
          return connection;
        }
      }
      
      2.2.1 ConnectionLogger

      其中ConnectionLogger負責在調用prepareStatement()方法時打印SQL語句日志,并返回經過加強后的PreparedStatement,也就是PreparedStatementLogger。

      /**
       * 負責打印數據庫連接相關信息和SQL語句,并創建PreparedStatementLogger
       *
       * @author Clinton Begin
       * @author Eduardo Macarron
       */
      public final class ConnectionLogger extends BaseJdbcLogger implements InvocationHandler {
      
        //真正的連接對象
        private final Connection connection;
      
        private ConnectionLogger(Connection conn, Log statementLog, int queryStack) {
          super(statementLog, queryStack);
          this.connection = conn;
        }
      
        @Override
        public Object invoke(Object proxy, Method method, Object[] params)
          throws Throwable {
          try {
            //如果調用的是Object的方法則跳過代理
            if (Object.class.equals(method.getDeclaringClass())) {
              return method.invoke(this, params);
            }
            //如果是調用prepareStatement或者prepareCall,則打印SQL語句
            if ("prepareStatement".equals(method.getName()) || "prepareCall".equals(method.getName())) {
              if (isDebugEnabled()) {
                debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);
              }
              PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
              /**
               * 創建PreparedStatement的代理類PreparedStatementLogger對象,并返回這個對象
               */
              stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
              return stmt;
            } else if ("createStatement".equals(method.getName())) {
              Statement stmt = (Statement) method.invoke(connection, params);
              stmt = StatementLogger.newInstance(stmt, statementLog, queryStack);
              return stmt;
            } else {
              return method.invoke(connection, params);
            }
          } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
          }
        }
        .....
                                              
      }
      
      
      2.2.2 PreparedStatementLogger

      PreparedStatementLogger增強類是為了在調用目標對象的executeQuery方時輸出將要執行的SQL語句,并將返回的ResultSet加強為ResultSetLogger

      public final class PreparedStatementLogger extends BaseJdbcLogger implements InvocationHandler {
      
        private final PreparedStatement statement;
      
        private PreparedStatementLogger(PreparedStatement stmt, Log statementLog, int queryStack) {
          super(statementLog, queryStack);
          this.statement = stmt;
        }
      
        @Override
        public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
          try {
            //判斷調用的是不是Object的方法
            if (Object.class.equals(method.getDeclaringClass())) {
              return method.invoke(this, params);
            }
            //如果執行的是SQL執行相關的方法,則打印參數
            if (EXECUTE_METHODS.contains(method.getName())) {
              if (isDebugEnabled()) {
                debug("Parameters: " + getParameterValueString(), true);
              }
              //清除父類中保存的SQL參數
              clearColumnInfo();
              if ("executeQuery".equals(method.getName())) {
                //執行executeQuery方法,獲取查詢結果集
                ResultSet rs = (ResultSet) method.invoke(statement, params);
                //將查詢結果集封裝成ResultSetLogger代理對象
                return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
              } else {
                //如果不是查詢語句,則不生成代理對象,也就不會打印日志
                return method.invoke(statement, params);
              }
            } else if (SET_METHODS.contains(method.getName())) {
              /**
               * 如果執行的是PreparedStatement中的Set方法,則將對應的參數值放入父類專門保存SQL參數值的容器中
               */
              if ("setNull".equals(method.getName())) {
                setColumn(params[0], null);
              } else {
                setColumn(params[0], params[1]);
              }
              return method.invoke(statement, params);
            } else if ("getResultSet".equals(method.getName())) {
              ResultSet rs = (ResultSet) method.invoke(statement, params);
              return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
            } else if ("getUpdateCount".equals(method.getName())) {
              int updateCount = (Integer) method.invoke(statement, params);
              if (updateCount != -1) {
                debug("   Updates: " + updateCount, false);
              }
              return updateCount;
            } else {
              return method.invoke(statement, params);
            }
          } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
          }
        }
      
      
      
      2.2.3 ResultSetLogger

      ResultSetLogger增強類是為了在獲取結果集時打印結果集信息。

      public final class ResultSetLogger extends BaseJdbcLogger implements InvocationHandler {
      
        private static final Set<Integer> BLOB_TYPES = new HashSet<>();
        private boolean first = true;
        private int rows;
        private final ResultSet rs;
        private final Set<Integer> blobColumns = new HashSet<>();
      
        static {
          BLOB_TYPES.add(Types.BINARY);
          BLOB_TYPES.add(Types.BLOB);
          BLOB_TYPES.add(Types.CLOB);
          BLOB_TYPES.add(Types.LONGNVARCHAR);
          BLOB_TYPES.add(Types.LONGVARBINARY);
          BLOB_TYPES.add(Types.LONGVARCHAR);
          BLOB_TYPES.add(Types.NCLOB);
          BLOB_TYPES.add(Types.VARBINARY);
        }
      
        private ResultSetLogger(ResultSet rs, Log statementLog, int queryStack) {
          super(statementLog, queryStack);
          this.rs = rs;
        }
      
        @Override
        public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
          try {
            if (Object.class.equals(method.getDeclaringClass())) {
              return method.invoke(this, params);
            }
            Object o = method.invoke(rs, params);
            if ("next".equals(method.getName())) {
              if ((Boolean) o) {
                rows++;
                if (isTraceEnabled()) {
                  ResultSetMetaData rsmd = rs.getMetaData();
                  final int columnCount = rsmd.getColumnCount();
                  if (first) {
                    first = false;
                    printColumnHeaders(rsmd, columnCount);
                  }
                  printColumnValues(columnCount);
                }
              } else {
                debug("     Total: " + rows, false);
              }
            }
            clearColumnInfo();
            return o;
          } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
          }
        }
      }
      
      

      注:上文中并沒有分析StatementLogger類,具體的分析方式與上文一致。

      詳細的代碼注釋請移步至:http://github.com/tianjindong/mybatis-source-annotation

      三. 總結

      MyBatis中使用適配器模式對第三方日志框架輸出級別進行統一,使用JDK動態代理技術分別加強Connection、PreparedStatementResultSet類使之能在自己的聲明周期中打印SQL執行的相關信息。

      posted @ 2020-04-30 15:19  聽到微笑  閱讀(32)  評論(0)    收藏  舉報  來源
      主站蜘蛛池模板: 雷波县| av日韩在线一区二区三区| 国产成人a在线观看视频免费| 深夜福利啪啪片| 欧美成人午夜在线观看视频| 亚洲第一香蕉视频啪啪爽| 日本一道一区二区视频| 成年站免费网站看v片在线| 国产在线中文字幕精品 | 国产乱码日韩精品一区二区| 97se亚洲国产综合在线| 亚洲色大成网站www久久九九| 日本无码欧美一区精品久久| 久久狠狠一本精品综合网| 国产成人综合网在线观看| 午夜成人性爽爽免费视频| 亚洲国产大片永久免费看| 日韩有码中文字幕国产| 亚洲码国产精品高潮在线| 精品人妻中文字幕在线| 性色av无码久久一区二区三区| 亚洲国产欧美一区二区好看电影| 国产精品久久久久久久专区| jizz国产免费观看| 中文文精品字幕一区二区| 亚洲精品无码高潮喷水A| 亚洲少妇人妻无码视频| 久久综合国产一区二区三区| 日韩人妻无码中文字幕视频| 各种少妇wbb撒尿| 国产超碰人人做人人爰| 精品一区二区不卡无码AV| 久久亚洲精品11p| 亚洲无人区码一二三四区| 日本熟妇色xxxxx日本免费看 | 国产老熟女乱子一区二区| 国产人妻精品午夜福利免费| 国产精品午夜福利精品| 一本久道久久综合狠狠躁av| 亚洲乱码精品中文字幕| 免费无码中文字幕A级毛片|