IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    Mybatis 框架日志相关源码分析(三)

    此间少年发表于 2023-04-18 09:45:00
    love 0

    通过上篇文章《Mybatis 框架日志相关源码分析(二)》了解了 Mybatis 通过工厂模式创建 Log 接口的实现类,那么拿到实现类之后, Mybatis 是如何输出日志的呢?

    本文将分析 Mybatis 框架的日志相关源码,了解 Mybatis 使用 JDBC 时,是通过何种方式输出日志。

    Mybatis 执行过程也可使用 JDBC 差不多,首先是要获取 Connection 对象。而获取此对象是通过 BaseExecutor#getConnection() 方法。

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

    这个方法的新参看到了熟悉的 Log 接口,假设 isDebugEnabled() 方法返回false,则返回正常的 Connection 对象;而 true 情况,则是通过 org.apache.ibatis.logging.jdbc.ConnectionLogger 类获取 Connection 对象。看来真相应该就在 ConnectionLogger#newInstance() 方法里面了。

    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);
    }

    先分析 ConnectionLogger 类。此类继承 BaseJdbcLogger 并实现 InvocationHandler 接口。而实现 InvocationHandler 接口的类,说明这是一个动态代理类。当代理的原对象方法被调用时,代理对象会执行 invoke() 方法。所以 ConnectionLogger 类中,肯定实现了 invoke() 方法。

    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
        if ("prepareStatement".equals(method.getName()) || "prepareCall".equals(method.getName())) {
          if (isDebugEnabled()) {
            // 输出日志
            debug(" Preparing: " + removeExtraWhitespace((String) params[0]), true);
          }
          // 返回 PreparedStatementLogger 代理对象
          PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
          return PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
        }
        // 如果方法名为:createStatement
        if ("createStatement".equals(method.getName())) {
          Statement stmt = (Statement) method.invoke(connection, params);
          // 返回 StatementLogger 对象
          return StatementLogger.newInstance(stmt, statementLog, queryStack);
        } else {
          // 执行被代理对象方法
          return method.invoke(connection, params);
        }
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }

    这个时候再回顾下 JDBC 的使用方法:

    ...
    PreparedStatement updateSales = con.prepareStatement("SELECT * FROM USER WHERE ACCOUNT = ?");
    ...

    假设 con 是代理对象,此时调用了 prepareStatement 方法,那么程序就会走到代理对象中的 invoke 方法。

    同理,Mybatis 在上面获取的 Connection 对象是 ConnectionLogger 代理对象,那么当调用 Connection 对象的 prepareStatement 方法时,也就自然就调用 ConnectionLogger 的 invoke 方法。于是,上面 Mybatis 代码中的 debug(" Preparing: " + removeExtraWhitespace((String) params[0]), true); 就可以被系统执行,从而实现输出日志。

    到此,我们知道了 Mybatis 是通过动态代理的方式,来输出调用 Connection 类中的方法时的日志。而在源代码中,不仅仅只针对 Connection 使用了代理,还对 PreparedStatement 、ResultSet 、Statement 也都是使用了代理,代理类为:PreparedStatementLogger 、ResultSetLogger 、 StatementLogger 。



沪ICP备19023445号-2号
友情链接