mybatis源码系列(四)—— Log模块

示例代码

InputStream inputStream = new FileInputStream(new File("src/main/resources/mybatis-config.xml"));
// 1.加载配置文件创建configuration对象,创建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 2.创建SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 3.获取Mapper接口动态代理
BaseTermMapper mapper = sqlSession.getMapper(BaseTermMapper.class);
// 4.动态代理回调sqlSession中的查询方法,sqlSession将查询方法转发给Executor,
// Executor基于JDBC访问数据库获取数据并通过反射将数据转换成POJO并返回
BaseTerm baseTerm = mapper.selectById(1);
System.out.println(baseTerm.getName());
sqlSession.close();

示例代码打印的日志如下:

 Preparing: select * from base_term where id = ?
 Parameters: 1(Integer)
 Total: 1

MyBatis中加载第三方日志库的优先级顺序:
slf4J → commonsLoging → Log4J2 → Log4J → JdkLog;

public final class LogFactory {
    ...
    //自动扫描日志实现,并且第三方日志插件加载优先级如下:slf4J → commonsLoging → Log4J2 → Log4J → JdkLog
   static {
      tryImplementation(LogFactory::useSlf4jLogging);
      tryImplementation(LogFactory::useCommonsLogging);
      tryImplementation(LogFactory::useLog4J2Logging);
      tryImplementation(LogFactory::useLog4JLogging);
      tryImplementation(LogFactory::useJdkLogging);
      tryImplementation(LogFactory::useNoLogging);
    }
    public static synchronized void useSlf4jLogging() {
        //使用Slf4jImpl实现类
  	    setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);
    }
    private static void tryImplementation(Runnable runnable) {
 	     if (logConstructor == null) {//当构造方法不为空才执行方法
             try {
             runnable.run();
          } catch (Throwable t) {
           // ignore
        }
   }
  }
   //通过指定的log类来初始化构造方法
   private static void setImplementation(Class<? extends Log> implClass) {
      try {
          //获取构造方法
         Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
         //获取日志对象
         Log log = candidate.newInstance(LogFactory.class.getName());
         logConstructor = candidate;
      } catch (Throwable t) {
        throw new LogException("Error setting Log implementation.  Cause: " + t, t);
      }
    }
    ...
    
}

在这里插入图片描述
这里MyBatis采用适配器模式,通过Log接口的方式实现对不同第三方Log插件的进行适配,具体代码如下:
Log定义了各种日志等级的打印方法:

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);

}

Slf4jImpl类实现Log接口中的打印方法完成日志打印的适配

public class Slf4jImpl implements Log {

  private Log log;

  public Slf4jImpl(String clazz) {
    Logger logger = LoggerFactory.getLogger(clazz);
    ... ...
    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);
  }
}

接着我们看一下MyBatis如何使用Log进行日志打印
在MyBatis中,打印的日志主要有:
1.执行的SQL语句
2.参数的类型和值
3.查询的结果数据条数
涉及的类图如下:
在这里插入图片描述
ConnectionLogger:负责打印连接信息和SQL语句,并创建PreparedStatementLogger;
PreparedStatementLogger:负责打印参数信息,并创建ResultSetLogger;
ResultSetLogger:负责打印数据结果信息。

接下来看一下涉及ConnectionLogger的相关源码
BaseExecutor.getConnection:

/**
 * 获取连接对象
 * @param statementLog
 * @return
 * @throws SQLException
 */
protected Connection getConnection(Log statementLog) throws SQLException {
  Connection connection = transaction.getConnection();
  if (statementLog.isDebugEnabled()) {
    //使用connection连接对象创建ConnectionLogger代理对象并返回
    return ConnectionLogger.newInstance(connection, statementLog, queryStack);
  } else {
    return connection;
  }
}

ConnectionLogger采用动态代理的方式对connection的功能进行增强
ConnectionLogger.newInstance:

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);
}

Debug展示的值
在这里插入图片描述
ConnectionLogger.invoke方法对Connection方法添加打印日志逻辑

@Override
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()) || "prepareCall".equals(method.getName())) {
      if (isDebugEnabled()) {
        //如果是调用prepareStatement、prepareCall打印要执行的sql语句
        debug(" Preparing: " + removeExtraWhitespace((String) params[0]), true);
      }
      PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
      //返回prepareStatement的代理对象PreparedStatementLogger,让prepareStatement也具备打印日志能力
      stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
      return stmt;
    } else if ("createStatement".equals(method.getName())) {
      Statement stmt = (Statement) method.invoke(connection, params);
      //返回Statement的代理对象StatementLogger,让Statement也具备打印日志能力
      stmt = StatementLogger.newInstance(stmt, statementLog, queryStack);
      return stmt;
    } else {
      return method.invoke(connection, params);
    }
  } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
  }
}

Debug展示的值
在这里插入图片描述
由上面的代码可以看到如果调用Connection的prepareStatement方法后,Connection的代理对象ConnectionLogger首先会打印sql语句,然后调用invoke方法生成PreparedStatement对象,再用PreparedStatement生成的PreparedStatementLogger代理对象,最后返回出去,因此我们接着看PreparedStatementLogger中的invoke方法,具体如下。

@Override
public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
  try {
    if (Object.class.equals(method.getDeclaringClass())) {
      return method.invoke(this, params);
    }
    if (EXECUTE_METHODS.contains(method.getName())) {
      //execute方法
      if (isDebugEnabled()) {
        //打印记录的查询参数的参数类型和参数值
        debug("Parameters: " + getParameterValueString(), true);
      }
      clearColumnInfo();
      if ("executeQuery".equals(method.getName())) {
        //如果是查询语句 返回ResultSet的动态代理对象ResultSetLogger,使ResultSet具有打印日志功能
        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())) {
      //记录查询参数,在调用execute方法时打印
      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的动态代理对象ResultSetLogger
      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的invoke方法

@Override
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())) {// 增强next方法
      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);
  }
}

可以看到最后也将查询结果总数也打印出来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值