上一章的案例,配置日志级别为 debug,执行一个简单的查询操作,会将 JDBC 操作打印出来。本章通过 MyBatis 日志部分源码分析它是如何实现日志打印的。

在 MyBatis 的日志模块中有一个 jdbc package,package 中的内容如下图所示:

BaseJdbcLogger 是一个抽象类,它是 jdbc package 下其他类的父类,类继承关系如下图所示:

BaseJdbcLogger 类中定义了一些公共集合和简单的工具方法,提供给子类使用。

BaseJdbcLogger 的子类有如下特性:

  • ConnectionLogger:Connection 的代理类,封装了 Connection 对象,继承了 BaseJdbcLogger 抽象类并实现了 InvocationHandler 接口,newInstance() 方法会为其封装的 Connection 对象创建相应的代理对象;
  • PreparedStatementLogger:PreparedStatement 的代理类,封装了 PreparedStatement 对象,继承了 BaseJdbcLogger 抽象类并实现了 InvocationHandler 接口,newInstance() 方法的实现与 Connection 的类似;
  • StatementLogger:与 PreparedStatementLogger 类似;
  • ResultSetLogger:ResultSet 的代理类,封装了 ResultSet 对象,继承了 BaseJdbcLogger 抽象类并实现了 InvocationHandler 接口,newInstance() 方法的实现与 Connection 的类似;

MyBatis 就是通过动态代理的方式,对 JDBC 原生类进行了一层封装,在代理类的 invoke 方法中添加对应 JDBC 操作的日志打印功能。

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继承的方法,则直接调用,不做任何其他处理
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对象创建代理对象
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 {
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) {
// 使用动态代理的方式创建代理对象
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 类似,不在赘述。

ConnectionLogger 会创建 PreparedStatementLogger 或 StatementLogger,PreparedStatementLogger 会创建 ResultSetLogger,这样就保证了每一步 JDBC 操作在 debug 日志级别下都有日志输出。

那么 ConnectionLogger 又是在哪里创建的呢?跟踪 SQL 的执行流程,在 org.apache.ibatis.executor.BaseExecutor#getConnection 方法中找到 ConnectionLogger 的创建代码:

protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
// 判断日志级别为debug,则创建Connection的代理类ConnectionLogger
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}

从源码中可以看出,如果日志级别为 debug,则会创建代理类 ConnectionLogger,否则只会使用正常的 Connection 对象。

MyBatis 源码篇

MyBatis 源码篇-日志模块2的更多相关文章

  1. MyBatis 源码篇-日志模块1

    在 Java 开发中常用的日志框架有 Log4j.Log4j2.Apache Common Log.java.util.logging.slf4j 等,这些日志框架对外提供的接口各不相同.本章详细描述 ...

  2. MyBatis 源码篇-插件模块

    本章主要描述 MyBatis 插件模块的原理,从以下两点出发: MyBatis 是如何加载插件配置的? MyBatis 是如何实现用户使用自定义拦截器对 SQL 语句执行过程中的某一点进行拦截的? 示 ...

  3. MyBatis 源码篇-MyBatis-Spring 剖析

    本章通过分析 mybatis-spring-x.x.x.jar Jar 包中的源码,了解 MyBatis 是如何与 Spring 进行集成的. Spring 配置文件 MyBatis 与 Spring ...

  4. MyBatis 源码篇-Transaction

    本章简单介绍一下 MyBatis 的事务模块,这块内容比较简单,主要为后面介绍 mybatis-spring-1.**.jar(MyBatis 与 Spring 集成)中的事务模块做准备. 类图结构 ...

  5. MyBatis 源码篇-DataSource

    本章介绍 MyBatis 提供的数据源模块,为后面与 Spring 集成做铺垫,从以下三点出发: 描述 MyBatis 数据源模块的类图结构: MyBatis 是如何集成第三方数据源组件的: Pool ...

  6. MyBatis 源码篇-资源加载

    本章主要描述 MyBatis 资源加载模块中的 ClassLoaderWrapper 类和 Java 加载配置文件的三种方式. ClassLoaderWrapper 上一章的案例,使用 org.apa ...

  7. MyBatis 源码篇-SQL 执行的流程

    本章通过一个简单的例子,来了解 MyBatis 执行一条 SQL 语句的大致过程是怎样的. 案例代码如下所示: public class MybatisTest { @Test public void ...

  8. MyBatis 源码篇-整体架构

    MyBatis 的整体架构分为三层, 分别是基础支持层.核心处理层和接口层,如下图所示. 基础支持层 反射模块 该模块对 Java 原生的反射进行了良好的封装,提供了更加简洁易用的 API ,方便上层 ...

  9. myBatis源码解析-日志篇(1)

    上半年在进行知识储备,下半年争取写一点好的博客来记录自己源码之路.在学习源码的路上也掌握了一些设计模式,可所谓一举两得.本次打算写Mybatis的源码解读. 准备工作 1. 下载mybatis源码 下 ...

随机推荐

  1. Reconnect due to socket error java.nio.channels.ClosedChannelException

    storm整合kafka后出现如下异常: 错误原因:有部分kafka服务器连接不上导致,检查一下是不是每个kafka都能连接到(有的kafka配置使用的是host,记得配置相同的环境) 造成异常代码段 ...

  2. Golang文件操作整理

    基本操作 文件创建 创建文件的时候,一定要注意权限问题,一般默认的文件权限是 0666 关于权限的相关内容,具体可以参考鸟叔p141 这里还是再回顾下,文件属性 r w x r w x r w x,第 ...

  3. Programming a Hearthstone agent using Monte Carlo Tree Search(chapter one)

    Markus Heikki AnderssonHåkon HelgesenHesselberg Master of Science in Computer Science Submission dat ...

  4. java 测试框架 TestNG

    Java中print.printf.println的区别 printf主要是继承了C语言的printf的一些特性,可以进行格式化输出 print就是一般的标准输出,但是不换行 println和prin ...

  5. 官网引用的axios,lodash文件在脚手架中如何使用?

    对于官网属性与侦听器模块,所引用的以下文件在脚手架中如何使用? <script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/ ...

  6. LayerDrawable

    层图形对象,包含一个Drawable数组,然后按照数组对应的顺序来绘制他们,索引 值最大的Drawable会被绘制在最上层!虽然这些Drawable会有交叉或者重叠的区域,但 他们位于不同的层,所以并 ...

  7. Swift 循环

    循环类型 Swift 语言提供了以下几种循环类型.点击链接查看每个类型的详细描述: 循环类型 描述 for-in 遍历一个集合里面的所有元素,例如由数字表示的区间.数组中的元素.字符串中的字符. fo ...

  8. 安装php扩展sphinx-1.2.0.tgz和libsphinxclient0.9.9

    一.首先安装libsphinxclient(php模块需要) cd /usr/local/src/tar zxvf sphinx-0.9.9.tar.gzcd sphinx-0.9.9/api/lib ...

  9. CockroachDB学习笔记——[译]Cgo的成本与复杂性

    原文链接:https://www.cockroachlabs.com/blog/the-cost-and-complexity-of-cgo/ 原作者:Tobias Schottdorf 原文日期:D ...

  10. centos7安装过程中的安装源设置

    centos7的安装源设置:http://mirrors.aliyun.com/centos/7/os/x86_64/