溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

MyBatis中有哪些日志模塊

發(fā)布時(shí)間:2021-06-18 15:19:35 來(lái)源:億速云 閱讀:167 作者:Leah 欄目:大數(shù)據(jù)

今天就跟大家聊聊有關(guān)MyBatis中有哪些日志模塊,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。

日志功能代碼所在包

org.apache.ibatis.logging

日志模塊的加載順序

mybatis 沒(méi)有自己的日志模塊 他是使用的第三方日志(也有jdk自帶日志) 日志的加載順序 slf4J → commonsLoging → Log4J2 → Log4J → JdkLog

相關(guān)代碼

在靜態(tài)代碼塊兒中聲明加載順序

public final class LogFactory {
  //被選定的第三方日志組件適配器的構(gòu)造方法
  private static Constructor<? extends Log> logConstructor;

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

  //被選定的第三方日志組件適配器的構(gòu)造方法
  private static Constructor<? extends Log> logConstructor;

  //自動(dòng)掃描日志實(shí)現(xiàn),并且第三方日志插件加載優(yōu)先級(jí)如下: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的語(yǔ)法糖 tryImplementation方法需要一個(gè)runnable類型的參數(shù),runnable被打上了@FunctionalInterface注解,所以可以使用lambda語(yǔ)法簡(jiǎn)寫 logConstructor 保存著日志對(duì)象的構(gòu)造方法,之后通過(guò)反射創(chuàng)建對(duì)象,只有l(wèi)ogConstructor == null的時(shí)候采取調(diào)用running的run方法,那么run的內(nèi)容是什么呢?

tryImplementation(LogFactory::useSlf4jLogging);

private static void tryImplementation(Runnable runnable) {
    if (logConstructor == null) {//當(dāng)構(gòu)造方法不為空才執(zhí)行方法
      try {
        runnable.run();
      } catch (Throwable t) {
        // ignore
      }
    }
  }

LogFactory::useSlf4jLogging setImplementation方法嘗試通過(guò)傳遞過(guò)來(lái)的Class創(chuàng)建類(通過(guò)反射拿到構(gòu)造方法),如果成功,講構(gòu)造方法保存到logConstructor中備用

public static synchronized void useSlf4jLogging() {
    setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);
  }
  
  
  //通過(guò)指定的log類來(lái)初始化構(gòu)造方法
  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);
    }
  }

這用到的設(shè)計(jì)模式 適配器模式 因?yàn)椴煌娜罩窘M件對(duì)日志的info error等定義不同,mybatis把他們統(tǒng)一成如下級(jí)別

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

我們看優(yōu)先級(jí)最高的日志slf4J的實(shí)現(xiàn)類

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

日志功能的體現(xiàn)

MyBatis通過(guò)動(dòng)態(tài)代理的方式增強(qiáng)了Connection,PreparedStatement,ResultSet.其對(duì)應(yīng)的實(shí)現(xiàn)是xxxLogger類

先看ConnectionLogger類部分代碼,特別注意27行,同樣通過(guò)動(dòng)態(tài)代理拿到了具有打印功能的PreparedStatement--PreparedStatementLogger

public final class ConnectionLogger extends BaseJdbcLogger implements InvocationHandler {// 這里使用了動(dòng)態(tài)代理

 //真正的連接對(duì)象
  private final Connection connection;

  private ConnectionLogger(Connection conn, Log statementLog, int queryStack) {
    super(statementLog, queryStack);
    this.connection = conn;
  }

  @Override
  //對(duì)連接的增強(qiáng)
  public Object invoke(Object proxy, Method method, Object[] params)
      throws Throwable {
    try {
    	//如果是從Obeject繼承的方法直接忽略
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, params);
      }
      //如果是調(diào)用prepareStatement、prepareCall、createStatement的方法,打印要執(zhí)行的sql語(yǔ)句
      //并返回prepareStatement的代理對(duì)象,讓prepareStatement也具備日志能力,打印參數(shù)
      if ("prepareStatement".equals(method.getName())) {
        if (isDebugEnabled()) {
          debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);//打印sql語(yǔ)句
        } 
         PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
        stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);//創(chuàng)建代理對(duì)象
        return stmt;
......
// 創(chuàng)建這個(gè)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 的基類,他是個(gè)抽象類 成員變量展示

//所有日志增強(qiáng)的抽象基類
public abstract class BaseJdbcLogger {

  //保存preparestatment中常用的set方法(占位符賦值)
  protected static final Set<String> SET_METHODS = new HashSet<>();
  //保存preparestatment中常用的執(zhí)行sql語(yǔ)句的方法
  protected static final Set<String> EXECUTE_METHODS = new HashSet<>();

  //保存preparestatment中set方法的鍵值對(duì)
  private final Map<Object, Object> columnMap = new HashMap<>();

  //保存preparestatment中set方法的key值
  private final List<Object> columnNames = new ArrayList<>();
  //保存preparestatment中set方法的value值
  private final List<Object> 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");

