1. Docker镜像加速的必要性

1. 概述

本文,我们来分享 MyBatis 的日志模块,对应 logging 包。如下图所示: 包logging 包

在 Java 世界里,有很多优秀的日志框架,例如 Log4j、 Log4j2、Slf4j 等等。MyBatis 除了提供了详细的日志输出信息,还能够集成多种日志框架,其日志模块的主要功能就是集成第三方日志框架到 MyBatis 中。

2. Log 接口

在 logging 包中,定义了 Log 接口,为 MyBatis 日志模块的核心接口。代码如下:

public interface Log {

    boolean isDebugEnabled();

    boolean isTraceEnabled();

    void error(String s, Throwable e);

    void error(String s);

    void debug(String s);

    void trace(String s);

    void warn(String s);

}
  • 定义了日志的五个级别:error、debug、trace、warn 。当然,还有 info 级别,暂时没定义。

2.1 LogFactory

org.apache.ibatis.logging.LogFactory ,Log 工厂类,负责创建 Log 对象。代码如下:

public final class LogFactory {

    /**
     * Marker to be used by logging implementations that support markers.
     */
    public static final String MARKER = "MYBATIS";

    /**
     * 使用的 Log 的构造方法
     */
    private static Constructor<? extends Log> logConstructor;

    static {
        // <1> 逐个尝试,判断使用哪个 Log 的实现类,即初始化 logConstructor 属性
        tryImplementation(LogFactory::useSlf4jLogging);
        tryImplementation(LogFactory::useCommonsLogging);
        tryImplementation(LogFactory::useLog4J2Logging);
        tryImplementation(LogFactory::useLog4JLogging);
        tryImplementation(LogFactory::useJdkLogging);
        tryImplementation(LogFactory::useNoLogging);
    }

    private LogFactory() {
        // disable construction
    }

    public static Log getLog(Class<?> clazz) {
        return getLog(clazz.getName());
    }

    public static Log getLog(String logger) {
        try {
            return logConstructor.newInstance(logger);
        } catch (Throwable t) {
            throw new LogException("Error creating logger for logger " + logger + ".  Cause: " + t, t);
        }
    }

    public static synchronized void useCustomLogging(Class<? extends Log> clazz) {
        setImplementation(clazz);
    }

    public static synchronized void useSlf4jLogging() {
        setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);
    }

    public static synchronized void useCommonsLogging() {
        setImplementation(org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl.class);
    }

    public static synchronized void useLog4JLogging() {
        setImplementation(org.apache.ibatis.logging.log4j.Log4jImpl.class);
    }

    public static synchronized void useLog4J2Logging() {
        setImplementation(org.apache.ibatis.logging.log4j2.Log4j2Impl.class);
    }

    public static synchronized void useJdkLogging() {
        setImplementation(org.apache.ibatis.logging.jdk14.Jdk14LoggingImpl.class);
    }

    public static synchronized void useStdOutLogging() {
        setImplementation(org.apache.ibatis.logging.stdout.StdOutImpl.class);
    }

    public static synchronized void useNoLogging() {
        setImplementation(org.apache.ibatis.logging.nologging.NoLoggingImpl.class);
    }

    private static void tryImplementation(Runnable runnable) {
        if (logConstructor == null) {
            try {
                runnable.run();
            } catch (Throwable t) {
                // ignore
            }
        }
    }

    private static void setImplementation(Class<? extends Log> implClass) {
        try {
            // 获得参数为 String 的构造方法
            Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
            // 创建 Log 对象
            Log log = candidate.newInstance(LogFactory.class.getName());
            if (log.isDebugEnabled()) {
                log.debug("Logging initialized using '" + implClass + "' adapter.");
            }
            // 创建成功,意味着可以使用,设置为 logConstructor
            logConstructor = candidate;
        } catch (Throwable t) {
            throw new LogException("Error setting Log implementation.  Cause: " + t, t);
        }
    }

}
  • logConstructor 静态属性,使用的 Log 的构造方法。后续,通过该构造方法,创建对应的 Log 对象。

  • <1> 处,在静态代码块中,基于 tryImplementation 方法,逐个尝试,判断使用哪个 Log 的实现类,即初始化 logConstructor 属性。因为只要 logConstructor 静态属性被设置成功,tryImplementation 方法,就会直接返回。

  • #setImplementation(Class<? extends Log> implClass) 方法,尝试设置 logConstructor 静态属性。代码如下:

    • 首先,获得参数为 String 的构造方法。
    • 然后,创建 Log 对象。若创建成功,则意味着可以使用,设置为 logConstructor 静态属性。
  • #getLog(...) 静态方法,创建 Log 对象。代码如下:

    public static Log getLog(Class<?> clazz) {
        return getLog(clazz.getName());
    }
    
    public static Log getLog(String logger) {
        try {
            return logConstructor.newInstance(logger);
        } catch (Throwable t) {
            throw new LogException("Error creating logger for logger " + logger + ".  Cause: " + t, t);
        }
    }
    
    • 通过 logConstructor 静态属性,创建 Log 对象。
  • 其它方法,主要是 #useXXX() 方法,尝试设置对应的 Log 实现类到 logConstructor 静态属性。当然,#useCustomLogging(Class<? extends Log> clazz) 方法,是设置自定义的 Log 实现类。

