初探Mybatis源码——Mybatis的执行器SimpleExecutor、BaseExecutor、CachingExecutor之间的关系及调用时的先后顺序

本文详细梳理了Mybatis中Executor执行器的创建过程,从BaseExecutor到CachingExecutor再到SimpleExecutor的调用链,以及装饰器和责任链模式在其中的应用。重点讲解了query方法如何在不同执行器间流转和缓存策略的运用。
摘要由CSDN通过智能技术生成

最近在阅读Mybatis源码,一步步边调试边阅读时发现,执行器executor调用query方法时,一会执行CachingExecutor里的query,一会执行BaseExecutor里的query,被绕晕了,于是准备梳理一下他们的联系。

一、

首先需要明确:BaseExecutor、CachingExecutor均为接口Executor的实现类,SimpleExecutor是BaseExecutor的子类

二、

然后再看Configuration类中执行器的创建过程:

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) {
    //判断是否启用缓存(对应xml中的配置),如果启用,重新封装成缓存执行器
    //装饰器设计模式,允许向一个现有的对象添加新的功能,同时又不改变其结构
    executor = new CachingExecutor(executor);
  }
  //责任链设计模式,按照xml中配置的拦截器(可能有多个),依次对执行器设置
  executor = (Executor) interceptorChain.pluginAll(executor);
  return executor;
}

创建执行器对象一共分为三步:

第一步:根据指定的执行器类型,生成一个批量执行器BatchExecutor或复用执行器ReuseExecutor或简单执行器SimpleExecutor(三者是并列关系,本文以简单执行器SimpleExecutor为例)

第二步:根据xml中设置的是否开启二级缓存,决定是否将第一步获得的执行器进一步包装为缓存执行器CachingExecutor(装饰器设计模式,本文假设开启二级缓存)

<settings>
    <!--开启二级缓存-->
    <setting name="cacheEnabled" value="ture"/>
</settings>

包装时调用 CachingExecutor的构造方法,第一步中获得的执行器成为CachingExecutor的成员变量

private final Executor delegate;

public CachingExecutor(Executor delegate) {
  this.delegate = delegate;
  ...
}

第三步:根据xml中设置的拦截器,对第二步获得的执行器进一步包装(责任链设计模式)

第三步中将第二步的CachingExecutor作为Object对象传递,没有其他类的参与,因此第三步结束后返回的Executor接口指向的还是CachingExecutor这个实现类(为什么用Object传递最后类型不变,见最后测试)

三、

在通过动态代理调用时(以查询select为例):

private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
  try {
    MappedStatement ms = configuration.getMappedStatement(statement);
    return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

executor.query:接口executor调用其指向的实现类的query方法,上文已知,该接口指向实现类CachingExecutor,因此会调用CachingExecutor中的query,下面是CachingExecutor中的query方法:

@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  //SQL语句
  BoundSql boundSql = ms.getBoundSql(parameterObject);
  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();
  //null为未打开缓存
  if (cache != null) {
    flushCacheIfRequired(ms);
    if (ms.isUseCache() && resultHandler == null) {
      ensureNoOutParams(ms, boundSql);
      @SuppressWarnings("unchecked")
      List<E> list = (List<E>) tcm.getObject(cache, key);
      if (list == null) {
        list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
        tcm.putObject(cache, key, list); // issue #578 and #116
      }
      return list;
    }
  }
  return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

上方源码中能够看出,query调用流程如下:

第一步:CachingExecutor中前一个query先被调用后,在方法中再调用后一个query

第二步:后一个query中又有delegate.query,CachingExecutor在构造时,已经将delegate指向了SimpleExecutor对象,因此会转而调用SimpleExecutor中的query

第三步:但是在SimpleExecutor中没有直接写query,而是它的父类BaseExecutor实现了该方法,因此转而调用BaseExecutor中的query(即一开始定位的是SimpleExecutor中的query,但没找到该方法,去父类中找,在父类中找到了该方法),源码如下:

protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
      throws SQLException;

@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()) {
    clearLocalCache();
  }
  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--;
  }
  if (queryStack == 0) {
    for (DeferredLoad deferredLoad : deferredLoads) {
      deferredLoad.load();
    }
    // issue #601
    deferredLoads.clear();
    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
      // issue #482
      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;
  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;
}

第四步:BaseExecutor中的query调用queryFromDatabase方法,queryFromDatabase调用doQuery,doQuery是抽象方法,去子类中找实现(此时的子类对应的是SimpleExecutor

第五步:调用SimpleExecutordoQuery方法

这就是为什么在调用query方法时,会在几个Executor接口实现类中绕来绕去,以上是对调用过程的解释。

MyBatis博大精深!万物互联!

注:

1、如果一开始xml中没有设置二级缓存,就不会经过CachingExecutor的包装,那么最开始的Executor接口会指向SimpleExecutor,在调用query时会直接在SimpleExecutor中找query方法。

2、BatchExecutor、ReuseExecutor、SimpleExecutor三者并列,它们的父类是BaseExecutor公共的方法都在父类中实现了,父类中的抽象方法(如doQuery)每个子类各自的实现方法不同,子类重写父类的抽象方法即可,公共方法不用重复再写(这就是模板设计模式


小测试:写了个Demo模拟几个执行器的关系(按我的理解自己写的,不一定合理)

TestInterface接口(对应Executor接口):

public interface TestInterface {
    void show();
}

Simple实现类(对应SimpleExecutor): 

public class Simple implements TestInterface{
    @Override
    public void show() {
        System.out.println("Simple---");
    }
}

Caching实现类(对应CachingExecutor):

public class Caching implements TestInterface{
    TestInterface testInterface;

    public Caching(TestInterface testInterface) {
        this.testInterface = testInterface;
    }

    @Override
    public void show() {
        System.out.println("Caching---");
        testInterface.show();
    }
}

Plugin类(对应拦截器责任链设计模式那段,以Object传递对象) 

public class Plugin {
    Object test(Object target){
        return target;
    }
}

测试: 

public class App {
    public static void main(String[] args) {
        TestInterface testInterface = new Simple();
        testInterface = new Caching(testInterface);
        testInterface = (TestInterface) new Plugin().test(testInterface);
        testInterface.show();
    }
}

输出为:

Caching---
Simple---

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cyc头发还挺多的

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值