美文网首页
Mybatis源码-日志模块(2)

Mybatis源码-日志模块(2)

作者: lazyguy | 来源:发表于2019-07-31 16:50 被阅读0次
    image.png

      在logging模块中有一个包jdbc,这个包虽然和其他包并列,但并不是一种简单的Log代理实现。而是在mybatis操作jdbc的时候,用记录相关SQL的复杂组件。我们最常用的打印sql日志的功能就是由这个模块实现的。

      所有StatementLogger,ConnectionLogger,ResultSetLogger,PreparedStatementLoggerr除了继承BaseJdbcLogger以外也同时实现了InvocationHandler接口。也就是说mybatis到底是怎么去实现打印jdbc相关信息的日志的呢?其实就是利用动态代理,拦截了我们熟悉的Statement,Connection,ResultSet,PreparedStatement中相关的方法,在执行前后加入了打印日志的逻辑。这是一种典型的AOP编程场景的运用。

    image.png
      因为对jdbc的操作,通常是Connection->Statement->Resultset所以这几个代理类其实最开始的入口是从生成Connection的代理开始。
    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本身的方法,不走Connection,而是直接调用,也就是object本身的方法不做任何代理。
          if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, params);
          }
          //如果是Connection的prepareStatement方法,打印要执行的sql,这里的sql会美化一次
          if ("prepareStatement".equals(method.getName())) {
            if (isDebugEnabled()) {
              debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);
            }
            //真正调用Connection的prepareStatement方法,这是反射和动态代理的典型逻辑了
            PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
            //这里是在将我们创建的PreparedStatement生成动态代理。因为PreparedStatement也是需要被代理的。
            stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
            //返回被代理过的PreparedStatement
            return stmt;
          } else if ("prepareCall".equals(method.getName())) {
            //prepareCall方法和上面一模一样的,感觉可以在一个分支里啊,干嘛又写一遍?
            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())) {
            //createStatement方法不同的是不会有日志打印,为啥?因为这个方法不会传sql
            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.
       * 将生成代理的步骤也在InvocationHandler中封装成一个工具类方法。
       * 这样创建代理就仅仅只需要调用这样一个API就完成了。
       * @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;
      }
    
    }
    
    PreparedStatementLogger的invoke方法
    @Override
      public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
        try {
          //same
          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();
            //如果是查询sql,ResultSet也需要被代理
            if ("executeQuery".equals(method.getName())) {
              ResultSet rs = (ResultSet) method.invoke(statement, params);
              return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
            } else {
              return method.invoke(statement, params);
            }
    
          } else if (SET_METHODS.contains(method.getName())) {
            //如果是设置参数的方法,先缓存参数,再调用方法
            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);
        }
      }
    

    相关文章

      网友评论

          本文标题:Mybatis源码-日志模块(2)

          本文链接:https://www.haomeiwen.com/subject/miazrctx.html