MyBatis 提供了一、二级缓存,其中一级缓存是 SqlSession 级别的,默认为开启状态。二级缓存配置在映射文件中,使用者需要显示配置才能开启。如果无特殊要求,二级缓存的配置很简单。如下
修改缓存的一些属性,可以像下面这样配置
1 2 3 4 5
| <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
|
根据上面的配置创建出的缓存有以下特点:
- 按先进先出的策略淘汰缓存项
- 缓存的容量为 512 个对象引用
- 缓存每隔 60 秒刷新一次
- 缓存返回的对象是写安全的,即在外部��改对象不会影响到缓存内部存储对象
除了上面两种配置方式,我们还可以给 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; }
|
上面的构建过程流程较为复杂,这里总结一下。如下:
- 设置默认的缓存类型及装饰器
- 应用装饰器到 PerpetualCache 对象上
- 应用标准装饰器
- 对非 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); }
|