1.目的
本节主要是讲Mybatis的二级缓存,一级缓存是会话SqlSession级别的,二级缓存是Mapper级别的这个大家都知道,一级缓存主要是同一个SqlSession实例才可以缓存,那么出现commit等其他情况可能清除缓存,我想要再发起的会话还是相同的查询操作,最好也是可以把数据从缓存中获取出来。这个时候该如何实现呢?
这时候引出来二级缓存,以一个 Mapper 为生命周期,在这个 Mapper 内的同一个操作,无论发起几次会话都可以使用缓存来处理数据。叫二级缓存,是因为要在一级缓存基础上额外添加缓存操作,(也就是要引出来设计模式-装饰器模式),当会话发生 close、commit 操作时则把数据刷新到二级缓存中进行保存,直到执行器发生 update 操作时清空缓存。
本节最重点主要就是装饰器模式,所以先提装饰器模式的思路,就是不改变原有的类和方法,在其上包装了一成自己的功能,是现有类的包装。
二级缓存需要在设置中开启全局缓存,如图,开启了缓存,关闭了一级缓存,然后需要在XMLConfigBuilder中解析,解析完毕将全局缓存设置到Configuration中的cacheEnabled中,true和false代表是否开启。
由于下图在Mapper中设置了cache标签的一些属性,所以需要在XMLMapperBuilder类中需要解析一下,解析一级缓存类,二级缓存类,缓存间隔时间,队列大小以及属性等等,最后将属性构建到缓存构建器中,接着将缓存放入到当前类MapperBuilderAssistant的Cache变量赋值,然后将MapperBuilderAssistant存入的缓存变量存入到MappedStatement的cache里。到此构建结束。
执行时则是构建Executor时判断是否全局缓存,如果全局缓存则在DefaultSqlSession中给的Executor就是CachingExecutor,如果不是则是SimpleExecutor,这里相当于CachingExecutor做了一层SimpleExecutor包装,因为CachingExecutor里有一个Executor变量,这个变量传的就是SimpleExecutor,那么真正执行操作时,我们就可以执行SimpleExecutor的业务,而CachingExecutor主要执行缓存业务,这就是扩展其功能,却又不改变其功能,这里就是装饰器的设计精髓和思想。
CachingExecutor类里实现Executor类,所以还是实现query、update等等的方法,执行query方法前先查看是否有缓存,此时进入事务缓存管理器TransactionalCacheManager,主要是事务缓存TransactionalCache
的装饰器,调用事务缓存里的获取缓存以及存入缓存操作,事务缓存是二级缓存FifoCache(本节二级缓存实现方式是先进先出的队列缓存)的装饰器,也就是说这里最终会调用二级缓存的存储获取缓存操作,事务缓存生命周期是在提交回滚将事务缓存中的变量数据缓存清空,放入到FifoCache缓存中。
2.uml类图
看类图UML就可以知道整个过程,
1.构建时由XMLConfigBuilder开始解析然后放入到Configration中,然后进入到XMLMapperBuilder解析图二的cache标签,然后进入MapperBuilderAssistant去存储相关内容,处理完后放入Configuration中和MappedStatement中。
2.执行时操作,由SqlSession开始进入到Executor中,首先进入第一个装饰器,CachingExecutor,然后进入事务缓存管理器TransactionalCacheManager,依赖事务缓存TransactionalCache,事务缓存又是二级缓存FifoCache的包装器,二级缓存又是一级缓存的PerpetualCache包装器,最终调度到最底层返回。大致就是UML的这个类图的过程
3.代码
3.1 全局缓存解析
3.1.1 XMLConfigBuilder
XMLConfigBuilder中添加解析全局缓存操作,解析setting标签的name和value,解析完毕放入configuration中的cacheEnabled变量里。
/**
* 解析配置在 XML 文件中的缓存机制。并把解析出来的内容存放到 Configuration 配置项中。
* <settings>
* <!--全局缓存:true/false -->
* <setting name="cacheEnabled" value="false"/>
* <!--缓存级别:SESSION/STATEMENT-->
* <setting name="localCacheScope" value="SESSION"/>
* </settings>
*/
private void settingsElement(Element context) {
if (context == null) return;
List<Element> elements = context.elements();
Properties props = new Properties();
for (Element element : elements) {
props.setProperty(element.attributeValue("name"), element.attributeValue("value"));
}
// 设置全局缓存 step-18加
configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
// 设置缓存级别
configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope")));
}
Configuration中的更改就比较简单了,主要是将是否是全局缓存以及将当前的缓存放入到Map中的操作处理,Configuration构造方法中则往类型处理器中注册一级缓存(PerpetualCache)和二级缓存(FiFoCache,本节二级缓存暂时实现先进先出)
public class Configuration {
// 省略其他
// 缓存,存在Map里
protected final Map<String, Cache> caches = new HashMap<>();
// 默认启用缓存,cacheEnabled = true/false
protected boolean cacheEnabled = true;
public Configuration() {
// 省略其他
// step-18-添加
typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
}
public boolean isCacheEnabled() {
return cacheEnabled;
}
public void setCacheEnabled(boolean cacheEnabled) {
this.cacheEnabled = cacheEnabled;
}
public void addCache(Cache cache) {
caches.put(cache.getId(), cache);
}
public Cache getCache(String id) {
return caches.get(id);
}
}
3.1.2 XMLMapperBuilder
XMLMapperBuilder类:
因为二级缓存是Mapper级别的,所以会有一些配置操作放入到mapper里需要去解析,如
<cache eviction="FIFO" flushInterval="600000" size="512" readOnly="true"/>标签,所以在cacheElement方法主要是处理解析这个标签里属性信息操作的,eviction=fifo,代表这个二级缓存是使用先入先出操作,
public class XMLMapperBuilder extends BaseBuilder {
// 省略其他。。。
private void configurationElement(Element element) {
// 省略其他。。。
// 2. 配置cache
cacheElement(element.element("cache"));
}
// 新添加的方法
/**
* <cache eviction="FIFO" flushInterval="600000" size="512" readOnly="true"/>
*/
private void cacheElement(Element context) {
if (context == null) return;
// 基础配置信息
String type = context.attributeValue("type", "PERPETUAL");
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
// 缓存队列FIFO
String eviction = context.attributeValue("eviction", "FIFO");
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
Long flushInterval = Long.valueOf(context.attributeValue("flushInterval"));
Integer size = Integer.valueOf(context.attributeValue("size"));
boolean readWrite = !Boolean.parseBoolean(context.attributeValue("readOnly", "false"));
boolean blocking = !Boolean.parseBoolean(context.attributeValue("blocking", "false"));
// 解析额外属性信息;<property name="cacheFile" value="/tmp/xxx-cache.tmp"/>
List<Element> elements = context.elements();
Properties props = new Properties();
for (Element element : elements) {
props.setProperty(element.attributeValue("name"), element.attributeValue("value"));
}
// 构建缓存
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWri