MyBatis 源码分析篇 5:Mapper 方法执行之 Executor

通过上一篇的讨论 MyBatis 源码分析篇 4:Mapper 方法执行,我们已经知道 MyBatis 在获取到 Mapper 接口之后,其方法是通过在动态代理中调用 SqlSession 的方法来执行数据库操作的。那么在 SqlSession 中它具体又是怎么做的呢?这一篇我们就一起来看看 SqlSession 是如何执行数据库操作的。

我们还是以上一篇使用的测试代码为入口继续 debug:

List author = mapper.selectAllTest();

selectAllTest 方法的 sql 如下:

select id, name, sex, phone from author

跳过前面的执行,我们直接进入方法 executeForMany():

  private  Object executeForMany(SqlSession sqlSession, Object[] args) {
    List result;
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.selectList(command.getName(), param, rowBounds);
    } else {
      result = sqlSession.selectList(command.getName(), param);
    }
    // issue #510 Collections & arrays support
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
      if (method.getReturnType().isArray()) {
        return convertToArray(result);
      } else {
        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
      }
    }
    return result;
  }

进入第 8 行代码,继续 debug,直到进入 org.apache.ibatis.session.defaults.DefaultSqlSession 类的 selectList 方法:

  @Override
  public  List selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

我们看到第 5 行代码:executor.query(...);。再查看一下 executor 的声明和 debug 的结果:

MyBatis 源码分析篇 5:Mapper 方法执行之 Executor_第1张图片
executor 的声明

MyBatis 源码分析篇 5:Mapper 方法执行之 Executor_第2张图片
executor 类型为 CachingExecutor

我们看到 executor 是 CachingExecutor 类型的,那么问题就来了:executor 是何时实例化又是如何实例化的呢?我们先带着这个问题继续往下走,直到 org.apache.ibatis.executor.CachingExecutor 类的 query() 方法:

  @Override
  public  List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    Cache cache = ms.getCache();
    if (cache != null) {
     //太长省略啦...
    }
    return delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

我们直接看最后一行代码,delegate. query(...);,再查看一下 delegate 的声明和 debug 的结果:

delegate 的声明
MyBatis 源码分析篇 5:Mapper 方法执行之 Executor_第3张图片
delegate 类型为 SimpleExecutor

咦?怎么又出来一个 SimpleExecutor?delegate 又是何时实例化以及如何实例化的呢?还记不记得上面我们遗留的问题(DefaultSqlSession 中的 executor)。事实上不知道大家还记不记得在 MyBatis 源码分析篇 2:SqlSession 一文中,我们就已经见过 Executor了(SqlSession 将数据库执行的具体操作委托给了 Executor 来实现)。那么现在我们就来认识一下 Executor 及其实现类吧。

Executor 是一个接口,声明了操作数据库的方法,其实现类有:

MyBatis 源码分析篇 5:Mapper 方法执行之 Executor_第4张图片
Executor 实现类

它们之间的关系是:BaseExecutor 和 CachingExecutor 直接实现了 Executor 接口;BatchExecutor、ClosedExecutor(内部类)、ReuseExecutor 和 SimpleExecutor 继承了 BaseExecutor。

MyBatis 将 Executor 类型的 executor 作为 SqlSession 持有的成员变量,来执行底层的数据库操作。该成员变量的实例化是在获取 session 时实现的:调用 SqlSessionFactory 中任意一个 openSession(...) 方法。

点进去该接口方法的实现类 org.apache.ibatis.session.defaults.DefaultSqlSessionFactory ,我们最终可以看到 :

  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);
      final Executor executor = configuration.newExecutor(tx, execType);
      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();
    }
  }

注意第 7 行代码:final Executor executor = configuration.newExecutor(tx, execType);,我们发现在这个地方对 executor 做了赋值操作,继续点进去代码,进入 org.apache.ibatis.session.Configuration 类的 newExecutor 方法:

  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 {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

这段代码则是对 executor 的具体实例化的操作,从这段代码可以看出,executor 具体使用哪个实现类来实例化取决于 executorType 的值。如果方法传入的 executorType 为空,那么 executorType 将赋值为 Configuration 持有的 defaultExecutorType,如果 defaultExecutorType 也为空,就默认赋值为 ExecutorType.SIMPLE(枚举值)。紧接着下面的几个 if 条件就根据 executorType 和 cacheEnabled 来决定如何实例化。

现在我们就能抽取出这段代码的关键决定因素:executorType 和 cacheEnabled。 假设我们执行的测试代码中调用的是无参的 openSession() 方法,那么我们就需要关注的是 defaultExecutorType。

在 Configuration 类中的成员变量 defaultExecutorType 默认值为 ExecutorType.SIMPLE,cacheEnabled 默认值为 true。

//...
protected boolean cacheEnabled = true;
//...
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
//...

根据这两个值以及上面实例化 executor 的代码,我们可以得出结论:初始时的 executor 是 CachingExecutor 类型的。

那么看到这里,第一个问题便迎刃而解了,我们现在就知道了 DefaultSqlSession 中的 executor 成员变量是在 openSession 时,通过 Configuration 中的 newExecutor() 方法实例化为 CachingExecutor 的(前提是未配置 cacheEnabled )

针对括号中的内容,需要补充的一点是,无论我们使用的是读取 XML 的方式还是通过 Class 的方式来获取 Configuration,都可以在配置中传入defaultExecutorType 和 cacheEnabled。 以 XML 方式为例,我们通过跟踪 defaultExecutorType 和 cacheEnabled 来整体看一下 Configuration 是如何读取配置的:

要想获取 SqlSession,首先要获取 SqlSessionFactory:

SqlSessionFactory  sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

进入org.apache.ibatis.session.SqlSessionFactoryBuilder 的 build() 方法:

  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      //...
    } finally {
      //...
    }
  }