2.2 Log 实现类

在 logging 包下,有多个子包,对应多种不同的日志框架的实现。如下图所示:Log 实现类Log 实现类

  • 每个子包下,基本只有一个 Log 实现类。当然,jcl 和 slf4j 包下,还有 XXXLoggerImpl 类,也是 Log 实现类。

  • 以 slf4j 包为例子,Slf4jImpl 代码如下:

    public class Slf4jImpl implements Log {
    
        private Log log;
    
        public Slf4jImpl(String clazz) {
            // 使用 SLF4J 的 LoggerFactory 创建 org.slf4j.Logger 对象
            Logger logger = LoggerFactory.getLogger(clazz);
    
            // 如果是 LocationAwareLogger ,则创建 LocationAwareLoggerImpl 对象
            if (logger instanceof LocationAwareLogger) {
                try {
                    // check for slf4j >= 1.6 method signature
                    logger.getClass().getMethod("log", Marker.class, String.class, int.class, String.class, Object[].class, Throwable.class);
                    log = new LocationAwareLoggerImpl((LocationAwareLogger) logger);
                    return;
                } catch (SecurityException e) {
                    // fail-back to Slf4jLoggerImpl
                } catch (NoSuchMethodException e) {
                    // fail-back to Slf4jLoggerImpl
                }
            }
    
            // Logger is not LocationAwareLogger or slf4j version < 1.6
            // 否则,创建 Slf4jLoggerImpl 对象
            log = new Slf4jLoggerImpl(logger);
        }
    
        @Override
        public boolean isDebugEnabled() {
            return log.isDebugEnabled();
        }
    
        @Override
        public boolean isTraceEnabled() {
            return log.isTraceEnabled();
        }
    
        @Override
        public void error(String s, Throwable e) {
            log.error(s, e);
        }
    
        @Override
        public void error(String s) {
            log.error(s);
        }
    
        @Override
        public void debug(String s) {
            log.debug(s);
        }
    
        @Override
        public void trace(String s) {
            log.trace(s);
        }
    
        @Override
        public void warn(String s) {
            log.warn(s);
        }
    
    }
    
    • 在构造方法中,使用 SLF4J 的 org.slf4j.LoggerFactory 创建 org.slf4j.Logger 对象。
    • 如果是 org.slf4j.spi.LocationAwareLogger 类型,则创建 LocationAwareLoggerImpl 对象。
    • 否则,创建 Slf4jLoggerImpl 对象。
    • 具体的每个方法,调用对应的 log 对应的方法。
  • 其它 Log 实现类,胖友可以自己看看。

3. 日志自动发现机制

在 「2.1 LogFactory」 的静态代码块中,我们可以看到,MyBatis 会尝试加载不同日志框架的对应的 Log 实现类。那么,如果我们有多个日志框架存在的情况下,到底使用哪个呢?这里就涉及到了日志的自动发现机制。

  • 第一步,org.apache.ibatis.logging.LogFactory 的静态代码块,会尝试按顺序加载不同日志框架的对应的 Log 实现类。
  • 第二步,在尝试加载的日志框架存在的条件下,使用该日志框架的对应的 Log 实现类。例如:
    • 配置了 -Dorg.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger 参数,则 useCommonsLogging() 方法,会设置为使用 commons-logging 日志框架。
    • 当然,如果 slf4j 存在,则 useSlf4jLogging() 方法,会设置为使用 slf4j 日志框架。
    • 等等等。
  • 第三步,如果都找不到,则使用 useNoLogging() 方法,即不使用日志框架。

