mybatis打印JDBC日志源码分析(JDK动态代理)

mybatis支持不同的数据库,具体访问数据库是由不同的数据库厂商提供的驱动jar包。
比如:mysql的mysql-connector-java.jar

不同厂商提供的提供的jar也是基于JDK下的java.sql包中的接口去实现的
比如:不同的数据库厂商基于Connection接口实现了自家的Connection访问数据库类

不同厂商只要基于JDK提供的接口实现类,就能访问数据库
此处不得不赞美面向接口编程的精妙之处

mybatis框架的作用是把数据库的元数据转成java对象,方便java代码操作,具体的数据库访问操作由JDBC完成。但是很多时候我们想要看到mybaits和jdbc之间的交互痕迹,比如:mybatis调用了JDBC的哪些方法,传了什么参数,得到了什么执行结果。

如果打出这些sql日志方便我们bug排查和分析,也可以根据sql优化代码,比如:通过优化mybatis的缓存使用,减少数据访问次数,减少数据库压力,提高系统响应时间。

  1. 实现JDBC sql日志打印的类:
    在这里插入图片描述
  2. 类的继承关系:
    在这里插入图片描述

connection打印日志的代理类ConnectionLogger,这个命名有点误导,看命名不会把它当作是一个proxy,但是看到他实现了InvocationHandler接口就知道这是一个代理类了。

代理类持有一个connection接口,并不是具体的类,具体实现类看jdbc接入的是什么数据库的connection实现类,再次体现了面向接口编程的精妙之处😂,也体现了java多态的特性。

  1. 下面看ConnectionLogger源码注释:
/**
 * Connection proxy to add logging
 * BaseJdbcLogger实现了几个代理的共同方法
 */
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 {
      // 返回拥有method方法的class
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, params);
      }
      /**
       * jdbc包下的这些类相当于装饰器
       * 不过这里从装饰是把返回的类都装饰成了代理类
       * 判断当前的方法是否需要代理
       */
      if ("prepareStatement".equals(method.getName()) || "prepareCall".equals(method.getName())) {
        if (isDebugEnabled()) {
          debug(" Preparing: " + removeExtraWhitespace((String) params[0]), true);
        }
        // 通过反射的方式调用代理类的代理方法
        PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
        // PreparedStatement对返回值再次加工和封装,返回一个新的代理类
        // 代理和被代理的类都需要实现同样的接口,这样就可以发挥java多态的特性了
        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.
   * 
   * 创建Connection代理的方法,注意这是一个static方法
   */
  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);
  }
}

此段源码可以作为JDK动态代理和java面向接口编程的典型场景进行分析

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值