深入浅出学习MyBatis Executor原理 积累技术深度

Executor 是如何执行 sql语句的?

Executor 是一个基础接口,它的类图:

BaseExecutor 是一个抽象类实现了部分Executor的方法,降低子类的实现难度。

CachingExecutor 是缓存执行器,使用适配器模式包装原本的Executor,内部有一个 TransactionCacheManager 对象是一个缓存管理器,每次查询时会查询缓存,未命中再用原本的执行器执行查询语句,后添加到缓存中。由于每次 openSession() 都会创建新的执行器,所以缓存的作用域是一个会话范围。

SimpleExecutor 是默认执行器。

ReuseExecutor 是复用执行器。

下面来分析下查询的主流程

SqlSession -> CachingExecutor(开启了缓存) -> BaseExecutor(未命中缓存) -> SimpleExecutor(执行数据库查询) 的调用链

CachingExecutor 执行源码

@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 拿到 sql
  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 {
  Cache cache = ms.getCache();
  if (cache != null) {
    // 有需要是否清空缓存
    flushCacheIfRequired(ms);
    if (ms.isUseCache() && resultHandler == null) {
      ensureNoOutParams(ms, parameterObject, boundSql);
      @SuppressWarnings("unchecked")
    // TransactionalCacheManager 维护了一个map,缓存都在其中
      List<E> list = (List<E>) tcm.getObject(cache, key);
    // 缓存未命中就交给原本的Executor执行
      if (list == null) {
        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);
}

BaseExecutor执行源码

BaseExecutor执行源码。BaseExecutor和SimpleExecutor是父子关系,采用模板方法设计的这2个类相互配合。在base里定义了query查询的整体逻辑,包括查询一级缓存和查询数据库的顺序。然后在simple执行器里定义真正查询数据库的逻辑。

@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
  if (closed) {
    throw new ExecutorException("Executor was closed.");
  }
  if (queryStack == 0 && ms.isFlushCacheRequired()) {
    // 和一级缓存有关,如果本次会话内执行了DML语句则清空一级缓存
    clearLocalCache();
  }
  List<E> list;
  try {
    queryStack++;
    // 查一级缓存,key是SQL+参数+分页信息构建的
    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);
    }
  } finally {
    queryStack--;
  }
  if (queryStack == 0) {
    for (DeferredLoad deferredLoad : deferredLoads) {
      deferredLoad.load();
    }
    // issue #601
    deferredLoads.clear();
    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
      // issue #482 一级缓存作用域默认是Session,会存在读不到最新提交的数据情况,违反RC隔离级别,所以作用域改成statement相当于禁止一级缓存
      clearLocalCache();
    }
  }
  return list;
}
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  List<E> list;
    // 这里做查询前,往缓存丢了个占位key
  localCache.putObject(key, EXECUTION_PLACEHOLDER);
  try {
    // doQuery() 是一个抽象函数,交由子类实现查询逻辑
    list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  } finally {
    // 删掉之前的占位key
    localCache.removeObject(key);
  }
    // 放入实际查询的结果
  localCache.putObject(key, list);
  if (ms.getStatementType() == StatementType.CALLABLE) {
    localOutputParameterCache.putObject(key, parameter);
  }
  return list;
}

SimpleExecutor执行源码

在base执行器的queryFromDatabase方法里会调用子类的doQuery方法做真正查询数据库逻辑,这种设计有多态思想根据不同实现子类执行不同逻辑。

@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();
    // 一通操作通过 mappedStatement 生成了 statementHandler,后续会又它去执行查询。 
    // 点进去看方法实现,可知这个handler实际是 RoutingStatementHandler 对象。
    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 接口及其实现类,跟Executor是一样的结构。这个接口看名字就知道是对sql语句进行管理的处理器对象。

  • BaseStatementHandler 是基础抽象类实现了部分 StatementHandler 的函数。
  • SimpleStatementHandler 是简单的sql语句处理器,它负责没有参数的sql语句。
  • PreparedStatementHandler 是预编译处理器,负责有参数sql。
  • CallableStatementHandler 是数据库存储过程处理器。
  • RoutingStatementHandler 是负责根据当前 mappedStatement 即上面说的sql语句类型选择一个合适的处理器执行的路由功能的对象。

RoutingStatementHandler:它的 crud 函数都是借由其它handler实现的。

因为大多数sql都是带参数的,所以以 PreparedStatementHandler 为例看看下面的过程:

@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  // 注意这里的 ps 对象已经是 java.sql.statement 对象,所以接下来就是用 jdbc api 发起查询了。
  PreparedStatement ps = (PreparedStatement) statement;
  // jdbc api 这里查询完毕后,ps 中就有了 resultSet
  ps.execute();
  // 用结果集处理器处理 resultSet,封装实体类等等...
  return resultSetHandler.<E> handleResultSets(ps);
}

我们看看 jdbc 的statement对象怎么拿到的,省去中间的曲折最终是由 PreparedStatementHandler 生成的:

@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
  String sql = boundSql.getSql();
  if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
    // 这个数组不知道是什么东东,反正最后是由 jdbc connection 生成预编译sql返回
// 这里需要注意,还没给这个sql设置参数
    String[] keyColumnNames = mappedStatement.getKeyColumns();
    if (keyColumnNames == null) {
      return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
    } else {
      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);
  }
}

ok,整个executor执行sql的流程基本分析完毕,有些细节如:生成了 PreparedStatement 对象后,会调用 ParameterHandler 去设置参数。执行完毕语句,会调用 ResultSetHandler 去处理结果集。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值