4. JDBC 日志

在 logging 包中,还有 jdbc 包,基于 JDBC 相关的操作,进行日志打印。核心类如下:

  • BaseJdbcLogger :所有 JDBC 日志实现类的基类
  • ConnectionLogger :连接日志
  • PreparedStatementLogger :预处理语句日志
  • StatementLogger :语句日志
  • ResultSetLogger :结果集日志

4.1 BaseJdbcLogger

org.apache.ibatis.logging.jdbc.BaseJdbcLogger ,所有 JDBC 日志实现类的基类。代码如下:

public abstract class BaseJdbcLogger {

    /**
     * SET 方法集合
     */
    protected static final Set<String> SET_METHODS;
    /**
     * EXECUTE 方法集合
     */
    protected static final Set<String> EXECUTE_METHODS = new HashSet<>();

    /**
     * SET 方法名称的集合
     */
    private static final Set<String> SET_METHOD_NAMES = Arrays.stream(PreparedStatement.class.getMethods())
            .filter(method -> method.getName().startsWith("set"))
            .filter(method -> method.getParameterCount() > 1)
            .map(Method::getName)
            .collect(Collectors.toSet());

    /**
     * 构造方法
     */
    static {
        SET_METHODS = Collections.unmodifiableSet(SET_METHOD_NAMES);
        EXECUTE_METHODS.add("execute");
        EXECUTE_METHODS.add("executeUpdate");
        EXECUTE_METHODS.add("executeQuery");
        EXECUTE_METHODS.add("addBatch");
    }

    /**
     * Log 对象
     */
    protected final Log statementLog;
    /**
     * 查询的列数
     */
    protected final int queryStack;

    /**
     * 记录 SQL 的参数
     */
    protected Map<Integer, String> columnMap = new HashMap<>();
    /**
     * 记录的参数列表
     */
    protected List<Object> columnValues = new ArrayList<>();

    protected BaseJdbcLogger(Log statementLog, int queryStack) {
        this.statementLog = statementLog;
        if (queryStack == 0) {
            this.queryStack = 1;
        } else {
            this.queryStack = queryStack;
        }
    }

}
  • SET_METHODS 静态属性,java.sql.PreparedStatement 所有 set 方法名的集合。即,PreparedStatement 所有 set 方法名,如下图所示:SET_METHODSSET_METHODS
  • EXECUTE_METHODS 静态属性,java.sql.Statement 所有 execute 相关方法名的集合。即,Statement 所有 execute 相关方法名,如下图所示:EXECUTE_METHODSEXECUTE_METHODS
  • statementLog 属性,Log 对象。
  • queryStack 属性,查询的列数。
  • columnMap 属性,记录的 SQL 的参数集合。KEY:参数的编号。VALUE:参数名。
  • columnValues 属性,记录的参数列表。

4.2 ConnectionLogger

org.apache.ibatis.logging.jdbc.ConnectionLogger ,继承 BaseJdbcLogger 类,Connection 日志。代码如下:

public final class ConnectionLogger extends BaseJdbcLogger {

    /**
     * Connection 对象
     */
    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 方法,则打印日志,并创建 PreparedStatementLogger 对象
            if ("prepareStatement".equals(method.getName())) {
                if (isDebugEnabled()) {
                    debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);
                }
                // 创建 PreparedStatement
                PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
                // 创建 PreparedStatementLogger
                stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
                return stmt;
            }
            // 如果为 prepareCall 方法,则打印日志,并创建 PreparedStatementLogger 对象
            else if ("prepareCall".equals(method.getName())) {
                if (isDebugEnabled()) {
                    debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);
                }
                // 创建 PreparedStatement
                PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
                // 创建 PreparedStatementLogger
                stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
                return stmt;
            }
            // 如果为 createStatement 方法,则打印日志,并创建 StatementLogger 对象
            else if ("createStatement".equals(method.getName())) {
                // 创建 Statement
                Statement stmt = (Statement) method.invoke(connection, params);
                // 创建 StatementLogger
                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 对象
        InvocationHandler handler = new ConnectionLogger(conn, statementLog, queryStack);
        // 创建 Connection 代理
        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;
    }

}
  • connection 属性,Connection 对象。
  • #invoke(Object proxy, Method method, Object[] params) 方法,代理方法。代码如下:
    • 如果是 prepareStatement、prepareCall、createStatement 方法,则打印日志,并创建对应的 PreparedStatementLogger 或 StatementLogger 对象。
  • #newInstance(Connection conn, Log statementLog, int queryStack) 静态方法,创建 Connection 的代理对象。

