Executor


前言

之前在用到Mybatis插件的时候就有提到过要好好分析下Mybatis的四大组件,但是由于之前一直在弄公司的消息服务器集群搭建的事情,就耽搁了好一阵子。现在终于有时间了,我们就来好好看看Mybatis的源码。

从一次查询过程说起

上一次我们我们讲述了Mapper接口以及命令模式的具体命令类MapperMethod。

我们接着之前的分析继续往下走这个查询过程。

接下来就该来到DefaultSqlSession类了。

SqlSession.selectList接MapperMethod

  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      //1.从confguration(构造DefaultSQLSession使用了建造者模式,configuration对象就是实际具体的建造者)对象中得到MappedStatement对象
      MappedStatement ms = configuration.getMappedStatement(statement);
      //2.使用执行器来进行查询的工作,现在我们的重心就到了Executor执行器上了
      List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
      return result;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

上述分析,我们得知了,真正的查询操作是通过Executor执行器代劳的。

Executor

在上述代码中,我们可以知道Executor对象是DefaultSqlSession类中的一个属性,那么首先,我们就得知道Executor是怎么初始化的。

我们从得到DefatulSqlSession的

SqlSession session= sessionFactory.openSession();

这行代码为入口分析

 sessionFactory.openSession();
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //1、在这里初始化了executor
      final Executor executor = configuration.newExecutor(tx, execType);
      //2、并将executor作为DefaultSqlSession构造方法的实参传入,Executor和SqlSession的联系就是在这里生成的。
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
  • configuration.newExecutor(tx, execType);
  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      //如果没有显式的设置,则使用SimpleExecutor,一般都是使用这个Executor
      executor = new SimpleExecutor(this, transaction);
    }
    //如果是缓存了的执行器,就新建缓存专用的执行器。
    if (cacheEnabled) {

      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

该方法通过Mybatis相关配置文件中设置的ExecutorType来决定(如果没有显式的设置,则使用默认的Type)到底生成哪种Executor。

这里对几种Executor进行介绍一下,并说明用使用显式配置时该如何配置

SimpleExecutor – SIMPLE 就是普通的执行器。

ReuseExecutor -执行器会重用预处理语句(prepared statements)

BatchExecutor –它是批量执行器

这些就是mybatis的三种执行器。你可以通过配置文件的settings里面的元素defaultExecutorType,配置它,默认是采用SimpleExecutor

  <setting name="defaultExecutorType" value="SIMPLE"/>

如果你在Spring运用它,那么你可以这么配置它:

<bean id="sqlSessionTemplateBatch" class="org.mybatis.spring.SqlSessionTemplate">       
<constructor-arg index="0" ref="sqlSessionFactory" />    
<!--更新采用批量的executor -->    
<constructor-arg index="1" value="BATCH"/>    
</bean>    

继续分析查询过程

  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      //1.从confguration(构造DefaultSQLSession使用了建造者模式,configuration对象就是实际具体的建造者)对象中得到MappedStatement对象
      MappedStatement ms = configuration.getMappedStatement(statement);
      //2.使用执行器来进行查询的工作,现在我们的重心就到了Executor执行器上了
      List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
      return result;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
  • 1.warpCollection是对Method参数的又一次处理,我们看看具体做了些什么
  private Object wrapCollection(final Object object) {
    //我们看到如果经过MapperMethod封装的参数的类型是List类型的话
    if (object instanceof List) {
      StrictMap<Object> map = new StrictMap<Object>();
      //将object作为Value存入map中,而且Key为list,StrictMap继承了HashMap,并且该HashMap的Key是String类型的,这一层封装的意义就在于限定该map的Key只能为String类型
      //这里更需要引起我们注意的是这里的Key为list,当你第一眼看到这个list的时候会想到什么呢,
      //反正我是一下就想到了在使用动态sql的foreach节点时候出现过list,所以,为了能够对Mapper配置文件中的list对应的对象群出来,这里先将list设置好,这就为什么当参数是List类型时候放入Map时的Key设为list了。
       map.put("list", object);
      return map;
    } else if (object != null && object.getClass().isArray()) {
      StrictMap<Object> map = new StrictMap<Object>();
     //当数组类型时,放入map中,Key为array,理由同上
      map.put("array", object);
      return map;
    }
    return object;
  }

warpCollection这个方法的作用就是:

将List或者Array类型转化为Map类型的,并且存入Map时一定要给限定相应的Key,至于为什么要转为Map类型的,当我们分析到后面的时候自然就会有答案了,现在不用深究。

public static class StrictMap<V> extends HashMap<String, V> {

    private static final long serialVersionUID = -5741767162221585340L;

    @Override
    public V get(Object key) {
      if (!super.containsKey(key)) {
        throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + this.keySet());
      }
      return super.get(key);
    }

  }

}
  • 2.query方法
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    //1.  BoundSql是对sql的封装,是MappedStatement对Mapper配置文件提取后将SQL的相关信息封装后都集中了BoundSql中,具体是怎么集中封装的,下面会分析
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
 //2
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

  • 1.MappedStatement的getBoundSql
