上一章的案例,配置日志级别为 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;}@Overridepublic 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的代理类ConnectionLoggerif (statementLog.isDebugEnabled()) {return ConnectionLogger.newInstance(connection, statementLog, queryStack);} else {return connection;}}
从源码中可以看出,如果日志级别为 debug,则会创建代理类 ConnectionLogger,否则只会使用正常的 Connection 对象。
作者:殷建卫 链接:https://www.yuque.com/yinjianwei/vyrvkf/fyog0c 来源:殷建卫 - 架构笔记 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
