Mybatis源码学习-日志模块分析

Mybatis源码学习-日志模块

所用到的设计模式:

  1. 适配器模式 (对外提供的日志功能)
  2. 代理模式 (将日志功能添加到业务中)
    代理模式 (静态代理 动态代理)
    定义:给目标对象提供一个代理对象,并由代理对象控制目标对象的引用
    目的:
    1.通过代理对象来访问目标对象,防止直接访问目标对象给系统带来的不必要的复杂性
    2.通过代理对象增强原有的业务逻辑(eg:给业务增加日志功能
  3. 装饰器模式 (真正获取connection对象的时候)

1.Mybatis源码的基本模块

模块如下图:一共16个模块,这16个模块可以分为3层

Mybatis源码的16个模块

基础支撑层:专注于底层实现,无业务含义(通用性比较强)
核心处理层:专门处理Mybatis的业务流程实现,依赖于基础支撑层
接口层:对外提供的访问接口,也就是面向SqlSession编程

日志模块

  1. 如何提供统一的日志级别
    前提:Mybatis本身没有日志的实现类,而是接入的第三方日志组件

提供了统一的(log)接口,让客户端面向接口编程,不用关系具体的实现

package org.apache.ibatis.logging;

/**
 * @author Clinton Begin
 */
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);

}
  1. 自动扫描日志,并按顺序加载第三方日志 (顺序slf4j->commomsLogging->log4j2->log4j->jdkLog)
    在LogFactory中,有下面的代码块
 static {
    tryImplementation(LogFactory::useSlf4jLogging);
    tryImplementation(LogFactory::useCommonsLogging);
    tryImplementation(LogFactory::useLog4J2Logging);
    tryImplementation(LogFactory::useLog4JLogging);
    tryImplementation(LogFactory::useJdkLogging);
    tryImplementation(LogFactory::useNoLogging);
  }

该静态代码块的功能是,扫描第三方的日志组件,将他们的构造方法赋给LogFactory,让LogFactory使用该构造方法来实例化日志对象

private static Constructor<? extends Log> logConstructor;
 
// 真正创建Log实现类的方法
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);
  }
}
// 获取第三方日志的构造方法
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);
    }
  }
  1. 将日志功能优雅的加入到相应的功能里面(使用动态代理模式)
    执行mybatis的时候,一共打印了3行日志,分别是:
    1.执行的sql (ConnectionLogger)
    2.参数值跟参数类型 (PreparedStatementLogger)
    3.查询的结果条数 (ResultSetLogger)

主要由以下几个类来提供日志的打印

BaseJdbcLogger:所有日志增强的抽象基类,用于记录jdbc哪些方法需要增强,保存运行期间的sql参数信息
ConnectionLogger:对Connection对象进行增强。负责打印连接信息和sql语句。

public final class ConnectionLogger extends BaseJdbcLogger implements InvocationHandler{
	//真正的连接对象
    private final Connection connection;
}

如果是调用prepareStatement、prepareCall、createStatement的方法,打印要执行的sql语句,并创建PreparedStatementLogger代理对象,让PreparedStatement对象也具有打印的功能

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;
   } else if ("prepareCall".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;
   } 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);
}

PreparedStatementLogger:对prepareStatement 进行增强
1.如果是excute相关方法,对excute相关方法进行参数的日志打印

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

2.如果是set方法,将参数设置到columnMap、columnNames、columnValues中
PreparedStatementLogger:

else if (SET_METHODS.contains(method.getName())) {//将参数设置到columnMap、columnNames、columnValues,为打印参数做好准备
        if ("setNull".equals(method.getName())) {
          setColumn(params[0], null);
        } else {
          setColumn(params[0], params[1]);
        }
        return method.invoke(statement, params);
      }

BaseJdbcLogger:

//保存preparestatment中set方法的键值对
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 void setColumn(Object key, Object value) {
    columnMap.put(key, value);
    columnNames.add(key);
    columnValues.add(value);
  }

3.如果是查询,增强 PreparedStatement 的 getResultSet 方法,返回动态代理能力的
resultSet
PreparedStatementLogger:

else if ("getResultSet".equals(method.getName())) {//返回动态代理能力的resultSet
	    ResultSet rs = (ResultSet) method.invoke(statement, params);
	    return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
 }

如果是更新,直接打印影响的行数
PreparedStatementLogger:

else if ("getUpdateCount".equals(method.getName())) {//   如果是更新,直接打印影响的行数
        int updateCount = (Integer) method.invoke(statement, params);
        if (updateCount != -1) {
          debug("   Updates: " + updateCount, false);
 }
 return updateCount;
      

ResultSetLogger:负责打印数据结果信息(总行数)。
原生jdbc,获取数据是执行resultSet.next()方法来获取。mybatis也是用.next()来获取,并设置一个变量,记录.next()的次数,这个次数就是查询结果的总行数
ResultSetLogger:

private int rows;
private final ResultSet rs;


Object o = method.invoke(rs, params);//执行result.next方法,判断是否还有数据
      if ("next".equals(method.getName())) {//如果还有数据,计数器rows加一
        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);//如果没有数据了,打印rows,打印查询出来的数据条数
        }
      }

到目前为止,只体现了mybatis如何实现日志增强的,那真正加入到业务里的是在SimpleExecutor
Mybatis中Executor才是访问数据库的真正组件,所以找到SimpleExecutor类中,能找到prepareStatement()这个方法

 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;
  }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值