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 调试

    JDBC Log logging.jdbc 包下的类,通过 JDK 动态代理的方式,将 JDBC 操作通过指定的日...

  • java.lang.ClassNotFoundException

    最近在学 JavaWeb ,在用 Servlet +Jdbc 时遇到以下错误,调试了代码许久,都没发现代码错误,最...

  • MyBatis原理(一)——概述

    此系列为鲁班大叔的MyBatis源码的学习总结。我调试的MyBatis版本为3.5.7 一、JDBC执行过程 先回...

  • JDBC

    JDBC原理: JDBC: 抽取JDBC工具类 : JDBCUtils JDBC控制事务:

  • JDBC 的使用

    JDBC JDBC什么是JDBCJDBC 的使用JDBC 的数据类型 什么是JDBC JDBC(Java Data...

  • Java和MySQL简建立连接

    JDBC JDBC插入多条数据 JDBC查询 JDBC动态传参 JDBC回滚 JDBC将数据库中的信息装入List...

  • JDBC

    JDBC JDBC:Java DataBase Connectivity JDBC的本质是什么?JDBC是SUN公...

  • java异常合集

    jdbc com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorExce...

  • Day05--JDBC

    JDBC: 抽取JDBC工具类:JDBCUtils JDBC控制事务

  • JDBC

    JDBC: 抽取JDBC工具类 : JDBCUtils JDBC控制事务:

网友评论

    本文标题:JDBC 调试

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