Mybatis二级缓存源码详解(一)

解析基于Mybatis 3.4.4 Release,近几个版本应该都适用。

Mybatis二级缓存总开关默认是开启的。

public class Configuration {
    ......
    protected boolean cacheEnabled = true;
}

这样SqlSessionFactory在生成SqlSession时,就会把原来的Eexcutor包装成一个CachingExecutor:

public class DefaultSqlSessionFactory implements SqlSessionFactory {

    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);
          // 生成Executor
          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();
        }
    }

}


public class Configuration {

    protected boolean cacheEnabled = true;

    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) {   // 包装成 CachingExecutor
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

}

下面我们看看CachingExecutor在查询时是如何使用缓存的:

// CachingExecutor 实现Executor 接口,同时又持有一个Executor 的实例,大多数方法最终还是要转发到delegate去执行
// CachingExecutor 对 delegate 做了装饰,或者在查询时做了静态代理,如果有缓存则直接返回,没有就用delegate去执行
public class CachingExecutor implements Executor {

  private Executor delegate;
  private TransactionalCacheManager tcm = new TransactionalCacheManager();

  @Override
  public  List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    Cache cache = ms.getCache();   // 获取MappedStatement 上的缓存对象
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, parameterObject, boundSql);
        // 通过CacheKey 作为key去缓存里搜索
        List list = (List) tcm.getObject(cache, key);   
        if (list == null) {      
          // 如果缓存value不存在,那么借助delegate执行查询,然后存入Cache,这样相同的查询下次就能复用此结果了                  
          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);
  }

  // 是否要刷新缓存
  private void flushCacheIfRequired(MappedStatement ms) {
    Cache cache = ms.getCache();
    if (cache != null && ms.isFlushCacheRequired()) {      
      tcm.clear(cache);
    }
  }

}

以上代码有两点关键:

  1. Cache cache = MappedStatement .getCache();
  2. tcm.getObject(cache, key); tcm.putObject(cache, key, list);

我们先看第一点吧,解析Cache对象的实例化过程。

Mybatis在构建SqlSessionFactory时,会解析指定路径下的所有Mapper.xml文件:

public class XMLMapperBuilder extends BaseBuilder {

    private void configurationElement(XNode context) {
        try {
            String namespace = context.getStringAttribute("namespace");
            if (namespace == null || namespace.equals("")) {
                throw new BuilderException("Mapper's namespace cannot be empty");
            }
            builderAssistant.setCurrentNamespace(namespace);
            cacheRefElement(context.evalNode("cache-ref"));
            cacheElement(context.evalNode("cache"));              //解析cache节点
            parameterMapElement(context.evalNodes("/mapper/parameterMap"));
            resultMapElements(context.evalNodes("/mapper/resultMap"));
            sqlElement(context.evalNodes("/mapper/sql"));
            buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
        } catch (Exception e) {
            throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
        }
    }

}

以下就是一个< cache>节点的定义形式:

type="PERPETUAL" eviction="LRU" size="60" blocking="false" flushInterval="24" readOnly="false"/>

下面看< cache>节点的解析代码:

public class XMLMapperBuilder extends BaseBuilder {

    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);
            boolean blocking = context.getBooleanAttribute("blocking", false);
            Properties props = context.getChildrenAsProperties();
            builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
        }
    }

}

在解析< cache>节点时,其元素大部分都有默认值,通过typeAliasRegistry将值转换为想要的类型:

public class Configuration {

    public Configuration() {
        ......
        typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
        typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
        typeAliasRegistry.registerAlias("LRU", LruCache.class);
        typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
        typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
        ......
    }

}

Configuration 在实例化时就为typeAliasRegistry注册了很多别名,在解析Mapper.xml时就能通过别名来获取到真实的Class类型。

接下来看Cache对象的创建过程:

public class MapperBuilderAssistant extends BaseBuilder {

    private String currentNamespace;
    private Cache currentCache;

    public Cache useNewCache(Classextends Cache> typeClass,
      Classextends Cache> evictionClass,
      Long flushInterval,
      Integer size,
      boolean readWrite,
      boolean blocking,
      Properties props) {
    // 每个Cache都是和currentNamespace紧密关联的,也就是Cache和Mapper.xml里面的namespace元素关联的,也就是每个Mapper匹配一个Cache
    // 也就是说Mapper.xml内元素解析成成的MappedStatement共用一个Cache对象
    Cache cache = new CacheBuilder(currentNamespace)  
        .implementation(valueOrDefault(typeClass, PerpetualCache.class))
        .addDecorator(valueOrDefault(evictionClass, LruCache.class))
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        .build();                    // 开始通过建造者构建Cache
    configuration.addCache(cache);   // 将Cache对象添加到Configuration中
    currentCache = cache;            //设置currentCache = cache
    return cache;
  }

}