4.3 PreparedStatementLogger

org.apache.ibatis.logging.jdbc.PreparedStatementLogger ,继承 BaseJdbcLogger 类,PreparedStatement 日志。代码如下:

public final class PreparedStatementLogger extends BaseJdbcLogger {

    /**
     * PreparedStatement 对象
     */
    private final PreparedStatement statement;

    private PreparedStatementLogger(PreparedStatement stmt, Log statementLog, int queryStack) {
        super(statementLog, queryStack);
        this.statement = stmt;
    }

    @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);
            }
            // 如果为 execute 相关方法
            if (EXECUTE_METHODS.contains(method.getName())) {
                if (isDebugEnabled()) {
                    debug("Parameters: " + getParameterValueString(), true); // <1> 打印参数
                }
                // 清空 columnMap 和 columnValues
                clearColumnInfo();
                // 执行方法
                if ("executeQuery".equals(method.getName())) {
                    ResultSet rs = (ResultSet) method.invoke(statement, params);
                    return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
                } else {
                    return method.invoke(statement, params);
                }
            }
            // 如果为 set 相关方法
            else if (SET_METHODS.contains(method.getName())) {
                if ("setNull".equals(method.getName())) { // 设置 NULL
                    // 添加到 columnMap 和 columnValues 中
                    setColumn(params[0], null);
                } else {
                    // 添加到 columnMap 和 columnValues 中
                    setColumn(params[0], params[1]);
                }
                return method.invoke(statement, params);
            }
            // 如果为 get 相关方法
            else if ("getResultSet".equals(method.getName())) {
                ResultSet rs = (ResultSet) method.invoke(statement, params);
                return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
            }
            // 如果为 getUpdateCount 方法
            else if ("getUpdateCount".equals(method.getName())) {
                int updateCount = (Integer) method.invoke(statement, params);
                if (updateCount != -1) {
                    debug("   Updates: " + updateCount, false);
                }
                return updateCount;
            } else {
                return method.invoke(statement, params);
            }
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
    }

    /**
     * Creates a logging version of a PreparedStatement.
     *
     * @param stmt - the statement
     * @return - the proxy
     */
    public static PreparedStatement newInstance(PreparedStatement stmt, Log statementLog, int queryStack) {
        // 创建 InvocationHandler 对象
        InvocationHandler handler = new PreparedStatementLogger(stmt, statementLog, queryStack);
        // 创建 PreparedStatement 代理
        ClassLoader cl = PreparedStatement.class.getClassLoader();
        return (PreparedStatement) Proxy.newProxyInstance(cl, new Class[]{PreparedStatement.class}, handler);
    }

    /**
     * Return the wrapped prepared statement.
     *
     * @return the PreparedStatement
     */
    public PreparedStatement getPreparedStatement() {
        return statement;
    }

}
  • statement 属性,PreparedStatement 对象。
  • #invoke(Object proxy, Method method, Object[] params) 方法,代理方法。代码如下:
    • <1> 处,如果是 execute 相关方法,则打印参数。打印的格式如下:

      16:50:09.149 [main] DEBUG o.a.i.l.j.PreparedStatementLogger - Parameters: 1(Integer), 101(String)
      
      • 其中,Parameters: 前缀,是通过 #getParameterValueString() 方法,拼接 columnMap 和 columnValues 属性,组合而成。
    • 如果是 set 相关方法,则添加到 columnMap 和 columnValues 属性中。

    • 如果是 getResultSet 方法,则创建 ResultSetLogger 对象。

    • 如果是 getUpdateCount 方法,则打印更新行数。

  • #newInstance(PreparedStatement stmt, Log statementLog, int queryStack) 静态方法,创建 PreparedStatement 的代理对象。

