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

  • 相关阅读:
    消息队列技术
    NET Core中使用Apworks
    TCP基础
    Oracle停止一个JOB
    如何在Java 8中愉快地处理日期和时间
    mysql字符串区分大小写的问题
    【已解决】javax.validation.UnexpectedTypeException: HV000030: No validator could be found for constraint
    spring boot 1.4默认使用 hibernate validator
    mysql shell
    android:background="@drawable/home_tab_bg"
  • 原文地址:https://www.cnblogs.com/yinjw/p/11757449.html
Copyright © 2020-2023  润新知