    // 調(diào)用哪些方法的時(shí)候需要打印sql語(yǔ)句
    EXECUTE_METHODS.add("execute");
    EXECUTE_METHODS.add("executeUpdate");
    EXECUTE_METHODS.add("executeQuery");
    EXECUTE_METHODS.add("addBatch");
  }

PreparedStatementLogger 部分代碼 20行 如果調(diào)用的方法存在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,增強(qiáng)PreparedStatement的setxxx方法將參數(shù)設(shè)置到columnMap、columnNames、columnValues,為打印參數(shù)做好準(zhǔn)備
  //2. 增強(qiáng)PreparedStatement的execute相關(guān)方法,當(dāng)方法執(zhí)行時(shí),通過(guò)動(dòng)態(tài)代理打印參數(shù),返回動(dòng)態(tài)代理能力的resultSet
  //3. 如果是查詢,增強(qiáng)PreparedStatement的getResultSet方法,返回動(dòng)態(tài)代理能力的resultSet
  //   如果是更新,直接打印影響的行數(shù)
  @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())) {//增強(qiáng)PreparedStatement的execute相關(guān)方法
        if (isDebugEnabled()) {
          debug("Parameters: " + getParameterValueString(), true);//當(dāng)方法執(zhí)行時(shí),通過(guò)動(dòng)態(tài)代理打印參數(shù)
        }
        clearColumnInfo();
        if ("executeQuery".equals(method.getName())) {//返回動(dòng)態(tài)代理能力的resultSet
          ResultSet rs = (ResultSet) method.invoke(statement, params);
          return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
        } else {
          return method.invoke(statement, params);

通過(guò)對(duì)日志的觀察,如下幾個(gè)位置需要打日志:

  1. 在創(chuàng)建 prepareStatement 時(shí),打印執(zhí)行的 SQL 語(yǔ)句;

  2. 訪問(wèn)數(shù)據(jù)庫(kù)時(shí),打印參數(shù)的類型和值

  3. 查詢出結(jié)構(gòu)后,打印結(jié)果數(shù)據(jù)條數(shù) 因此在日志模塊中有 BaseJdbcLogger、 ConnectionLogger、 PreparedStatementLogger 和 ResultSetLogge 通過(guò)動(dòng)態(tài)代理負(fù)責(zé)在不同的位置打印日志; BaseJdbcLogger: 所有日志增強(qiáng)的抽象基類,用于記錄 JDBC 那些方法需要增強(qiáng),保存運(yùn) 行期間 sql 參數(shù)信息; ConnectionLogger:負(fù)責(zé)打印連接信息和 SQL 語(yǔ)句。通過(guò)動(dòng)態(tài)代理,對(duì) connection 進(jìn)行 增強(qiáng),如果是調(diào)用 prepareStatement、 prepareCall、 createStatement 的方法,打印要執(zhí) 行的 sql 語(yǔ)句并返回 prepareStatement 的代理對(duì)象(PreparedStatementLogger),讓 prepareStatement 也具備日志能力,打印參數(shù); PreparedStatementLogger:對(duì) prepareStatement 對(duì)象增強(qiáng),增強(qiáng)的點(diǎn)如下: 增強(qiáng) PreparedStatement 的 setxxx 方法將參數(shù)設(shè)置到 columnMap、 columnNames、 columnValues,為打印參數(shù)做好準(zhǔn)備;? 增強(qiáng) PreparedStatement 的 execute 相關(guān)方法,當(dāng)方法執(zhí)行時(shí),通過(guò)動(dòng)態(tài)代理打印 參數(shù),返回動(dòng)態(tài)代理能力的 resultSet; 如果是查詢,增強(qiáng) PreparedStatement 的 getResultSet 方法,返回動(dòng)態(tài)代理能力的 resultSet;如果是更新,直接打印影響的行數(shù) ResultSetLogge:負(fù)責(zé)打印數(shù)據(jù)結(jié)果信息;

最后一個(gè)問(wèn)題:上面講這么多,都是日志功能的實(shí)現(xiàn),那日志功能是怎么加入主體功能的?

答:既然在 Mybatis 中 Executor 才是訪問(wèn)數(shù)據(jù)庫(kù)的組件,日志功能是在 Executor 中被嵌入的, 具體代碼在 org.apache.ibatis.executor.SimpleExecutor.prepareStatement(StatementHandler, Log) 方法中,如下代碼所示

//創(chuàng)建Statement
  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    //獲取connection對(duì)象的動(dòng)態(tài)代理,添加日志能力;
    Connection connection = getConnection(statementLog);
    //通過(guò)不同的StatementHandler,利用connection創(chuàng)建(prepare)Statement
    stmt = handler.prepare(connection, transaction.getTimeout());
    //使用parameterHandler處理占位符
    handler.parameterize(stmt);
    return stmt;
  }

getConnection(statementLog)內(nèi)容如下

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

看完上述內(nèi)容,你們對(duì)MyBatis中有哪些日志模塊有進(jìn)一步的了解嗎?如果還想了解更多知識(shí)或者相關(guān)內(nèi)容,請(qǐng)關(guān)注億速云行業(yè)資訊頻道,感謝大家的支持。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI