MyBatis 提供了一、二级缓存,其中一级缓存是 SqlSession 级别的,默认为开启状态。二级缓存配置在映射文件中,使用者需要显示配置才能开启。如果无特殊要求,二级缓存的配置很简单。如下

1
<cache/>

修改缓存的一些属性,可以像下面这样配置

1
2
3
4
5
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>

根据上面的配置创建出的缓存有以下特点:

  1. 按先进先出的策略淘汰缓存项
  2. 缓存的容量为 512 个对象引用
  3. 缓存每隔 60 秒刷新一次
  4. 缓存返回的对象是写安全的,即在外部��改对象不会影响到缓存内部存储对象

除了上面两种配置方式,我们还可以给 MyBatis 配置第三方缓存或者自己实现的缓存等。比如,我们将 Ehcache 缓存整合到 MyBatis 中,可以这样配置。

1
2
3
4
5
6
7
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
<property name="timeToIdleSeconds" value="3600"/>
<property name="timeToLiveSeconds" value="3600"/>
<property name="maxEntriesLocalHeap" value="1000"/>
<property name="maxEntriesLocalDisk" value="10000000"/>
<property name="memoryStoreEvictionPolicy" value="LRU"/>
</cache>

缓存配置的解析逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void cacheElement(XNode context) throws Exception {
if (context != null) {
// 获取各种属性
String type = context.getStringAttribute("type", "PERPETUAL");
Class<? extends Cache> typeClass =
typeAliasRegistry.resolveAlias(type);
String eviction = context.getStringAttribute("eviction", "LRU");
Class<? extends Cache> 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);
}
}

缓存对象的构建逻辑封装在 BuilderAssistant 类的 useNewCache 方法中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// -☆- MapperBuilderAssistant
public Cache useNewCache(Class<? extends Cache> typeClass,
Class<? extends Cache> evictionClass,Long flushInterval,
Integer size,boolean readWrite,boolean blocking,Properties props) {
// 使用建造模式构建缓存实例
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();

// 添加缓存到 Configuration 对象中
configuration.addCache(cache);
// 设置 currentCache 遍历,即当前使用的缓存
currentCache = cache;

return cache;
}

建造模式构建 Cache 实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// -☆- CacheBuilder
public Cache build() {
// 设置默认的缓存类型(PerpetualCache)和缓存装饰器(LruCache)
setDefaultImplementations();
// 通过反射创建缓存
Cache cache = newBaseCacheInstance(implementation, id);
setCacheProperties(cache);
// 仅对内置缓存 PerpetualCache 应用装饰器
if (PerpetualCache.class.equals(cache.getClass())) {
// 遍历装饰器集合,应用装饰器
for (Class<? extends Cache> decorator : decorators) {
// 通过反射创建装饰器实例
cache = newCacheDecoratorInstance(decorator, cache);
// 设置属性值到缓存实例中
setCacheProperties(cache);
}
// 应用标准的装饰器,比如 LoggingCache、SynchronizedCache
cache = setStandardDecorators(cache);
} else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
// 应用具有日志功能的缓存装饰器
cache = new LoggingCache(cache);
}
return cache;
}

上面的构建过程流程较为复杂,这里总结一下。如下:

  1. 设置默认的缓存类型及装饰器
  2. 应用装饰器到 PerpetualCache 对象上
  3. 应用标准装饰器
  4. 对非 LoggingCache 类型的缓存应用 LoggingCache 装饰器

setDefaultImplementations

1
2
3
4
5
6
7
8
9
10
private void setDefaultImplementations() {
if (implementation == null) {
// 设置默认的缓存实现类
implementation = PerpetualCache.class;
if (decorators.isEmpty()) {
// 添加 LruCache 装饰器
decorators.add(LruCache.class);
}
}
}

主要做的事情是在 implementation 为空的情况下,为它设置一个默认值。

仔细看前面的方法,会发现 MyBatis 做了不少判空的操作

1
2
3
4
5
6
7
8
9
10
11
12
// 判空操作 1,若用户未设置 cache 节点的 type 和 eviction 属性,
// 这里设置默认值 PERPETUAL
String type = context.getStringAttribute("type", "PERPETUAL");
String eviction = context.getStringAttribute("eviction", "LRU");

// 判空操作 2,若 typeClass 或 evictionClass 为空,
// valueOrDefault 方法会为它们设置默认值
Cache cache = new CacheBuilder(currentNamespace)
.implementation(valueOrDefault(typeClass, PerpetualCache.class))
.addDecorator(valueOrDefault(evictionClass, LruCache.class))
// 省略部分代码
.build();

