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 源码篇

posted @ 2019-10-29 10:45  殷建卫  阅读(346)  评论(0编辑  收藏  举报