Mybatis : 从 Log 看整个处理流程

·  阅读 435
Mybatis : 从 Log 看整个处理流程

首先分享之前的所有文章 , 欢迎点赞收藏转发三连下次一定 >>>> 😜😜😜
文章合集 : 🎁 juejin.cn/post/694164…
Github : 👉 github.com/black-ant
CASE 备份 : 👉 gitee.com/antblack/ca…

一 . 前言

好久没有关注 Mybatis 源码了 , 最近工作上碰到了一些 Mybatis 的问题 , 于是又花了一点时间把 Mybatis 的笔记整理了一遍 , 方便以后问题的排查

这一篇以一个小环节开始 , 来逐步的展开 Mybatis 的处理流程

二 . Logger 触发流程

以一个Query 流程为例,通常我们可以打印 SQL Log 以判断 SQL 的具体执行情况 , 以一个常见的 SQL 打印为例 :

JDBC Connection [HikariProxyConnection@2034411604 wrapping com.mysql.cj.jdbc.ConnectionImpl@53b8afea] will not be managed by Spring
==>  Preparing: select sn, username, `password`, `power`, utype, isactive , age from user 
==> Parameters: 
<==    Columns: sn, username, password, power, utype, isactive, age
<==        Row: 0, 22, 33, 44, null, null, 1
<==        Row: 1, 1, 1, 1, 1, 1, null
<==      Total: 2
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@64355120]
复制代码

那么整体触发流程是什么样的呢 ? 其实查看 Log 得流程就可以了解到整个流程 ,其中包括 :

  • Connection 连接的创建 : 连接对象信息 , 使用什么样的连接池 , 连接对象等等
  • SQL 语句的生成 : 操作使用的什么 SQL , 占位符特性
  • 参数的处理 : 查询使用的参数
  • ROW 行的打印 : 查询的 colums 和 Row 行结果
  • 查询总数的计算 : 当前查询的总数
  • Session 的关闭 : Session 的额外处理 , 操作 , 使用的 Session 类型

每个 Log 的打印 , 往往意味着一个处理节点的完成 , 下面先来看一下整个处理流程 :

image.png

黄色标注的即为 log 打印处理 ,可以看到是贯穿到整个 Mybatis 流程中的.

补充 : Log 实现类

Log 核心接口 , 常用的实现有以下几个

C- StdOutImpl :实现 Log 接口,StdOut 实现类 , 基于 System.out 和 System.err 来实现
C- Slf4jImpl : 实现 Log 接口,Slf4j 实现类 , 
    MC- Slf4jImpl , 构造函数中初始化对象
        |- logger.getClass().getMethod
        |- 会通过2中方式去生成具体的类
            |- org.apache.ibatis.logging.slf4j.Slf4jLocationAwareLoggerImpl
                |- new Slf4jLocationAwareLoggerImpl((LocationAwareLogger) logger)
            |- org.apache.ibatis.logging.slf4j.Slf4jLoggerImpl 类
    M- error / warn / info / debug 等方法直接调用log.xxx
C- BaseJdbcLogger
    |- Log 下 JDBC 包 ,有以下的实现类
    |- 基于 JDBC 接口实现增强的案例,而原理上,也是基于 JDK 实现动态代理
    
// 其他 Logger 实现类    
C- BaseJdbcLogger
C- ConnectionLogger
C- PreparedStatementLogger
C- StatementLogger
C- ResultSetLogger
复制代码

补充 : LogFactory

MC-  构造器中 , 会初始化 logConstructor 属性 , 此处通过对各种实现进行 tryImplementation(Runnable runnable) 操作 , 判断能使用哪个实现
    |-  tryImplementation(LogFactory::useSlf4jLogging);
M- useSlf4jLogging : 调用 #setImplementation(Class<? extends Log> implClass) 方法,尝试使用指定的 Log 实现类
    |- new Instance 方式 , 通过 getName 创建
    M- useCustomLogging(Class<? extends Log> clazz) 方法,设置自定义的 Log 实现类    
M- getLog : 获得 Log 对象
    |- logConstructor.newInstance**
复制代码

二 . Log 处理详情

下面再来简单过一下整个log的处理详情 :

3.1 连接信息打印

Mybatis 为了适配各类 log 框架 , 基于 Log 接口 实现了很多实现类 , 核心处理的包括 BaseJdbcLogger及其四 个子类 , 作用是 通过 JDK 动态代理的方式,将 JDBC 的操作,打印到日志中 .另外还有工厂类 LogFactory

通过 SpringManagedTransaction 获取连接信息时会触发 getConnection 操作>>

private void openConnection() throws SQLException {
  
  // 获取连接和事务自动提交配置
  this.connection = DataSourceUtils.getConnection(this.dataSource);
  this.autoCommit = this.connection.getAutoCommit();
  this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
  
// 打印 JDBC Connection 信息
  if (LOGGER.isDebugEnabled()) {
    LOGGER.debug(
        "JDBC Connection ["
            + this.connection
            + "] will"
            + (this.isConnectionTransactional ? " " : " not ")
            + "be managed by Spring");
  }
}
复制代码

3.2 SQL 信息的打印

// S1 : ConnectionLogger 代理入口
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
      PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
      stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
      return stmt;
    } else if ("prepareCall".equals(method.getName())) {
      // 打印 Log
      if (isDebugEnabled()) {
        debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);
      }        
      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);
    }
  } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
  }
}


// S2 : 调用具体的 Log 实现类
protected void debug(String text, boolean input) {
  if (statementLog.isDebugEnabled()) {
    statementLog.debug(prefix(input) + text);
  }
}
复制代码

触发方式

// S1 : 入口 
  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;
  }


// S2 : SQL 打印逻辑 PreparedStatementHandler
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    String sql = boundSql.getSql();
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
      String[] keyColumnNames = mappedStatement.getKeyColumns();
      if (keyColumnNames == null) {
        return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
      } else {
        // 此处的 connection 会实际调用  ConnectionLogger
        return connection.prepareStatement(sql, keyColumnNames);
      }
    } else if (mappedStatement.getResultSetType() != null) {
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    } else {
      return connection.prepareStatement(sql);
    }
  }

复制代码

3.3 Select 结果的打印

查询结果的打印是在 ResultSetLogger 中进行 , 显示数据结构

public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
    // 当 Invoke 的方法为 next() 时 ,会返回 Boolean 型对象
    Object o = method.invoke(rs, params);
    if ("next".equals(method.getName())) {
      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 {
        // 如果 next 返回 false , 表示 next 不存在 , 打印结果
        debug("     Total: " + rows, false);
      }
    }
    clearColumnInfo();
    return o;
}
复制代码

ResultSetMetaData 数据结构

image.png

3.4 Close Session 的打印

在 SqlSessionInterceptor finally 中 , 最终会调用 Close 关闭 Session

public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
  notNull(session, NO_SQL_SESSION_SPECIFIED);
  notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);

  // SqlSessionHolder 用于在当前 TransactionSynchronizationManager 维持 Session
  SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
  if ((holder != null) && (holder.getSqlSession() == session)) {
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Releasing transactional SqlSession [" + session + "]");
    }
    // 如果是 SqlSessionHolder 进行处理 ,则直接释放
    holder.released();
  } else {
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Closing non transactional SqlSession [" + session + "]");
    }
    // 如果是 session 处理 , 无事务 ,则直接关闭
    session.close();
  }
}
复制代码

总结

从 Log 上大概了解到了 Mybatis 的处理流程 , 下面分别深入下细节以及学习如何进行扩展定制

分类:
后端
标签:
收藏成功!
已添加到「」, 点击更改