4.4 StatementLogger

org.apache.ibatis.logging.jdbc.StatementLogger ,继承 BaseJdbcLogger 类,Statement 日志。代码如下:

public final class StatementLogger extends BaseJdbcLogger {

    /**
     * Statement 对象
     */
    private final Statement statement;

    private StatementLogger(Statement stmt, Log statementLog, int queryStack) {
        super(statementLog, queryStack);
        this.statement = stmt;
    }

    @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);
            }
            // 如果为 execute 相关方法
            if (EXECUTE_METHODS.contains(method.getName())) {
                if (isDebugEnabled()) {
                    debug("Parameters: " + getParameterValueString(), true); // 打印参数
                }
                // 清空 columnMap 和 columnValues
                clearColumnInfo();
                // 执行方法
                if ("executeQuery".equals(method.getName())) {
                    ResultSet rs = (ResultSet) method.invoke(statement, params);
                    return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
                } else {
                    return method.invoke(statement, params);
                }
            }
            // 如果为 get 相关方法
            else if ("getResultSet".equals(method.getName())) {
                ResultSet rs = (ResultSet) method.invoke(statement, params);
                return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
            }
            // 如果为 getUpdateCount 方法
            else if ("getUpdateCount".equals(method.getName())) {
                int updateCount = (Integer) method.invoke(statement, params);
                if (updateCount != -1) {
                    debug("   Updates: " + updateCount, false);
                }
                return updateCount;
            } else {
                return method.invoke(statement, params);
            }
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
    }

    /**
     * Creates a logging version of a Statement.
     *
     * @param stmt - the statement
     * @return - the proxy
     */
    public static Statement newInstance(Statement stmt, Log statementLog, int queryStack) {
        // 创建 InvocationHandler 对象
        InvocationHandler handler = new StatementLogger(stmt, statementLog, queryStack);
        // 创建 Statement 代理
        ClassLoader cl = Statement.class.getClassLoader();
        return (Statement) Proxy.newProxyInstance(cl, new Class[]{Statement.class}, handler);
    }

    /**
     * Return the wrapped prepared statement.
     *
     * @return the Statement
     */
    public Statement getStatement() {
        return statement;
    }

}
  • 和 PreparedStatementLogger 类似,胖友自己看。

4.5 ResultSetLogger

org.apache.ibatis.logging.jdbc.ResultSetLogger ,继承 BaseJdbcLogger 类,ResultSet 日志。代码如下:

public final class ResultSetLogger extends BaseJdbcLogger {

    /**
     * ResultSet 对象
     */
    private final ResultSet rs;
    /**
     * 读取的列数
     */
    private final int rows;
    /**
     * 第一行
     */
    private boolean first = true;

    private ResultSetLogger(ResultSet rs, Log statementLog, int queryStack) {
        super(statementLog, queryStack);
        this.rs = rs;
        this.rows = 0;
    }

