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

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

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

List<Author> author = mapper.selectAllTest();

selectAllTest 方法的 sql 如下:

select id, name, sex, phone from author

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

  private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
    } else {
      result = sqlSession.<E>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 <E> List<E> 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 的结果:

executor 的声明

executor 类型为 CachingExecutor

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

  @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) {
     //太长省略啦...
    }
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

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

delegate 的声明
delegate 类型为 SimpleExecutor

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

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

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<? extends TypeHandler> typeHandler = (Class<? extends TypeHandler>)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<? extends Log> logImpl = (Class<? extends Log>)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 为装饰器类,便于扩展。

CachingExecutor 的装饰器模式

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

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.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); 继续跟进代码,我们已经知道 delegate 这时是 SimpleExecutor 类型的,但是 SimpleExecutor 类中没有该 query 方法的实现,那么 debug 就自然会进入其父类 BaseExecutor:

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    //...
    List<E> list;
    try {
      queryStack++;
      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--;
    }
    //...
    return list;
  }

继续跟进几步,发现它又跳入了子类 SimpleExecutor 的 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();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

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

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

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

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

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

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

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

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

附:

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,192评论 6 511
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,858评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,517评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,148评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,162评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,905评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,537评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,439评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,956评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,083评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,218评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,899评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,565评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,093评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,201评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,539评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,215评论 2 358