MyBatis 源码篇-日志模块2
上一章的案例,配置日志级别为 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的更多相关文章
- MyBatis 源码篇-日志模块1
在 Java 开发中常用的日志框架有 Log4j.Log4j2.Apache Common Log.java.util.logging.slf4j 等,这些日志框架对外提供的接口各不相同.本章详细描述 ...
- MyBatis 源码篇-插件模块
本章主要描述 MyBatis 插件模块的原理,从以下两点出发: MyBatis 是如何加载插件配置的? MyBatis 是如何实现用户使用自定义拦截器对 SQL 语句执行过程中的某一点进行拦截的? 示 ...
- MyBatis 源码篇-MyBatis-Spring 剖析
本章通过分析 mybatis-spring-x.x.x.jar Jar 包中的源码,了解 MyBatis 是如何与 Spring 进行集成的. Spring 配置文件 MyBatis 与 Spring ...
- MyBatis 源码篇-Transaction
本章简单介绍一下 MyBatis 的事务模块,这块内容比较简单,主要为后面介绍 mybatis-spring-1.**.jar(MyBatis 与 Spring 集成)中的事务模块做准备. 类图结构 ...
- MyBatis 源码篇-DataSource
本章介绍 MyBatis 提供的数据源模块,为后面与 Spring 集成做铺垫,从以下三点出发: 描述 MyBatis 数据源模块的类图结构: MyBatis 是如何集成第三方数据源组件的: Pool ...
- MyBatis 源码篇-资源加载
本章主要描述 MyBatis 资源加载模块中的 ClassLoaderWrapper 类和 Java 加载配置文件的三种方式. ClassLoaderWrapper 上一章的案例,使用 org.apa ...
- MyBatis 源码篇-SQL 执行的流程
本章通过一个简单的例子,来了解 MyBatis 执行一条 SQL 语句的大致过程是怎样的. 案例代码如下所示: public class MybatisTest { @Test public void ...
- MyBatis 源码篇-整体架构
MyBatis 的整体架构分为三层, 分别是基础支持层.核心处理层和接口层,如下图所示. 基础支持层 反射模块 该模块对 Java 原生的反射进行了良好的封装,提供了更加简洁易用的 API ,方便上层 ...
- myBatis源码解析-日志篇(1)
上半年在进行知识储备,下半年争取写一点好的博客来记录自己源码之路.在学习源码的路上也掌握了一些设计模式,可所谓一举两得.本次打算写Mybatis的源码解读. 准备工作 1. 下载mybatis源码 下 ...
随机推荐
- Ryu控制器安装部署和入门
Ryu官网简介,原滋原味 Ryu is a component-based software defined networking framework. Ryu provides software c ...
- Leetcode题目283.移动零(简单)
题目描述: 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序. 示例: 输入: [0,1,0,3,12] 输出: [1,3,12,0,0] 说明: 必须在原 ...
- Flutter移动电商实战 --(38)路由_Fluro中Handler编写方法
在main.dart中初始化Fluro 编写handler 在lib下新建routers文件夹,表示里面要很多路由相关的文件 我们声明一个Handler,在里面handlerFunc固定的两个参数 重 ...
- PHP学习之工厂方法模式
<?php //工厂方法模式 interface Doing { function eat(); function sleep(); } class Cat implements Doing { ...
- 在调试linux的休眠功能时如何打开调试信息开关?
答:在bootargs中添加参数no_console_suspend即可进行调试
- kotlin之注解
注解是用来代码添加元数据的一种手段,要声明一个 注解,需要在类之前添加annotation修饰符 annotation class demo 注解其他属性,可以通过向注解类添加元注解的方式来指定 @T ...
- c++拷贝文件-传统处理异常(学习)
#include <iostream>#include <stdio.h> using namespace std; int my_copy(const char* src_f ...
- 阶段5 3.微服务项目【学成在线】_day04 页面静态化_22-页面静态化-静态化测试-静态化程序测试
测试service内些的静态化的方法 先新建一个测试类 模板的id 放到下拉的静态数据里面 这样这条数据 就是用用的轮播图005这个模板 把这条数据静态化 进入到断点里面.先获取数据模型 获取模板时 ...
- expect实现免交互
如果想写一个能够自动处理输入输出的脚本又不想面对C或Perl,那么expect是最好的选择.它可以用来做一些Linux下无法做到交互的一些命令操作. (1).安装和使用expect expect是不会 ...
- Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.5:test
解决方法: 打包跳过测试有两种方法 一是命令行 mvn clean package -Dmaven.test.skip=true 二是写入pom文件 <plugin> <groupI ...