已经将所有Cache所有相关的数据注入到CacheBuilder中了,接下来看其build( )方法:

public class CacheBuilder {
    private String id;
    private Class extends Cache> implementation;
    private List<Class extends Cache>> decorators;
    private Integer size;
    private Long clearInterval;
    private boolean readWrite;
    private Properties properties;
    private boolean blocking;

    public Cache build() {
        setDefaultImplementations();      
        Cache cache = newBaseCacheInstance(implementation, id);
        setCacheProperties(cache);
        // issue #352, do not apply decorators to custom caches
        if (PerpetualCache.class.equals(cache.getClass())) {
            for (Class extends Cache> decorator : decorators) {
                // 将PerpetualCache外层装饰上 < cache> 内 "eviction" 指定的缓存淘汰策略,可以是 "FIFO","LRU","SOFT","WEAK" 中的一个
                cache = newCacheDecoratorInstance(decorator, cache);
                setCacheProperties(cache);
            }
            // 根据< cache> 的数据,在Cache对象外层再依次封装上相应的装饰类
            cache = setStandardDecorators(cache);
        } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
            cache = new LoggingCache(cache);
        }
        return cache;
    }

    // Cache的原始实现和原始装饰为空时使用默认值
    private void setDefaultImplementations() {
        if (implementation == null) {
            implementation = PerpetualCache.class;
            if (decorators.isEmpty()) {
                decorators.add(LruCache.class);
            }
        }
    }

    // 根据< cache> 元素内的数据,生成相应的装饰类,封装原始的Cache对象
    private Cache setStandardDecorators(Cache cache) {
        try {
            MetaObject metaCache = SystemMetaObject.forObject(cache);
            if (size != null && metaCache.hasSetter("size")) {
                metaCache.setValue("size", size);
            }
            if (clearInterval != null) {   // 加上定时清空策略, 时间单位时小时
                cache = new ScheduledCache(cache);
                ((ScheduledCache) cache).setClearInterval(clearInterval);
            }
            if (readWrite) {   // 加上读写装饰,以二进制数组的形式缓存查询结果
                cache = new SerializedCache(cache);
            }
            cache = new LoggingCache(cache);    // 加上日志装饰,以便打印日志
            cache = new SynchronizedCache(cache);   // 加上同步装饰, 将非线程安全的方法都用 "synchronized" 修饰,确保线程安全
            if (blocking) {
                cache = new BlockingCache(cache);   // 加上对同一key的阻塞访问装饰
            }
            return cache;
        } catch (Exception e) {
            throw new CacheException("Error building standard cache decorators.  Cause: " + e, e);
        }
  }

}

以SynchronizedCache为例,看看其对原始Cache的装饰方式:

public class SynchronizedCache implements Cache {

  private Cache delegate;

  public SynchronizedCache(Cache delegate) {
    this.delegate = delegate;
  }

  @Override
  public String getId() {
    return delegate.getId();
  }

  @Override
  public synchronized int getSize() {
    return delegate.getSize();
  }

  @Override
  public synchronized void putObject(Object key, Object object) {
    delegate.putObject(key, object);
  }

  @Override
  public synchronized Object getObject(Object key) {
    return delegate.getObject(key);
  }

  @Override
  public synchronized Object removeObject(Object key) {
    return delegate.removeObject(key);
  }

  @Override
  public synchronized void clear() {
    delegate.clear();
  }

  @Override
  public int hashCode() {
    return delegate.hashCode();
  }

  @Override
  public boolean equals(Object obj) {
    return delegate.equals(obj);
  }

  @Override
  public ReadWriteLock getReadWriteLock() {
    return null;
  }

}

SynchronizedCache 将所有非线程安全的方法都加上了 “synchronized ”关键字,确保了缓存数据操作的线程安全性。因为这里的缓存大部分仅仅是对缓存容器的操作,不会占用太多资源和时间,所以性能还是可以接受的。

总结:这篇博客主要讲了 < cache> 元素的解析和实例化的过程,使用装饰者模式,根据< cache>元素内的数据定义,来生成相应的装饰者对象去装饰原始的PerpetualCache对象

你可能感兴趣的:(mybatis)