深入理解 Mybatis - Executor(二)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 深入理解 Mybatis - Executor(二)

ReuseExecutor#


这个ReuseExecutor相对于SimpleExecutor来说,不同点就是它先来的对Statement的复用,换句话说,某条Sql对应的Statement创建出来后被放在容器中保存起来,再有使用这个statement的地方就是容器中拿就行了


他是怎么实现的呢? 看看下面的代码就知道了


public class ReuseExecutor extends BaseExecutor {
    private final Map<String, Statement> statementMap = new HashMap();
    public ReuseExecutor(Configuration configuration, Transaction transaction) {
        super(configuration, transaction);
    }


嗯! 所谓的容器,不过是一个叫statementMap的HashMap而已

下一个问题: 这个容器什么时候派上用场呢? 看看下面的代码也就知道了--this.hasStatementFor(sql)


private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        BoundSql boundSql = handler.getBoundSql();
        String sql = boundSql.getSql();
        Statement stmt;
        if (this.hasStatementFor(sql)) {
            stmt = this.getStatement(sql);
            this.applyTransactionTimeout(stmt);
        } else {
            Connection connection = this.getConnection(statementLog);
            stmt = handler.prepare(connection, this.transaction.getTimeout());
            this.putStatement(sql, stmt);
        }
        handler.parameterize(stmt);
        return stmt;
    }


最后一点: 当MyBatis知道发生了事务的提交,回滚等操作时,ReuseExecutor会批量关闭容器中的Statement


BatchExecutor#


这个执行器相对于SimpleExecutor的特点是,它的update()方法是批量执行的

执行器提交或回滚事务时会调用 doFlushStatements,从而批量执行提交的 sql 语句并最终批量关闭 statement 对象。


CachingExecutor与二级缓存#


首先来说,这个CachingExecutor是什么? 那就得看一下的属性,如下:


public class CachingExecutor implements Executor {
  private final Executor delegate;
  private final TransactionalCacheManager tcm = new TransactionalCacheManager();


让我们回想一下他的创建时机,没错就是在每次创建一个新的SqlSession时创建出来的,源码如下,这就出现了一个惊天的大问号!!!,一级缓存和二级缓存为啥就一个属于SqlSession级别,另一个却被所有的SqlSession共享了? 这不是开玩笑呢? 我当时确实也是真的蒙,为啥他俩都是随时用随时new,包括上面代码中的TransactionalCacheManager也是随时用随时new,凭什么它维护的二级缓存就这么牛? SqlSession挂掉后一级缓存也跟着挂掉,凭什么二级缓存还在呢?


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


先说一下,我是看到哪行代码后意识到二级缓存是这么特殊的,如下:大家也看到了,下面代码中的tcm.getObject(cache, key);,是我们上面新创建出来的TransactionalCacheManager,然后通过这个空白的对象的getObject()竟然就将缓存中的对象给获取出来了,(我当时忽略了入参位置的cache,当然现在看,满眼都是这个cache)


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, 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);
  }


我当时出现这个问题完全是我忽略了一部分前面解析配置文件部分的源码,下面我带大家看看这部分源码是怎么执行的


一开始MyBatis会创建一个XMLConfigBuilder用这个builder去解析配置文件(因为我们环境是单一的MyBatis,并没有和其他框架整,这个builder就是用来解析配置文件的)

我们关注什么呢? 我们关注的是这个builder解析<mapper>标签的,源码入下:


private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      ...
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers"));


关注这个方法中的configuration.addMapper(mapperInterface);方法,如下: 这里面存在一个对象叫做,MapperRegistry,这个对象叫做mapper的注册器,其实我觉得这是个需要记住的对象,因为它出现的频率还是挺多的,它干什么工作呢? 顾名思义,解析mapper呗 我的当前是基于注解搭建的环境,于是它这个MapperRegistry为我的mapper生成的对象就叫MapperAnnotationBuilder见名知意,这是个基于注解的构建器


public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
  }


所以说我们就得去看看这个解析注解版本mapper的builder,到底是如何解析我提供的mapper的,源码如下:


