写于:2020-02-01 08:52:37 :未完成

基础支持层” 提供的基础数据和基础功能模块,为 “核心处理层” 完成 SQL 语句的执行 提供了基础。

“核心处理层” 执行 SQL 语句的过程大体可以分为几个部分,这几个部分分别对应了几个 Mybatis 定义的组件类:

  • Executor 其本身并不进行 SQL 语句的操作执行,只负责维护一级缓存和二级缓存,并提供事务管理的相关操作。
  • StatementHandler StatementHandler 负责管理 SQL 拼接、执行和 结果集映射。
    • ParameterHandler:进行 SQL 参数拼接,组装完整的 SQL 语句。
    • java.sql.Statement: 原生 JDBC 进行 SQL 执行。
    • ResultSetHandler:进行JDBC 结果集 到 JAVA 对象的 映射。

各个组件的组成的 SQL 执行流程大致如下图:

SQL执行流程图

# 根据 Mybaits 各个组件剖析 SQL 执行流程:以 SQL查询为例

Mybatis 执行流程中,默认实现:

  • Executor 默认实现 - CachingExecuto
  • StatementHandler 默认实现 - RoutingStatementHandler
  • ParameterHandler 默认实现 - DefaultParameterHandler
  • ResultSetHandler 默认实现 - DefaultResultSetHandler

以 Mybatis 默认实现为例的 SQL 执行时序简图如下:

SQL执行流程时序图

整个执行流程经由:Executor -> StatementHandler -> ResultSetHandler 处理最后将结果进行返回。 各个组件的基本职责:

  • 1、Executor 进行缓存维护。当 Executor 命中缓存,直接返回结果集,流程结束
  • 2、StatementHandler 作为管理者将SQL的拼接委派给 ParameterHandler 完成,SQL执行委派给JDBC来完成,结果集映射委派给ResultSetHandler完成,最终完成的 SQL 的拼接,执行 以及结果集的映射。

# 流程的起始点 Executor:以默认实现 CachingExecutor 为例

Mybatis 中默认的执行器为 CachingExecutor ,而 CachingExecutor持有的被装饰对象为 SimpleExecutor。

直接查看 CachingExecutor#query 代码实现

public class CachingExecutor implements Executor {
  // 默认实现为:SimpleExecutor
  private Executor delegate;
  // 事务缓存管理器
  private TransactionalCacheManager tcm = new TransactionalCacheManager();
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    // 创建缓存 key
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    // 从 MappedStatement 中获取二级缓存容器
    Cache cache = ms.getCache();
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, parameterObject, boundSql);
        @SuppressWarnings("unchecked")
        // 根据 key 从二级缓存中获取数据,并且放入 TransactionalCache 中
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          // 二级缓存没有命中,由被装饰对象 SimpleExecutor#query 执行。
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
}

CachingExecutor#query 方法的关键入参:

  • MappedStatement ms

    Mybatis【源码篇】Mybatis-基础支持层-配置文件解析 中提到 MappedStatement 封装的是 SQL 语句,以及二级缓存 Cache。【MappedStatment 是全局的,也就是二级缓存时全局的,为了 保证多线程时的线程安全问题二级缓存使用 SynchronizedCache 实现】

  • Object parameterObject SQL 拼接需要的参数

在 CachingExecutor#query 提供了二级缓存的能力,相关逻辑如下:

  • 从 MappedStatement 中尝试获取二级缓存容器 SynchronizedCache 。 只有当 Mapper.xml 文件中配置有 <cache><cache-ref> 时,二级缓存容器才存在。

  • 根据 Key 尝试从缓存容器中获取二级缓存,同时将记录存到 TransactionalCacheManager 二级缓存没有命中时,会从一级缓存或者数据库中获取到,然后暂时存入到 TransactionalCacheManager 中,只有当事务:commit、rollback或者 SqlSession#close 时,二级缓存才会真正存入到 SynchronizedCache 缓存容器中,此时二级缓存才能够被其他 SqlSession 访问到。

  • 二级缓存没有命中,查询则委派给 SimpleExecutor#query 完成。

# SimpleExecutor#query

SimpleExecutor 继承了抽象父类 BaseExecutor,Executor 基本的增删改查操作由 BaseExecutor 固定实现了,并提供了一级缓存功能。

直接上代码

public abstract class BaseExecutor implements Executor {
  protected PerpetualCache localCache;
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ......
    List<E> list;
    try {
      // 尝试从一级缓存中获取数据
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      // 缓存命中
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        // 缓存不命中,从数据库查询
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    }
    ......
    return list;
  }

  // 当以及缓存不命中时,尝试从数据库中获取
  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      // 真正的执行为派给其子类 SimpleExecutor#doQuery 执行
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }
}

