Mybatis源码分析--缓存源码分析

247 阅读4分钟

Mybatis提供一级和二级缓存。
一级缓存只存在session级别,底层用一个Map存储。默认开启。
二级缓存与namespace(一个Mapper接口)对应,必须显示commit才生效。默认不开启。


上面这幅图很好的描述了MyBatis缓存的执行流程。
一次查询,先从二级缓存找,然后一级缓存最后数据库。

 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 {
    //1正常会走到这里
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
    //2 对上面的excutor进行保装 返回CachingExecutor。
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

上面2处用到了装饰器模式,对原始类SimpleExecutor包了一层。当下次调用时,就使用了委托模式进行调用。
下面我们看看一次查询的流程。最终会走到

CachingExecutor
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      {//1这里拿到二级缓存对象
    Cache cache = ms.getCache();
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
      //2查寻之前,先到二级缓存里找一下
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
        //3查寻数据库
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          //4 结果放进缓存
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    //6一级缓存直接走这里,跳过上面if
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }  

上面的逻辑很简单,但要我们自己设计一个缓存系统该怎么设计呢?

我们先看下MyBatis缓存的设计

MyBatis采用了委托机制和装饰者模式来设计的。
基础缓存: 为缓存数据最终存储的处理类,默认为 PerpetualCache,基于Map存储;可自定义存储处理,如基于EhCache、Memcached等;
排除算法缓存:
当缓存数量达到一定大小后,将通过算法对缓存数据进行清除。默认 Lru ,提供有 fifo 等
装饰器缓存:
缓存put/get处理前后的装饰器,如使用 LoggingCache 输出缓存命中日志信息、使用 SerializedCache 对 Cache的数据 put或get 进行序列化及反序列化处理、当设置flushInterval(默认1/h)后,则使用 ScheduledCache 对缓存数据进行定时刷新等

一般缓存框架的数据结构基本上都是 Key-Value 方式存储,MyBatis 对于其 Key 的生成采取规则为: [hash:checksum:mappedStementId:offset:limit:execSql:qryParams]。 对于并发 Read/Write 时缓存数据的同步问题,MyBatis 默认基于 JDK/concurrent中的ReadWriteLock
那么,怎么把上面的组建给结合起来呢?
加载配置文件时,configuration中有这样一段代码来解析cache的配置:

private void cacheElement(XNode context) throws Exception {  
    if (context != null) {  
      // 基础缓存类型  
      String type = context.getStringAttribute("type", "PERPETUAL");  
      Class typeClass = typeAliasRegistry.resolveAlias(type);  
      // 排除算法缓存类型  
      String eviction = context.getStringAttribute("eviction", "LRU");  
      Class evictionClass = typeAliasRegistry.resolveAlias(eviction);  
      // 缓存自动刷新时间  
      Long flushInterval = context.getLongAttribute("flushInterval");  
      // 缓存存储实例引用的大小  
      Integer size = context.getIntAttribute("size");  
      // 是否是只读缓存  
      boolean readWrite = !context.getBooleanAttribute("readOnly", false);  
      Properties props = context.getChildrenAsProperties();  
      // 初始化缓存实现  
      builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, props);  
    }  
  } 

看看useNewCache是怎么实例化的。

public Cache useNewCache(Class typeClass,  
                           Class evictionClass,  
                           Long flushInterval,  
                           Integer size,  
                           boolean readWrite,  
                           Properties props) {  
    typeClass = valueOrDefault(typeClass, PerpetualCache.class);  
    evictionClass = valueOrDefault(evictionClass, LruCache.class);  
    // 这里构建 Cache 实例采用 Builder 模式,每一个 Namespace 生成一个  Cache 实例  
    Cache cache = new CacheBuilder(currentNamespace)  
        // Builder 前设置一些从XML中解析过来的参数  
        .implementation(typeClass)  
        .addDecorator(evictionClass)  
        .clearInterval(flushInterval)  
        .size(size)  
        .readWrite(readWrite)  
        .properties(props)  
        // 再看下面的 build 方法实现  
        .build();  
    configuration.addCache(cache);  
    currentCache = cache;  
    return cache;  
}  
  
public Cache build() {  
    setDefaultImplementations();  
    // 创建基础缓存实例  
    Cache cache = newBaseCacheInstance(implementation, id);  
    setCacheProperties(cache);  
    // 缓存排除算法初始化,并将其委托至基础缓存中  
    for (Class<? extends Cache> decorator : decorators) {  
      cache = newCacheDecoratorInstance(decorator, cache);  
      setCacheProperties(cache);  
    }  
    // 标准装饰器缓存设置,如LoggingCache之类,同样将其委托至基础缓存中  
    cache = setStandardDecorators(cache);  
    // 返回最终缓存的责任链对象  
    return cache;  
}  

这里我们看到来装饰器模式的威力。相当于把一个对象放到下一个要构造对象的构造方法中,但这样 是不是强耦合呢?


可见,所有构建的缓存实例已经通过责任链方式将其串连在一起,各 Cache 各负其责、依次调用,直到缓存数据被 Put 至 基础缓存实例中存储。