上一章的案例,配置日志级别为 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 源码篇