BaseExecutor#query 提供了一级缓存功能,一级缓存实现逻辑如下:

  • 从本地缓存 localCache(PerpetualCache实现)中获一级缓存
  • 缓存命中直接返回结果
  • 缓存不命中从数据库进行查询

# 从数据库中查询

当一级缓存和二级缓存都不命中的情况下,结果就需要从数据库中获取

进行 SQL 处理和SQL 执行的是 StatementHandler 。

# 关于 StatementHandler 的一些知识点

StatementHandler类图

介绍Mybatis 中对于 StatementHandler 的几种实现:

  • BaseStatementHandler

BaseStatementHandler 是一个实现了 StatementHandler 接口的抽象类,它只提供了一些参数绑定相关的方法,并没有实现操作数据库的方法。

  • SimpleStatementHandler

SimpleStatementHandler 继承了 BaseStatementHandler 抽象类。对于数据库的操作是调用底层的 java.sql.Statement ,所以 SimpleStatementHandler 只能执行没有的动态参数的 SQL

  • PreparedStatementHandler

PreparedStatementHandler 继承了 BaseStatementHandler 抽象类。对于数据库的操作时调用底层的 java.sql.PreparedStatement,

  • RoutingStatementHandler

    RoutingStatementHandler 是Mybatis默认的实现。从名字也能够知道,该类仅仅是一个“路由”类。 RoutingStatmentHandler 通过策略模式根据statementType 选择 StatementHandler 实现类,然后将 StatementHandler 所有的相关操作通过委派的方式委派给策略模式选择出来的 StatementHandler 实现。

下面来看看 SimpleExecutor#doQuery 如何组装 StatemetnHandler。

# StatemetnHandler 组装入口 SimpleExecutor#doQuery

直接上代码

public class SimpleExecutor extends BaseExecutor {
  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      // StatemetnHandler 的组装由 Configuration 完成
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }
}

通过代码能够知道 StatementHandler 的组装是由 Configuration 完成的。

# Configuration 进行 StatementHandler 组装

直接来看 Configuraiton 的代码

public class Configuration {
  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    // 扩展点
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }
}

在代码中能够看到很多类似 interceptorChain.pluginAll(xxxHandler) 的代码,这个也是 Mybatis 对外开放的扩展点,通过这些拓展点,能够实现如:SQL打印,分页等插件的实现。

通过代码能够知道 StatementHandler 的默认实现为: RoutingStatementHandler。

# StatementHandler:RoutingStatementHandler

RoutingStatementHandler 使用了委派的方式,将代码的实现为派给了 SimpleStatementHandler|PreparedStatementHandler|CallableStatementHandler 来完成

public class RoutingStatementHandler implements StatementHandler {
  // 委派的对象
  private final StatementHandler delegate;
  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    // 根据 MapperStatement 中 Statment 类型,为派给不同类型的 StatementHandler 处理
    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }

  }
}

以 StatementType 为 PREPARED 的情况解析:PreparedStatementHandler

# PreparedStatementHandler 构造

public class PreparedStatementHandler extends BaseStatementHandler {

  public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
  }
}

调用父类 BaseStatementHandler 构造函数

public abstract class BaseStatementHandler implements StatementHandler {
  protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    ......
    // 创建 ParameterHandler 
    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    // 创建 ResultSetHandler 
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
  }
}

# PreparedStatementHandler#query

public class PreparedStatementHandler extends BaseStatementHandler {
  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.<E> handleResultSets(ps);
  }
}

结果集处理 DefaultResultSetHandler

# DefaultResultSetHandler#handleResultSets

public class DefaultResultSetHandler implements ResultSetHandler {
  @Override
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    // 保存映射结果集得到的结果对象
    final List<Object> multipleResults = new ArrayList<Object>();

    int resultSetCount = 0;
    // 获取第一个 ResultSet d对象,可能存在多个 ResultSet ,也只获取第一个
    ResultSetWrapper rsw = getFirstResultSet(stmt);

    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    // 如果结果集不为空,则 resultMaps 集合不能为空,抛出异常
    while (rsw != null && resultMapCount > resultSetCount) { // 遍历 resultMaps 集合
      ResultMap resultMap = resultMaps.get(resultSetCount);
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
      while (rsw != null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }

    return collapseSingleResultList(multipleResults);
  }
}

Mybatis 会将结果集按照文件中定义的映射规则,例如 <resultMap> 节点、resultType 属性等,映射成响应的结果对象。这样可以有效的避免重复的 JDBC 代码。

// 处理结果集,生成相应的结果对象结婚

// 处理结果集,返回相应的游标对象

// 处理存储过程输出参数

通过 select 语句查询数据库得到的结果集由 DefaultResultSetHandler#handleResultSets 方法处理,该方法可以处理 Statement、PreparedStatement、CallableStatement 产生的结果集。

精彩内容推送,请关注公众号!
最近更新时间: 3/24/2020, 9:44:42 PM