MyBatis 源碼篇-日誌模塊2

上一章的案例,配置日誌級別爲 debug,執行一個簡單的查詢操作,會將 JDBC 操作打印出來。本章通過 MyBatis 日誌部分源碼分析它是如何實現日誌打印的。

在 MyBatis 的日誌模塊中有一個 jdbc package,package 中的內容如下圖所示:

image.png

BaseJdbcLogger 是一個抽象類,它是 jdbc package 下其他類的父類,類繼承關係如下圖所示:

BaseJdbcLogger 類中定義了一些公共集合和簡單的工具方法,提供給子類使用。

BaseJdbcLogger 的子類有如下特性:

  • ConnectionLogger:Connection 的代理類,封裝了 Connection 對象,繼承了 BaseJdbcLogger 抽象類並實現了 InvocationHandler 接口,newInstance() 方法會爲其封裝的 Connection 對象創建相應的代理對象;
  • PreparedStatementLogger:PreparedStatement 的代理類,封裝了 PreparedStatement 對象,繼承了 BaseJdbcLogger 抽象類並實現了 InvocationHandler 接口,newInstance() 方法的實現與 Connection 的類似;
  • StatementLogger:與 PreparedStatementLogger 類似;
  • ResultSetLogger:ResultSet 的代理類,封裝了 ResultSet 對象,繼承了 BaseJdbcLogger 抽象類並實現了 InvocationHandler 接口,newInstance() 方法的實現與 Connection 的類似;

MyBatis 就是通過動態代理的方式,對 JDBC 原生類進行了一層封裝,在代理類的 invoke 方法中添加對應 JDBC 操作的日誌打印功能。

ConnectionLogger 的實現如下:

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()方法或createStatement()方法
      // 則在創建相應的statement對象後,爲其創建代理對象並返回該代理對象
      if ("prepareStatement".equals(method.getName())) {
        // 輸出日誌
        if (isDebugEnabled()) {
          debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);
        }        
        // 調用Connection的prepareStatement()方法,得到PreparedStatement對象
        PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
        // 爲PreparedStatement對象創建代理對象
        stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
        return stmt;
      } else if ("prepareCall".equals(method.getName())) {
        // 輸出日誌
        if (isDebugEnabled()) {
          debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);
        }        
        PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
        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);
    }
  }

  /*
   * Creates a logging version of a connection
   *
   * @param conn - the original connection
   * @return - the connection with logging
   */
  public static Connection newInstance(Connection conn, Log statementLog, int queryStack) {
    // 使用動態代理的方式創建代理對象
    InvocationHandler handler = new ConnectionLogger(conn, statementLog, queryStack);
    ClassLoader cl = Connection.class.getClassLoader();
    return (Connection) Proxy.newProxyInstance(cl, new Class[]{Connection.class}, handler);
  }

  /*
   * return the wrapped connection
   *
   * @return the connection
   */
  public Connection getConnection() {
    return connection;
  }

}

其他子類的實現與 ConnectionLogger 類似,不在贅述。

ConnectionLogger 會創建 PreparedStatementLogger 或 StatementLogger,PreparedStatementLogger 會創建 ResultSetLogger,這樣就保證了每一步 JDBC 操作在 debug 日誌級別下都有日誌輸出。

那麼 ConnectionLogger 又是在哪裏創建的呢?跟蹤 SQL 的執行流程,在 org.apache.ibatis.executor.BaseExecutor#getConnection 方法中找到 ConnectionLogger 的創建代碼:

protected Connection getConnection(Log statementLog) throws SQLException {
  Connection connection = transaction.getConnection();
  // 判斷日誌級別爲debug,則創建Connection的代理類ConnectionLogger
  if (statementLog.isDebugEnabled()) {
    return ConnectionLogger.newInstance(connection, statementLog, queryStack);
  } else {
    return connection;
  }
}

從源碼中可以看出,如果日誌級別爲 debug,則會創建代理類 ConnectionLogger,否則只會使用正常的 Connection 對象。

 

MyBatis 源碼篇

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章