前言

在程序开发过程中,为了调试方便、了解程序的运行过程,进行必要的日志输出总是免不了的。对于使用Mybatis而言,我们常见的需求是希望可以在日志中打印出Mybatis执行过程中进行数据库操作的SQL语句及其传递的参数。Mybatis的日志输出是统一管理的,它有自己的日志接口,然后在需要进行日志输出的时候使用统一的API进行日志输出。这个统一的接口是org.apache.ibatis.logging.Log。Mybatis分别基于常用的日志输出工具给出了对应的实现,比如LOG4J、SLF4J等。默认情况下Mybatis的org.apache.ibatis.logging.LogFactory会按照以下顺序依次判断当前程序下可以使用哪种日志实现,直到找到为止,如果一个实现都没有那就是最后的noLogging了,将采用NoLoggingImpl实现。

上文我们介绍了项目中如何打印日志mybatis 拦截器打印完整sql实现

日志配置入口
static {

    tryImplementation(new Runnable() {

      @Override

      public void run() {

        useSlf4jLogging();

      }

    });

    tryImplementation(new Runnable() {

      @Override

      public void run() {

        useCommonsLogging();

      }

    });

    tryImplementation(new Runnable() {

      @Override

      public void run() {

        useLog4J2Logging();

      }

    });

    tryImplementation(new Runnable() {

      @Override

      public void run() {

        useLog4JLogging();

      }

    });

    tryImplementation(new Runnable() {

      @Override

      public void run() {

        useJdkLogging();

      }

    });

    tryImplementation(new Runnable() {

      @Override

      public void run() {

        useNoLogging();

      }

    });

  }

上述的默认机制有的时候可能不能满足你的需求,比如有的时候可能你是想使用Commons-Logging,但是因为类路径中包含SLF4J的包,结果Mybatis就自动使用了SLF4J了,所以如果有自己明确想使用的日志实现时,我们应该自己来指定日志实现,这可以通过在Mybatis的全局配置文件中通过如下进行配置,这里的value是一个日志实现的别名,可选值有SLF4J、COMMONS_LOGGING、LOG4J、LOG4J2、JDK_LOGGING、STDOUT_LOGGING、NO_LOGGING,如果希望使用的日志输出实现是Mybatis自身没有实现的,我们也可以自己来实现,只需要自己定义一个类实现Mybatis的org.apache.ibatis.logging.Log接口,然后在下面的配置中把value属性改为自己的实现类的全路径名称即可。

mybatis怎么打印出sql

我们把java.sql.*,com.ibatis等的日志级别都配置成debug的,把sql打印出来了。但打印sql与这些java.sql,com.ibatis包半毛钱关系都没有。至打印日志的对象是org.apache.ibatis.logging.slf4j.Slf4jImpl(statementLog),而statementLog又是在系统启动时,扫描的logId(如com.**.Mapper.query)作为其构造参数的。是否打印日志就是这个参数是否与log4j.xml,与log4j.properties里的log4j.logger匹配来决定的。下面通过源码来分析整个过程:

SQL及执行结果日志输出概述

每个MappedStatement创建一个日志对象,可以控制每个运行时SQL语句的打印以及执行结果的打印。 日志Debug级别打印SQL语句:PreparedStatement(prepareStatement、prepareCall)创建时打印SQL语句,执行时打印参数值。Statement执行时打印SQL语句。 日志Trace级别打印执行结果。