public BoundSql getBoundSql(Object parameterObject) {
  //2 DynamicSqlSource的getBoundSql
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
  //将得到的paramterMappings放入到BoundSql对象中,这点是非常重要的
    if (parameterMappings == null || parameterMappings.size() <= 0) {
      boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
    }
    • 2.DynamicSqlSource的getBoundSql(因为使用了动态SQL,所以使用的DynamicSqlSource,关于各种情况具体是怎么封装sql的,也就是关于BoundSql对象以及SqlSource接口和相应的一些实现类,会有一个专门的博客介绍,对于Mybatis源码的一些关键类,都应该有单独的博客介绍,但是我们还是先有一个大体的认识)

 public BoundSql getBoundSql(Object parameterObject) {
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    //3.将Mapper配置文件中的SQL组装起来,在初始化的过程中,该SQL对应的处理节点分成了两个StaticSqlNode,和一个ForEachSqlNode。
   rootSqlNode.apply(context);
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
   //4.得到SqlSource对象,最重要的是parse方法让我们得到paramtermappings,这个对象对我们执行参数化sql时非常重要
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
      boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
    }
    return boundSql;
  }
    • 3.DynamicContext的创建
  public static final String PARAMETER_OBJECT_KEY = "_parameter";
  public static final String DATABASE_ID_KEY = "_databaseId";
public DynamicContext(Configuration configuration, Object parameterObject) {
    if (parameterObject != null && !(parameterObject instanceof Map)) {
      MetaObject metaObject = configuration.newMetaObject(parameterObject);
      bindings = new ContextMap(metaObject);
    } else {
      bindings = new ContextMap(null);
    }
    bindings.put(PARAMETER_OBJECT_KEY, parameterObject);
    bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId());
  }

这里没有特别难理解的地方,就是DynamicContext中有个ContextMap类型(同样继承了HashMap)的属性bindings,这个属性是非常重要的,然后将两个元素放入这个Map中,这里需要注意的是我们会将Method封装参数放入Map中,并且对应的Key是_parameter。一看到_parameter,我们肯定就想到了在学习编写Mapper配置文件中使用过这个。

3.(1)MixedSqlNode

  private List<SqlNode> contents;

  public MixedSqlNode(List<SqlNode> contents) {
    this.contents = contents;
  } 
public boolean apply(DynamicContext context) {
    for (SqlNode sqlNode : contents) {
      sqlNode.apply(context);
    }
    return true;
  }

这里的contents就对应的两个StaticSqlNode和一个ForEachSqlNode,如何拼接ForEachSqlNode,以及如何提取ps.setParameter()所用到的信息是问题的关键。

public boolean apply(DynamicContext context) {
    Map<String, Object> bindings = context.getBindings();
    final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings);
    if (!iterable.iterator().hasNext()) {
      return true;
    }
    boolean first = true;
    applyOpen(context);
    int i = 0;
    for (Object o : iterable) {
      DynamicContext oldContext = context;
      if (first) {
        context = new PrefixedContext(context, "");
      } else {
        if (separator != null) {
          context = new PrefixedContext(context, separator);
        } else {
          context = new PrefixedContext(context, "");
        }
      }
      int uniqueNumber = context.getUniqueNumber();
      if (o instanceof Map.Entry) { // Issue #709 
        @SuppressWarnings("unchecked") 
        Map.Entry<Object, Object> mapEntry = (Map.Entry<Object, Object>) o;
        applyIndex(context, mapEntry.getKey(), uniqueNumber);
        applyItem(context, mapEntry.getValue(), uniqueNumber);
      } else {
        applyIndex(context, i, uniqueNumber);
        applyItem(context, o, uniqueNumber);
      }
      contents.apply(new FilteredDynamicContext(configuration, context, index, item, uniqueNumber));
      if (first) first = !((PrefixedContext) context).isPrefixApplied();
      context = oldContext;
      i++;
    }
    applyClose(context);
    return true;
  }
    • 4.最后我们需要了解下parse方法,注意这个方法的具体实现里面得到了paramterMappings,这是非常重要的。
  public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
    ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
    //在这个过程中会把 #{} 里的信息赋给paramterMappings
    GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
    String sql = parser.parse(originalSql);
    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
  }
  • 2.BaseExecutor的query方法
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    //生成一个CacheKey
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }

终于分析到这个方法的最后一行代码了。

BaseExecutor的query方法

  @SuppressWarnings("unchecked")
  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()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      //查看是不是已经有查询过了,有的话直接在本地缓存中取出来
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        //如果是存储过程,需要把输出处理一下,具体处理目前没有细看
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        //如果没有本地缓存或者使用了resultHandler,需要到数据库中去查。
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      deferredLoads.clear(); // issue #601
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        clearLocalCache(); // issue #482
      }
    }
    return list;
  }

queryFromDatabase

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

SimpleExecutor

  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();
      //在这里初始化了除了Excutor的其他三大组件,注意了,这行代码完成的工作很重要,而Executor对象则在初始化得到DefaultSqlSession对象的时候就已经注入到其中了。
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

在这个方法里面,我们完成了得到结果的所有流程。

1、我们先得到Configuration对象。

2、利用COnfiguration对象去生成其他三大组件对象,用三大组件对象完成查询的具体操作。

3、利用三大组件进行具体得到结果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值