Mybatis的logger

一:Mybatis第三方日志组件
我们都知道Mybatis本身是没有提供给日志的实现类,Mybatis的日志是接入了第三方的日志组件并设置了统一的四个级别:trace,warm,debug,error。Mybatis一共接入了5总日志插件并在LogFactory类的静态代码块定义了5种日志的使用顺序。
 public final class LogFactory {
  static {
    tryImplementation(LogFactory::useSlf4jLogging);
    tryImplementation(LogFactory::useCommonsLogging);
    tryImplementation(LogFactory::useLog4J2Logging);
    tryImplementation(LogFactory::useLog4JLogging);
    tryImplementation(LogFactory::useJdkLogging);
    tryImplementation(LogFactory::useNoLogging);
  }

从上面的代码可以看出日志组件的加载顺序为:Slf4j–>CommonsLogging–>Log4j2–>Log4J–>JdkLog

二:logger的适配器模式
Mybatis日志主要是通过适配器模式来实现。适配器模式主要是作为两个不兼容接口的桥梁,将一个类的接口转换成另一个可供用户使用的接口
在这里插入图片描述
接下来以Log4J为例:
Mybatis提供了一个Log的接口作为目标角色,提供了5种事务级别的方法:

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

Log4JImpl作为适配器角色,Log4J的Logger作为适配者角色,可以看到error()方法最终还是会调用Log4j的级别为ERROR的方法。

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

上面提到LogFactory初始化时会执行选择第三方组健的代码块,之后会执行tryImplementation(LogFactory::useLog4JLogging);的方法,最后调用setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class)反射拿到对应的构造方法。这样就可以构造创建Log对象。

  //构造方法
  private static Constructor<? extends Log> logConstructor;
  //选择第三方日志组健
  static {
    tryImplementation(LogFactory::useSlf4jLogging);
    tryImplementation(LogFactory::useCommonsLogging);
    tryImplementation(LogFactory::useLog4J2Logging);
    tryImplementation(LogFactory::useLog4JLogging);
    tryImplementation(LogFactory::useJdkLogging);
    tryImplementation(LogFactory::useNoLogging);
  }
 //获取构造方法
  public static Log getLog(Class<?> aClass) {
    return getLog(aClass.getName());
  }

  //通过newInstance创建对象
  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 useSlf4jLogging() {
    setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);
  }
//Log4j组健传入参数org.apache.ibatis.logging.slf4j.Slf4jImpl.class
  //通过反射获取对应的构造方法
  private static void setImplementation(Class<? extends Log> implClass) {
    try {
      Constructor<? extends Log> 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);
    }
  }

三:Mybatis日志的调用
看了上面博主写的之后小伙伴肯定明白了Mybatis的Log实现原理,这时候有些小伙伴就要说了,我确实知道Log的原理了但是它是在什么时候调用的呢?接下来就以数据库查询介绍一下日志的调用。
执行器SimpleExecutor的prepareStatement是JDBC的一个入口方法

 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    //建立数据库连接
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }

接下来我们进入getConnection方法看一下

 protected Connection getConnection(Log statementLog) throws SQLException {
    Connection connection = transaction.getConnection();
    if (statementLog.isDebugEnabled()) {
    	//动态代理,增强Collection
      return ConnectionLogger.newInstance(connection, statementLog, queryStack);
    } else {
      return connection;
    }
  }

@Override
//JDBC的conn.prepareStatement会进入,并返回增强的PreparedStatement 
  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(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);
        }
        PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
        stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
        //返回增强的PreparedStatement 
        return stmt;
      } else if ("prepareCall".equals(method.getName())) {
        if (isDebugEnabled()) {
          debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);
        }
        PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
        stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
        return stmt;

类似的PreparedStatement 的动态代理也会返回增强的ResultSet
由此可见JDBC的日志都是通过动态代理对方法增强输出日志信息。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值