既然前面已经做了两次判空操作,implementation不可能为空,那么setDefaultImplementations方法似乎没有存在的必要了。其实不然,如果有人不按套路写代码。比如:

1
2
3
Cache cache = new CacheBuilder(currentNamespace)
// 忘记设置 implementation
.build();

这里忘记设置 implementation ,或人为的将 implementation 设为空。如果不对implementation 进行判空,会导致build方法在构建实例时触发空指针异常,对于框架来说,出现空指针异常是很尴尬的,这是一个低级错误。这里以及之前做了这么多判空,就是为了避免出现空指针的情况,以提高框架的健壮性。

我们在使用 MyBatis 内置缓存时,一般不用为它们配置自定义属性。但使用第三方缓存时,则应按需进行配置。来看一下这部分配置是如何设置到缓存实例中的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
private void setCacheProperties(Cache cache) {
if (properties != null) {
// 为缓存实例生成一个“元信息”实例,forObject 方法调用层次比较深,
// 但最终调用了 MetaClass 的 forClass 方法
MetaObject metaCache = SystemMetaObject.forObject(cache);
//用反射设置额外的property属性
for (Map.Entry<Object, Object> entry : properties.entrySet()) {
String name = (String) entry.getKey();
String value = (String) entry.getValue();
if (metaCache.hasSetter(name)) {
// 获取 setter 方法的参数类型
Class<?> type = metaCache.getSetterType(name);
// 根据参数类型对属性值进行转换,并将转换后的值
// 通过 setter 方法设置到 Cache
if (String.class == type) {
metaCache.setValue(name, value);
} else if (int.class == type
|| Integer.class == type) {
/*
* 此处及以下分支包含两个步骤:
* 1.类型转换 → Integer.valueOf(value)
* 2.将转换后的值设置到缓存实例中 →
* metaCache.setValue(name, value)
*/
metaCache.setValue(name, Integer.valueOf(value));
} else if (long.class == type
|| Long.class == type) {
metaCache.setValue(name, Long.valueOf(value));
} else if (short.class == type
|| Short.class == type) {
metaCache.setValue(name, Short.valueOf(value));
} else if (byte.class == type
|| Byte.class == type) {
metaCache.setValue(name, Byte.valueOf(value));
} else if (float.class == type
|| Float.class == type) {
metaCache.setValue(name, Float.valueOf(value));
} else if (boolean.class == type
|| Boolean.class == type) {
metaCache.setValue(name, Boolean.valueOf(value));
} else if (double.class == type
|| Double.class == type) {
metaCache.setValue(name, Double.valueOf(value));
} else {
throw new CacheException("Unsupported property type for cache: '" + name + "' of type " + type);
}
}
}
}
}

大段代码用于对属性值进行类型转换,和设置转换后的值到Cache实例中。

设置标准装饰器的过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private Cache setStandardDecorators(Cache cache) {
try {
// 创建“元信息”对象
MetaObject metaCache = SystemMetaObject.forObject(cache);
if (size != null && metaCache.hasSetter("size")) {
// 设置 size 属性,
metaCache.setValue("size", size);
}
if (clearInterval != null) {
// clearInterval 不为空,应用 ScheduledCache 装饰器
cache = new ScheduledCache(cache);
((ScheduledCache) cache).setClearInterval(clearInterval);
}
if (readWrite) {
// readWrite 为 true,应用 SerializedCache 装饰器
cache = new SerializedCache(cache);
}
/*
* 应用 LoggingCache,SynchronizedCache 装饰器,
* 使原缓存具备打印日志和线程同步的能力
*/
cache = new LoggingCache(cache);
cache = new SynchronizedCache(cache);
if (blocking) {
// blocking 为 true,应用 BlockingCache 装饰器
cache = new BlockingCache(cache);
}
return cache;
} catch (Exception e) {
throw new CacheException("……");
}
}

以上代码用于为缓存应用一些基本的装饰器,除了 LoggingCache 和 SynchronizedCache这两个是必要的装饰器,其他的装饰器应用与否,取决于用户的配置。

其余的一些额外操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// issue #352, do not apply decorators to custom caches
if (PerpetualCache.class.equals(cache.getClass())) {
for (Class<? extends Cache> decorator : decorators) {
//装饰者模式一个个包装cache
cache = newCacheDecoratorInstance(decorator, cache);
//又要来一遍设额外属性
setCacheProperties(cache);
}
//最后附加上标准的装饰者
cache = setStandardDecorators(cache);
} else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
//如果是custom缓存,且不是日志,要加日志
cache = new LoggingCache(cache);
}