MyBatis中有哪些日志模块

今天就跟大家聊聊有关MyBatis中有哪些日志模块,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。

从策划到设计制作,每一步都追求做到细腻,制作可持续发展的企业网站。为客户提供成都网站建设、做网站、网站策划、网页设计、申请域名虚拟主机、网络营销、VI设计、 网站改版、漏洞修补等服务。为客户提供更好的一站式互联网解决方案,以客户的口碑塑造优易品牌,携手广大客户,共同发展进步。

日志功能代码所在包

org.apache.ibatis.logging

日志模块的加载顺序

mybatis 没有自己的日志模块 他是使用的第三方日志(也有jdk自带日志) 日志的加载顺序 slf4J → commonsLoging → Log4J2 → Log4J → JdkLog

相关代码

在静态代码块儿中声明加载顺序

public final class LogFactory {
  //被选定的第三方日志组件适配器的构造方法
  private static Constructor logConstructor;

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

  //被选定的第三方日志组件适配器的构造方法
  private static Constructor logConstructor;

  //自动扫描日志实现,并且第三方日志插件加载优先级如下:slf4J → commonsLoging → Log4J2 → Log4J → JdkLog
  static {
    tryImplementation(LogFactory::useSlf4jLogging);
    tryImplementation(LogFactory::useCommonsLogging);
    tryImplementation(LogFactory::useLog4J2Logging);
    tryImplementation(LogFactory::useLog4JLogging);
    tryImplementation(LogFactory::useJdkLogging);
    tryImplementation(LogFactory::useNoLogging);
  }

这里用到了java8的语法糖 tryImplementation方法需要一个runnable类型的参数,runnable被打上了@FunctionalInterface注解,所以可以使用lambda语法简写 logConstructor 保存着日志对象的构造方法,之后通过反射创建对象,只有logConstructor == null的时候采取调用running的run方法,那么run的内容是什么呢?

tryImplementation(LogFactory::useSlf4jLogging);

private static void tryImplementation(Runnable runnable) {
    if (logConstructor == null) {//当构造方法不为空才执行方法
      try {
        runnable.run();
      } catch (Throwable t) {
        // ignore
      }
    }
  }

LogFactory::useSlf4jLogging setImplementation方法尝试通过传递过来的Class创建类(通过反射拿到构造方法),如果成功,讲构造方法保存到logConstructor中备用

public static synchronized void useSlf4jLogging() {
    setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);
  }
  
  
  //通过指定的log类来初始化构造方法
  private static void setImplementation(Class implClass) {
    try {
      Constructor candidate = implClass.getConstructor(String.class);
      Log log = candidate.newInstance(LogFactory.class.getName());
      if (log.isDebugEnabled()) {
        log.debug("Logging initialized using '" + implClass + "' adapter.");
      }
      logConstructor = candidate;
    } catch (Throwable t) {
      throw new LogException("Error setting Log implementation.  Cause: " + t, t);
    }
  }

这用到的设计模式 适配器模式 因为不同的日志组件对日志的info error等定义不同,mybatis把他们统一成如下级别

package org.apache.ibatis.logging;
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);

我们看优先级最高的日志slf4J的实现类

package org.apache.ibatis.logging.log4j;

import org.apache.ibatis.logging.Log;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;

/**
 * @author Eduardo Macarron
 */
public class Log4jImpl implements Log {
  private static final String FQCN = Log4jImpl.class.getName();
  private final Logger log;
  public Log4jImpl(String clazz) {
    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);
  }
}

日志功能的体现

MyBatis通过动态代理的方式增强了Connection,PreparedStatement,ResultSet.其对应的实现是xxxLogger类

先看ConnectionLogger类部分代码,特别注意27行,同样通过动态代理拿到了具有打印功能的PreparedStatement--PreparedStatementLogger

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 {
    	//如果是从Obeject继承的方法直接忽略
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, params);
      }
      //如果是调用prepareStatement、prepareCall、createStatement的方法,打印要执行的sql语句
      //并返回prepareStatement的代理对象,让prepareStatement也具备日志能力,打印参数
      if ("prepareStatement".equals(method.getName())) {
        if (isDebugEnabled()) {
          debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);//打印sql语句
        } 
         PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
        stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);//创建代理对象
        return stmt;
......
// 创建这个ConnectionLogger的代码
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);
  }

BaseJdbcLogger 是Connection,PreparedStatement,ResultSet 的基类,他是个抽象类 成员变量展示

//所有日志增强的抽象基类
public abstract class BaseJdbcLogger {

  //保存preparestatment中常用的set方法(占位符赋值)
  protected static final Set SET_METHODS = new HashSet<>();
  //保存preparestatment中常用的执行sql语句的方法
  protected static final Set EXECUTE_METHODS = new HashSet<>();

  //保存preparestatment中set方法的键值对
  private final Map columnMap = new HashMap<>();

  //保存preparestatment中set方法的key值
  private final List columnNames = new ArrayList<>();
  //保存preparestatment中set方法的value值
  private final List columnValues = new ArrayList<>();

  protected Log statementLog;
  protected int queryStack;
  
