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

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

    image.png

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

    image.png

    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 的实现如下:

    1. public final class ConnectionLogger extends BaseJdbcLogger implements InvocationHandler {
    2. private final Connection connection;
    3. private ConnectionLogger(Connection conn, Log statementLog, int queryStack) {
    4. super(statementLog, queryStack);
    5. this.connection = conn;
    6. }
    7. @Override
    8. public Object invoke(Object proxy, Method method, Object[] params)
    9. throws Throwable {
    10. try {
    11. // 如果调用的是从Object继承的方法,则直接调用,不做任何其他处理
    12. if (Object.class.equals(method.getDeclaringClass())) {
    13. return method.invoke(this, params);
    14. }
    15. // 如果调用的是prepareStatement()方法、prepareCall()方法或createStatement()方法
    16. // 则在创建相应的statement对象后,为其创建代理对象并返回该代理对象
    17. if ("prepareStatement".equals(method.getName())) {
    18. // 输出日志
    19. if (isDebugEnabled()) {
    20. debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);
    21. }
    22. // 调用Connection的prepareStatement()方法,得到PreparedStatement对象
    23. PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
    24. // 为PreparedStatement对象创建代理对象
    25. stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
    26. return stmt;
    27. } else if ("prepareCall".equals(method.getName())) {
    28. // 输出日志
    29. if (isDebugEnabled()) {
    30. debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);
    31. }
    32. PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
    33. stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
    34. return stmt;
    35. } else if ("createStatement".equals(method.getName())) {
    36. Statement stmt = (Statement) method.invoke(connection, params);
    37. stmt = StatementLogger.newInstance(stmt, statementLog, queryStack);
    38. return stmt;
    39. } else {
    40. return method.invoke(connection, params);
    41. }
    42. } catch (Throwable t) {
    43. throw ExceptionUtil.unwrapThrowable(t);
    44. }
    45. }
    46. /*
    47. * Creates a logging version of a connection
    48. *
    49. * @param conn - the original connection
    50. * @return - the connection with logging
    51. */
    52. public static Connection newInstance(Connection conn, Log statementLog, int queryStack) {
    53. // 使用动态代理的方式创建代理对象
    54. InvocationHandler handler = new ConnectionLogger(conn, statementLog, queryStack);
    55. ClassLoader cl = Connection.class.getClassLoader();
    56. return (Connection) Proxy.newProxyInstance(cl, new Class[]{Connection.class}, handler);
    57. }
    58. /*
    59. * return the wrapped connection
    60. *
    61. * @return the connection
    62. */
    63. public Connection getConnection() {
    64. return connection;
    65. }
    66. }

    其他子类的实现与 ConnectionLogger 类似,不在赘述。

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

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

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

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

    作者:殷建卫 链接:https://www.yuque.com/yinjianwei/vyrvkf/fyog0c 来源:殷建卫 - 架构笔记 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。