我们继续点击第 4 行代码的 parser.parse() 方法,进入 org.apache.ibatis.builder.xml.XMLConfigBuilder:


  private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

从这个方法我们可以看到很多熟悉的东西,即在 mybatis-config.xml 中我们配置过的 properties 、settings、typeAliases、plugins、objectFactory、environments、mappers 等元素。以 settings 为例,点击进入 settingsElement(settings); 方法,我们会看到 settings 允许配置的所有子元素 setting 的内容:

  private void settingsElement(Properties props) throws Exception {
    configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
    configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
    configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
    configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
    configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
    configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
    configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
    configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
    configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
    configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
    configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
    configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
    configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
    configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
    configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
    configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
    configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
    configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
    configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
    @SuppressWarnings("unchecked")
    Class typeHandler = (Class)resolveClass(props.getProperty("defaultEnumTypeHandler"));
    configuration.setDefaultEnumTypeHandler(typeHandler);
    configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
    configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
    configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
    configuration.setLogPrefix(props.getProperty("logPrefix"));
    @SuppressWarnings("unchecked")
    Class logImpl = (Class)resolveClass(props.getProperty("logImpl"));
    configuration.setLogImpl(logImpl);
    configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
  }

其中,就包含 cacheEnabled(第 4 行,默认值为 true)和 defaultExecutorType(第 11 行,默认值为 "SIMPLE")。也就是说,如果我们想使用别的 Executor 实现类,只需要传入对应的 cacheEnabled 和 defaultExecutorType 就可以。

好了,到这里我们就剩下一个问题了:CachingExecutor 中的 Executor 类型的 delegate 是如何实例化成 SimpleExecutor 的?

喂,等等,这句话怎么读着这么绕呢?怎么 Executor 实现类里又持有一个 Executor 变量呢?事实上,如果你熟悉设计模式的话,我想你一定不难看出,在这里,MyBatis 使用了装饰器模式:CachingExecutor 为装饰器类,便于扩展。

MyBatis 源码分析篇 5:Mapper 方法执行之 Executor_第5张图片
CachingExecutor 的装饰器模式

另外,同样地,BaseExecutor 也使用了装饰器模式:

MyBatis 源码分析篇 5:Mapper 方法执行之 Executor_第6张图片
BaseExecutor 的装饰器模式

我们再次回到 Configuration 的 newExecutor(...) 方法:

  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 {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

这时候要关注的是实例化 CachingExecutor 的倒数第 5 行代码:executor = new CachingExecutor(executor);。通过第一个问题的讲解我们已经得出结论:默认情况下,在 if (cacheEnabled) 执行之前,executor 的为 SimpleExecutor 类型。那么由此而知,此时传入到 CachingExecutor 装饰器类中实际的类型为 SimpleExecutor,即 CachingExecutor 中的 delegate 此时为 SimpleExecutor 类型。好了,第二个问题便说清楚了。

现在我们就接着之前位置:delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); 继续跟进代码,我们已经知道 delegate 这时是 SimpleExecutor 类型的,但是 SimpleExecutor 类中没有该 query 方法的实现,那么 debug 就自然会进入其父类 BaseExecutor:

  @Override
  public  List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    //...
    List list;
    try {
      queryStack++;
      list = resultHandler == null ? (List) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    //...
    return list;
  }

继续跟进几步,发现它又跳入了子类 SimpleExecutor 的 doQuery() 方法:

  @Override
  public  List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

跟进第 8 行代码,进入 org.apache.ibatis.executor.statement.RoutingStatementHandler,我们会发现 RoutingStatementHandler 也是用了装饰器模式(真是无处不在的装饰器啊( ̄▽ ̄)/)。

  @Override
  public  List query(Statement statement, ResultHandler resultHandler) throws SQLException {
    return delegate.query(statement, resultHandler);
  }

继续跟进,最后,我们会进入 org.apache.ibatis.executor.statement.PreparedStatementHandler 的 query 方法,这时,看到一段非常熟悉的代码:

  @Override
  public  List query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler. handleResultSets(ps);
  }

可爱的 JDBC 代码有木有!!好啦,到这里我们终于吧啦完了 Mybatis 是如何执行数据库操作的。。

最后,总结一下(敲黑板),这篇文章我们以两个问题为出发点,讲解了 Executor 及其实现类,进一步引出了对 Configuration 何时且如何读取 XML 中的元素的讨论,并看到了无处不在的装饰器模式,我们在实际编码中也要善于合理地使用这些优秀的设计模式哦。

希望大家没有被我绕晕,个人经验,阅读源码呢,需要综合正向和反向思维,善于猜想和验证,勤于思考,找到合适的切入点,一点一点深入,看不懂了就多重复几次。一开始跟丢了呢,就各种笨办法上,例如
Ctrl+Shift+F(intellij idea)。比如我一开始不知道哪里实例化的 Executor 就是全局查找 new SimpleExecutor,然后反向推,一步步就找到了入口。还有一点要注意的是,看源码要带有目的性,即主次分明,比如我就是要看它是如何实现查询的,那么其他关于缓存啊,参数和结果集映射等代码就都先略过不看,就只一步步跟入实现查询的代码。每一块儿都可以这么来看,等到这样看完所有的关键点,就可以对整体有了一个很好的理解。

小可爱们,散会!!(〃'▽'〃)

附:

当前版本:mybatis-3.5.0
官网文档:MyBatis
项目实践:MyBatis Learn
手写源码:MyBatis 简易实现

你可能感兴趣的:(MyBatis 源码分析篇 5:Mapper 方法执行之 Executor)