  static {
    // 哪些方法需要打印日志
    SET_METHODS.add("setString");
    SET_METHODS.add("setNString");
    SET_METHODS.add("setInt");
    SET_METHODS.add("setByte");
    SET_METHODS.add("setShort");
    SET_METHODS.add("setLong");
    SET_METHODS.add("setDouble");
    SET_METHODS.add("setFloat");
    SET_METHODS.add("setTimestamp");
    SET_METHODS.add("setDate");
    SET_METHODS.add("setTime");
    SET_METHODS.add("setArray");
    SET_METHODS.add("setBigDecimal");
    SET_METHODS.add("setAsciiStream");
    SET_METHODS.add("setBinaryStream");
    SET_METHODS.add("setBlob");
    SET_METHODS.add("setBoolean");
    SET_METHODS.add("setBytes");
    SET_METHODS.add("setCharacterStream");
    SET_METHODS.add("setNCharacterStream");
    SET_METHODS.add("setClob");
    SET_METHODS.add("setNClob");
    SET_METHODS.add("setObject");
    SET_METHODS.add("setNull");

    // 调用哪些方法的时候需要打印sql语句
    EXECUTE_METHODS.add("execute");
    EXECUTE_METHODS.add("executeUpdate");
    EXECUTE_METHODS.add("executeQuery");
    EXECUTE_METHODS.add("addBatch");
  }

PreparedStatementLogger 部分代码 20行 如果调用的方法存在EXECUTE_METHODS中 打印日志

public final class PreparedStatementLogger extends BaseJdbcLogger implements InvocationHandler {

  private final PreparedStatement statement;

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

  //1,增强PreparedStatement的setxxx方法将参数设置到columnMap、columnNames、columnValues,为打印参数做好准备
  //2. 增强PreparedStatement的execute相关方法,当方法执行时,通过动态代理打印参数,返回动态代理能力的resultSet
  //3. 如果是查询,增强PreparedStatement的getResultSet方法,返回动态代理能力的resultSet
  //   如果是更新,直接打印影响的行数
  @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())) {//增强PreparedStatement的execute相关方法
        if (isDebugEnabled()) {
          debug("Parameters: " + getParameterValueString(), true);//当方法执行时,通过动态代理打印参数
        }
        clearColumnInfo();
        if ("executeQuery".equals(method.getName())) {//返回动态代理能力的resultSet
          ResultSet rs = (ResultSet) method.invoke(statement, params);
          return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
        } else {
          return method.invoke(statement, params);

通过对日志的观察,如下几个位置需要打日志:

  1. 在创建 prepareStatement 时,打印执行的 SQL 语句;

  2. 访问数据库时,打印参数的类型和值

  3. 查询出结构后,打印结果数据条数 因此在日志模块中有 BaseJdbcLogger、 ConnectionLogger、 PreparedStatementLogger 和 ResultSetLogge 通过动态代理负责在不同的位置打印日志; BaseJdbcLogger: 所有日志增强的抽象基类,用于记录 JDBC 那些方法需要增强,保存运 行期间 sql 参数信息; ConnectionLogger:负责打印连接信息和 SQL 语句。通过动态代理,对 connection 进行 增强,如果是调用 prepareStatement、 prepareCall、 createStatement 的方法,打印要执 行的 sql 语句并返回 prepareStatement 的代理对象(PreparedStatementLogger),让 prepareStatement 也具备日志能力,打印参数; PreparedStatementLogger:对 prepareStatement 对象增强,增强的点如下: 增强 PreparedStatement 的 setxxx 方法将参数设置到 columnMap、 columnNames、 columnValues,为打印参数做好准备; 增强 PreparedStatement 的 execute 相关方法,当方法执行时,通过动态代理打印 参数,返回动态代理能力的 resultSet; 如果是查询,增强 PreparedStatement 的 getResultSet 方法,返回动态代理能力的 resultSet;如果是更新,直接打印影响的行数 ResultSetLogge:负责打印数据结果信息;

最后一个问题:上面讲这么多,都是日志功能的实现,那日志功能是怎么加入主体功能的?

答:既然在 Mybatis 中 Executor 才是访问数据库的组件,日志功能是在 Executor 中被嵌入的, 具体代码在 org.apache.ibatis.executor.SimpleExecutor.prepareStatement(StatementHandler, Log) 方法中,如下代码所示

//创建Statement
  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    //获取connection对象的动态代理,添加日志能力;
    Connection connection = getConnection(statementLog);
    //通过不同的StatementHandler,利用connection创建(prepare)Statement
    stmt = handler.prepare(connection, transaction.getTimeout());
    //使用parameterHandler处理占位符
    handler.parameterize(stmt);
    return stmt;
  }

getConnection(statementLog)内容如下

 protected Connection getConnection(Log statementLog) throws SQLException {
    Connection connection = transaction.getConnection();
    if (statementLog.isDebugEnabled()) {// 需要日志功能 返回ConnectionLogger
      return ConnectionLogger.newInstance(connection, statementLog, queryStack);
    } else {
      return connection;
    }
  }

看完上述内容,你们对MyBatis中有哪些日志模块有进一步的了解吗?如果还想了解更多知识或者相关内容,请关注创新互联行业资讯频道,感谢大家的支持。


标题名称:MyBatis中有哪些日志模块
标题来源:http://bzwzjz.com/article/ppsheo.html