public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
      loadXmlResource();
      configuration.addLoadedResource(resource);
      assistant.setCurrentNamespace(type.getName());
      parseCache();
      parseCacheRef();
      Method[] methods = type.getMethods();
      for (Method method : methods) {
        try {
          // issue #237
          if (!method.isBridge()) {
            parseStatement(method);
          }
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }


方法千千万,但是我关注的是它的parseCache();方法,为什么我知道来这里呢? (我靠!,我找了老半天...)


接下来就进入了一个高潮,相信你看到下面的代码也会激动, 为什么激动呢? 因为我们发现了Mybatis处理@CacheNamespace注解的细节信息


private void parseCache() {
    CacheNamespace cacheDomain = type.getAnnotation(CacheNamespace.class);
    if (cacheDomain != null) {
      Integer size = cacheDomain.size() == 0 ? null : cacheDomain.size();
      Long flushInterval = cacheDomain.flushInterval() == 0 ? null : cacheDomain.flushInterval();
      Properties props = convertToProperties(cacheDomain.properties());
      assistant.useNewCache(cacheDomain.implementation(), cacheDomain.eviction(), flushInterval, size, cacheDomain.readWrite(), cacheDomain.blocking(), props);
    }
  }


再往下跟进这个assistant.useNewCache()方法,就会发现,MyBatis将创建出来的一个Cache对象,这个Cache的实现类叫BlockingCache


创建出来的对象给谁了?

  • Configuration对象自己留了一份 (放在了 caches = new StrictMap<>("Caches collection");中)
  • 当前类MapperBuilderAssistant也保留一了一份
  • 最主要的是MappedStatement对象中也保留了一份mappedStatement.cache

说了这么多了,附上一张图,用来纪念创建这个Cache的成员



小结#


其实上面创建这个Cache对象才是二级缓存者, 前面说的那个CachingExecutor中的TransactionalCacheManager不过是拥有从这个Cache中获取数据的能力而已

我有调试他是如何从Cache中获取出缓存,事实证明,二级缓存中存放的不是对象,而是被序列化后存储的数据,需要反序列化出来


下图是Mybatis反序列化数据到新创建的对象中的截图



下图是TransactionalCacheManager是如何从Cache中获取数据的调用栈的截图



二级缓存与一级缓存的互斥性#


第一点: 通过以上代码的调用顺序也能看出,二级缓存在一级缓存之前优先被执行, 也就是说二级缓存不存在,则查询一级缓存,一级缓存再不存在,就查询DB

第二点: 就是说,对于二级缓存来说,无论我们有没有开启事务的自动提交功能,都必须手动commit()二级缓存才能生效,否则二级缓存是没有任何效果的

第三点: CachingExecutor提交事务时的源码如下:


@Override
  public void commit(boolean required) throws SQLException {
    // 代理执行器提交
    delegate.commit(required);
    // 事务缓存管理器提交
    tcm.commit();
  }


这就意味着,TransactionalCacheManager和BaseExecutor的实现类的事务都会被提交

为什么说二级缓存和以及缓存互斥呢?可以看看BaseExecutor的源码中commit()如下: 怎么样? 够互斥吧,一个不commit()就不生效,commit()完事把一级缓存干掉了


@Override
  public void commit(boolean required) throws SQLException {
    if (closed) {
      throw new ExecutorException("Cannot commit, transaction is already closed");
    }
    clearLocalCache();
    flushStatements();
    if (required) {
      transaction.commit();
    }
  }


相关文章
|
6月前
|
SQL 缓存 Java
MyBatis 四大核心组件之 Executor 源码解析
MyBatis 四大核心组件之 Executor 源码解析
|
SQL 存储 缓存
MyBatis四大组件Executor、StatementHandler、ParameterHandler、ResultSetHandler 详解(2)
MyBatis四大组件Executor、StatementHandler、ParameterHandler、ResultSetHandler 详解
239 0
|
SQL 存储 缓存
MyBatis四大组件Executor、StatementHandler、ParameterHandler、ResultSetHandler 详解(1)
MyBatis四大组件Executor、StatementHandler、ParameterHandler、ResultSetHandler 详解
224 0
|
缓存 安全 Java
MyBatis源码-解读Executor的三个实现类之BatchExecutor(批处理执行器)
MyBatis源码-解读Executor的三个实现类之BatchExecutor(批处理执行器)
312 0
|
SQL 缓存 Java
MyBatis源码-解读Executor的三个实现类之ReuseExecutor(重用执行器)
MyBatis源码-解读Executor的三个实现类之ReuseExecutor(重用执行器)
153 0
|
SQL Java 数据库连接
MyBatis源码-解读Executor的三个实现类之SimpleExecutor(简单执行器)
MyBatis源码-解读Executor的三个实现类之SimpleExecutor(简单执行器)
136 0
|
SQL 缓存 安全
MyBatis源码-深入理解MyBatis Executor的设计思想
MyBatis源码-深入理解MyBatis Executor的设计思想
109 0
|
SQL 缓存 Java
Java 最常见的面试题:mybatis 有哪些执行器(Executor)?
Java 最常见的面试题:mybatis 有哪些执行器(Executor)?
112 0
|
SQL 存储 XML
详解MyBatis中Executor执行SQL语句的过程
在详解MyBatis的SqlSession获取流程文章中已经知道,MyBatis中获取SqlSession时会创建执行器Executor并存放在SqlSession中,通过SqlSession可以获取映射接口的动态代理对象,动态代理对象的生成可以参考详解MyBatis加载映射文件和动态代理,可以用下图进行概括。
273 0
|
SQL 缓存 Java
深入理解 Mybatis - Executor(一)
本片博客的目的就是探究如上图中从顶级接口Executor中拓展出来的各个子执行器的功能,以及进一步了解Mybatis的一级缓存和二级缓存
219 0