一、MappedStatement
1.1 构建MappedStatement时指定日志 public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) { mappedStatement.configuration = configuration; // 命名空间.statementId即Mapper接口完全限定名.方法名 mappedStatement.id = id; mappedStatement.sqlSource = sqlSource; mappedStatement.statementType = StatementType.PREPARED; mappedStatement.parameterMap = new ParameterMap.Builder(configuration, “defaultParameterMap”, null, new ArrayList()).build(); mappedStatement.resultMaps = new ArrayList(); mappedStatement.sqlCommandType = sqlCommandType; mappedStatement.keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; String logId = id; // 配置日志前缀 if (configuration.getLogPrefix() != null) { ** logId = configuration.getLogPrefix() + id;** } // 每个MappedStatement(每个SQL语句)创建一个日志对象 ** mappedStatement.statementLog = LogFactory.getLog(logId);** mappedStatement.lang = configuration.getDefaultScriptingLanguageInstance(); }
二、BaseExecutor2.1 创建Connection // SimpleExecutor、BatchExecutor、ReuseExecutor调用该方法获取Connection protected Connection getConnection(Log statementLog) throws SQLException { Connection connection = transaction.getConnection(); // MappedStatement开启Debug级别日志 if (statementLog.isDebugEnabled()) { // 具有日志打印功能的Connection return ConnectionLogger.newInstance(connection, statementLog, queryStack); } else { return connection; } }
三、ConnectionLogger3.1 创建Connection代理实例 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); }
3.2 创建日志打印功能的Statement public Object invoke(Object proxy, Method method, Object[] params) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, params); } if (“prepareStatement”.equals(method.getName())) { if (isDebugEnabled()) { // 日志Debug级别打印SQL语句 debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true); } PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params); stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack); return stmt; } else if (“prepareCall”.equals(method.getName())) { if (isDebugEnabled()) { // 日志Debug级别打印SQL语句 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); } }
四、StatementLogger4.1 创建Statement代理实例 public static Statement newInstance(Statement stmt, Log statementLog, int queryStack) { InvocationHandler handler = new StatementLogger(stmt, statementLog, queryStack); ClassLoader cl = Statement.class.getClassLoader(); return (Statement) Proxy.newProxyInstance(cl, new Class[]{Statement.class}, handler); }
4.2 创建日志打印功能的ResultSet public Object invoke(Object proxy, Method method, Object[] params) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, params); }
// execute、executeUpdate、executeQuery、addBatch if (EXECUTE_METHODS.contains(method.getName())) { if (isDebugEnabled()) { // 日志Debug级别打印SQL语句 ** debug(" Executing: " + removeBreakingWhitespace((String) params[0]), true);** } 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); } } else if (“getResultSet”.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); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } }
五、PreparedStatementLogger5.1 创建PreparedStatement代理实例 public static PreparedStatement newInstance(PreparedStatement stmt, Log statementLog, int queryStack) { InvocationHandler handler = new PreparedStatementLogger(stmt, statementLog, queryStack); ClassLoader cl = PreparedStatement.class.getClassLoader(); return (PreparedStatement) Proxy.newProxyInstance(cl, new Class[]{PreparedStatement.class, CallableStatement.class}, handler); }
5.2 创建日志打印功能的ResultSet public Object invoke(Object proxy, Method method, Object[] params) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, params); }
// execute、executeUpdate、executeQuery、addBatch if (EXECUTE_METHODS.contains(method.getName())) { if (isDebugEnabled()) { // 日志Debug级别打印参数值 debug(“Parameters: " + getParameterValueString(), true); } 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); } } else if (SET_METHODS.contains(method.getName())) { if (“setNull”.equals(method.getName())) { setColumn(params[0], null); } else { setColumn(params[0], params[1]); } return method.invoke(statement, params); } else if (“getResultSet”.equals(method.getName())) { ResultSet rs = (ResultSet) method.invoke(statement, params); return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack); } 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); } }
六、ResultSetLogger 日志TRACE级别打印执行结果6.1 创建ResultSet代理实例 public static ResultSet newInstance(ResultSet rs, Log statementLog, int queryStack) { InvocationHandler handler = new ResultSetLogger(rs, statementLog, queryStack); ClassLoader cl = ResultSet.class.getClassLoader(); return (ResultSet) Proxy.newProxyInstance(cl, new Class[]{ResultSet.class}, handler); }
6.2 打印执行结果 public Object invoke(Object proxy, Method method, Object[] params) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, params); } Object o = method.invoke(rs, params); if (“next”.equals(method.getName())) { if (((Boolean) o)) { rows++; 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); } } clearColumnInfo(); return o; } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } }
6.3 打印列头 private void printColumnHeaders(ResultSetMetaData rsmd, int columnCount) throws SQLException { StringBuilder row = new StringBuilder(); row.append(" Columns: “); for (int i = 1; i <= columnCount; i++) { if (BLOB_TYPES.contains(rsmd.getColumnType(i))) { blobColumns.add(i); } String colname = rsmd.getColumnLabel(i); row.append(colname); if (i != columnCount) { row.append(”, "); } } trace(row.toString(), false); }
6.4 打印列值 private void printColumnValues(int columnCount) { StringBuilder row = new StringBuilder(); row.append(" Row: “); for (int i = 1; i <= columnCount; i++) { String colname; try { if (blobColumns.contains(i)) { colname = “<>”; } else { colname = rs.getString(i); } } catch (SQLException e) { // generally can’t call getString() on a BLOB column colname = “<>”; } row.append(colname); if (i != columnCount) { row.append(”, "); } } trace(row.toString(), false); }