JDBC 调试

作者: 93张先生 | 来源:发表于2020-09-14 10:29 被阅读0次

    JDBC Log

    logging.jdbc 包下的类,通过 JDK 动态代理的方式,将 JDBC 操作通过指定的日志框架打印出来。这个功能通常在开发阶段使用 ,它可以输出 SQL 语句、用户传入的绑定参数、 SQL 语句影响行数等等信息,对调试程序来说是非常重要的。

    日志样例
    [DEBUG]2020-08-26 15:02:33.976 c.x.s.d.S.insert:143 - ==>  Preparing: INSERT INTO student (id, name, age, address, class_id) VALUES (?,?,?, ?, ?)
    [DEBUG]2020-08-26 15:02:33.977 c.x.s.d.S.insert:143 - ==> Parameters: 25(Long), tom(String), 12(Long),  china(String), 2(Long)
    

    UML 类图

    image.png

    BaseJdbcLogger

    BaseJdbcLogger 是 JdbcLog 的抽象基类,提供了输出日志需要参数和基本方法,工子类使用。参数包含 用户实参、用户调用的 SQL 类型方法,然后还有一些输出格式化日志的方法。

    public abstract class BaseJdbcLogger {
    
      // 记录了 PreparedStatement 接口中定义的常用的 set*() 方法 比如:setBoolean(int parameterIndex, boolean x),用来设置 查询参数绑定的方法
      protected static final Set<String> SET_METHODS;
      // 记录了 Statement 接口和 PreparedStatement 接口中与执行 SQL 语句相关的方法 比如:ResultSet executeQuery(),是 Statement 执行 SQL 的方法
      protected static final Set<String> EXECUTE_METHODS = new HashSet<>();
      // 记录了 PreparedStatement.set*()方法设置 键位对
      private final Map<Object, Object> columnMap = new HashMap<>();
      // 记录了 PreparedStatement.set*() 方法设置的 key
      private final List<Object> columnNames = new ArrayList<>();
    
      // 记录了 PreparedStatement.set*() 方法设置的 value
      private final List<Object> columnValues = new ArrayList<>();
      // 用于输出日志的 Log 对象
      protected final Log statementLog;
      // 记录了 SQL 的层数,用于格式化输出 SQL
      protected final int queryStack;
    
      /*
       * Default constructor
       */
      public BaseJdbcLogger(Log log, int queryStack) {
        this.statementLog = log;
        if (queryStack == 0) {
          this.queryStack = 1;
        } else {
          this.queryStack = queryStack;
        }
      }
    
      static {
        // 添加设置参数的 set*() 方法
        // PreparedStatement 中的方法,已 set 开头,参数个数 大于一的方法名称 放入 SET_METHODS
        SET_METHODS = Arrays.stream(PreparedStatement.class.getDeclaredMethods())
                .filter(method -> method.getName().startsWith("set"))
                .filter(method -> method.getParameterCount() > 1)
                .map(Method::getName)
                .collect(Collectors.toSet());
        // 添加 SQL 执行的方法
        EXECUTE_METHODS.add("execute");
        EXECUTE_METHODS.add("executeUpdate");
        EXECUTE_METHODS.add("executeQuery");
        EXECUTE_METHODS.add("addBatch");
      }
    
      protected void setColumn(Object key, Object value) {
        columnMap.put(key, value);
        columnNames.add(key);
        columnValues.add(value);
      }
    
      protected Object getColumn(Object key) {
        return columnMap.get(key);
      }
    
      /**
       * 查询实参的参数值以及参数类型
       * @return
       */
      protected String getParameterValueString() {
        List<Object> typeList = new ArrayList<>(columnValues.size());
        for (Object value : columnValues) {
          if (value == null) {
            typeList.add("null");
          } else {
            typeList.add(objectValueString(value) + "(" + value.getClass().getSimpleName() + ")");
          }
        }
        final String parameters = typeList.toString();
        return parameters.substring(1, parameters.length() - 1);
      }
    
      protected String objectValueString(Object value) {
        if (value instanceof Array) {
          try {
            return ArrayUtil.toString(((Array) value).getArray());
          } catch (SQLException e) {
            return value.toString();
          }
        }
        return value.toString();
      }
    
      protected String getColumnString() {
        return columnNames.toString();
      }
    
      /**
       * 清空实参相关集合
       */
      protected void clearColumnInfo() {
        columnMap.clear();
        columnNames.clear();
        columnValues.clear();
      }
    
      protected String removeBreakingWhitespace(String original) {
        StringTokenizer whitespaceStripper = new StringTokenizer(original);
        StringBuilder builder = new StringBuilder();
        while (whitespaceStripper.hasMoreTokens()) {
          builder.append(whitespaceStripper.nextToken());
          builder.append(" ");
        }
        return builder.toString();
      }
    
      protected boolean isDebugEnabled() {
        return statementLog.isDebugEnabled();
      }
    
      protected boolean isTraceEnabled() {
        return statementLog.isTraceEnabled();
      }
    
      protected void debug(String text, boolean input) {
        if (statementLog.isDebugEnabled()) {
          statementLog.debug(prefix(input) + text);
        }
      }
    
      protected void trace(String text, boolean input) {
        if (statementLog.isTraceEnabled()) {
          statementLog.trace(prefix(input) + text);
        }
      }
    
      private String prefix(boolean isInput) {
        char[] buffer = new char[queryStack * 2 + 2];
        Arrays.fill(buffer, '=');
        buffer[queryStack * 2 + 1] = ' ';
        if (isInput) {
          buffer[queryStack * 2] = '>';
        } else {
          buffer[0] = '<';
        }
        return new String(buffer);
      }
    
    }
    
    

    ConnectionLogger

    添加了 logging 的 connection 代理
    ConnectionLogger 继承了 BaseJdbcLogger 抽象类,其中封装了 Connection 对象,并同时实现了 InvocationHandler 接口。

    ConnectionLogger.newInstance() 方法为会为其封装的 Connection 对象创建相应的代理对象。

    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;
     }
    
     /**
      * 代理对象执行被代理对象方法的调用
      * @param proxy
      * @param method
      * @param params
      * @return
      * @throws Throwable
      */
     @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 对象创建代理对象,添加了 log 功能
           // PreparedStatementLogger 中封装了 PreparedStatement 对象,也继承了 BaseJdbcLogger 抽象类并实现了 InvocationHandler 接口
           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 {
           // 其他方法则直接调用底层 Connection 对象的相应方法
           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) {
       // 使用 JDK 动态代理的方式创建代理对象
       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 的使用

    使用了代理模式,为已经生成的 Connection 对象添加 Log 功能。

      /**
       * 为 Connection 对象添加 Log 日志功能
       * @param statementLog
       * @return
       * @throws SQLException
       */
      protected Connection getConnection(Log statementLog) throws SQLException {
        Connection connection = transaction.getConnection();
        if (statementLog.isDebugEnabled()) {
          return ConnectionLogger.newInstance(connection, statementLog, queryStack);
        } else {
          return connection;
        }
      }
    

    类似功能

    PreparedStatementLogger、StatementLogger、ResultSetLogger 和 ConnectionLogger 实现的方式是一样的,顾不在展开叙述。

    相关文章

      网友评论

        本文标题:JDBC 调试

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