    @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);
            }
            // 调用方法
            Object o = method.invoke(rs, params);
            // 如果为 next 方法
            if ("next".equals(method.getName())) {
                // 遍历是否还有下一条
                if (((Boolean) o)) {
                    // 增加 rows
                    rows++;
                    // 如果是 debug 日志级别,并且是第一行,则打印表头
                    if (isTraceEnabled()) {
                        ResultSetMetaData rsmd = rs.getMetaData();
                        final int columnCount = rsmd.getColumnCount();
                        if (first) {
                            first = false;
                            // 打印列头
                            printColumnHeaders(rsmd, columnCount);
                        }
                        // 打印该行的记录
                        printColumnValues(columnCount);
                    }
                } else {
                    // 打印总行数
                    debug("     Total: " + rows, false);
                }
            }
            // 清空 columnMap 和 columnValues
            clearColumnInfo();
            return o;
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
    }

    /**
     * Creates a logging version of a ResultSet.
     *
     * @param rs - the ResultSet to proxy
     * @return - the ResultSet with logging
     */
    public static ResultSet newInstance(ResultSet rs, Log statementLog, int queryStack) {
        // 创建 InvocationHandler 对象
        InvocationHandler handler = new ResultSetLogger(rs, statementLog, queryStack);
        // 创建 ResultSet 代理
        ClassLoader cl = ResultSet.class.getClassLoader();
        return (ResultSet) Proxy.newProxyInstance(cl, new Class[]{ResultSet.class}, handler);
    }

    /**
     * Get the wrapped result set.
     *
     * @return the resultSet
     */
    public ResultSet getResultSet() {
        return rs;
    }

    private void printColumnHeaders(ResultSetMetaData rsmd, int columnCount) throws SQLException {
        StringBuilder row = new StringBuilder();
        row.append("   Columns: ");
        for (int i = 1; i <= columnCount; i++) {
            if (i > 1) {
                row.append(", ");
            }
            row.append(rsmd.getColumnLabel(i));
        }
        trace(row.toString(), false);
    }

    private void printColumnValues(int columnCount) {
        StringBuilder row = new StringBuilder();
        row.append("       Row: ");
        for (int i = 1; i <= columnCount; i++) {
            if (i > 1) {
                row.append(", ");
            }
            try {
                if (rs.getObject(i) != null) {
                    row.append(rs.getObject(i).toString());
                } else {
                    row.append("NULL");
                }
            } catch (SQLException e) {
                row.append("Cannot read value: " + e.getMessage());
            }
        }
        trace(row.toString(), false);
    }

}
  • rows 属性,读取的列数。

  • first 属性,是否是第一行。

  • #invoke(Object proxy, Method method, Object[] params) 方法,代理方法。代码如下:

    • 如果是 next 方法,则根据是否还有下一条记录,打印相关日志。打印的格式如下:

      16:50:09.156 [main] DEBUG o.a.i.l.j.ResultSetLogger -       Row: 1, 101, 101
      16:50:09.156 [main] DEBUG o.a.i.l.j.ResultSetLogger -       Row: 2, 102, 102
      16:50:09.156 [main] DEBUG o.a.i.l.j.ResultSetLogger -     Total: 2
      
      • 其中,Row: 前缀,是通过 #printColumnValues(int columnCount) 方法,拼接该行的记录。
      • 其中,Total: 前缀,是打印总行数。
  • #newInstance(ResultSet rs, Log statementLog, int queryStack) 静态方法,创建 ResultSet 的代理对象。

4.6 使用示例

在 BaseExecutor 中,代码如下:

protected Connection getConnection(Log statementLog) throws SQLException {
    Connection connection = transaction.getConnection();
    if (statementLog.isDebugEnabled()) { // 开启 debug 的情况下,才返回代理
        return ConnectionLogger.newInstance(connection, statementLog, queryStack);
    } else {
        return connection;
    }
}
  • 在开启 debug 的情况下,才返回 Connection 的代理对象。这样,所有该 Connection 产生的 PreparedStatement、ResultSet 等等,都会被代理,从而实现日志的打印。

5. 集成三方框架

在 logging 包中,还有 commons-logging、log4j、log4j2、slf4j 等等包,实现集成三方日志框架到 MyBatis 中。以 log4j 包为例子,代码如下:

public class Log4jImpl implements Log {

    private static final String FQCN = Log4jImpl.class.getName();

    private final Logger log;

    public Log4jImpl(String clazz) {
        this.log = Logger.getLogger(clazz);
    }

    @Override
    public boolean isDebugEnabled() {
        return log.isDebugEnabled();
    }

    @Override
    public boolean isTraceEnabled() {
        return log.isTraceEnabled();
    }

    @Override
    public void error(String s, Throwable e) {
        log.log(FQCN, Level.ERROR, s, e);
    }

    @Override
    public void error(String s) {
        log.log(FQCN, Level.ERROR, s, null);
    }

    @Override
    public void debug(String s) {
        log.log(FQCN, Level.DEBUG, s, null);
    }

    @Override
    public void trace(String s) {
        log.log(FQCN, Level.TRACE, s, null);
    }

    @Override
    public void warn(String s) {
        log.log(FQCN, Level.WARN, s, null);
    }

}
  • 通过集成 org.apache.log4j.Logger 类,实现 Log 接口,从而集成到 MyBatis 的日志体系中。

更多推荐