前言
到此为止,Mybatis的核心特性基本介绍的差不多了,后面会陆续更新一些扩展相关的技术点。
之前在缓存(下)章节中提到,可以自定义实现缓存接口,用于灵活处理业务需求,下面一起来看看如何实现吧。
一级缓存可以自定义吗?
从源码来看,一级缓存是没法自定义的,直接指定了PerpetualCache。下面是BaseExecutor的初始化代码:
protected BaseExecutor(Configuration configuration, Transaction transaction) {
this.transaction = transaction;
this.deferredLoads = new ConcurrentLinkedQueue<>();
// 本地缓存,指定了PerpetualCache
this.localCache = new PerpetualCache("LocalCache");
this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
this.closed = false;
this.configuration = configuration;
this.wrapper = this;
}
自定义存储类
这里我们实现极端特殊的缓存,只能保存最近的单个缓存,抛开实用性不谈,只是引入自定义的概念。
@Slf4j
public class SingleCache implements Cache {
/**
* 缓存对象id
*/
private final String id;
/**
* 缓存key
*/
private Object key;
/**
* 缓存value
*/
private Object value;
public SingleCache(String id) {
this.id = id;
}
@Override
public String getId() {
return id;
}
@Override
public void putObject(Object key, Object value) {
this.key = key;
this.value = value;
}
@Override
public Object getObject(Object key) {
if (Objects.equals(key, this.key)) {
return value;
}
return null;
}
@Override
public Object removeObject(Object key) {
if (Objects.equals(key, this.key)) {
Object result = value;
this.key = null;
this.value = null;
return result;
} else {
log.info("miss key :{}.", key);
return null;
}
}
@Override
public void clear() {
this.key = null;
this.value = null;
}
@Override
public int getSize() {
return key == null ? 0 : 1;
}
}
引用此缓存
在@CacheNamespace注解里的implementation属性,指定使用上面定义的SingleCache。这是二级缓存的地基,其他炫酷的特性,如LRU,序列化,定时处理等,都是在此基础之上,做的层层代理封装,细节可以看缓存(下)章节。
@Repository
@CacheNamespace(implementation = SingleCache.class)
public interface ImageInfoMapper extends Mapper<ImageInfo> {
// ...
}
如何生效
上文提到,这是二级缓存的地基,所以,前提条件是开启二级缓存,否则不会被调用到。
cache-enabled: true
初始化Mapper接口时新建缓存对象
使用案例
自定义驱除策略
驱除类也比较极端,在成功取用一次以后,就删除对应key的缓存。
@Slf4j
public class OnceCache implements Cache {
private final Cache delegate;
public OnceCache(Cache delegate) {
this.delegate = delegate;
}
@Override
public int getSize() {
return delegate.getSize();
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public void putObject(Object key, Object value) {
delegate.putObject(key, value);
}
@Override
public Object getObject(Object key) {
Object object = delegate.getObject(key);
if (object != null) {
// 成功读一次以后就删除
removeObject(key);
log.info("once remove,key:{},value:{}", key , object);
}
return object;
}
@Override
public Object removeObject(Object key) {
return delegate.removeObject(key);
}
@Override
public void clear() {
delegate.clear();
}
}
引用
在@CacheNamespace注解里的eviction属性,指定使用上面定义的OnceCache。
@Repository
@CacheNamespace(eviction = OnceCache.class)
public interface ImageInfoMapper extends Mapper<ImageInfo> {
// ...
}
使用案例
自定义缓存的局限性
上面这两个自定义的类SingleCache、OnceCache,可以一起使用吗?其实是不能一起出现的,如果这么写的话,只有SingleCache会用到,驱逐策略将会无效。
@CacheNamespace(implementation = SingleCache.class, eviction = OnceCache.class)
为什么呢,看看缓存build的源码就可知晓,注意源码中的注释: // issue #352, do not apply decorators to custom caches
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) {
cache = newCacheDecoratorInstance(decorator, cache);
setCacheProperties(cache);
}
// 加载其余装饰类,只有在implementation指定为PerpetualCache类时,才有效
cache = setStandardDecorators(cache);
} else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
cache = new LoggingCache(cache);
}
return cache;
}
两个自定义的缓存实现类,不能一起使用的问题,这行注解说的很清楚:
do not apply decorators to custom caches。字面意思是:不能讲装饰类用于自定义缓存
也就是说如果implementation是自定义的缓存,那么只有LoggingCache会对其进行装饰,连注解中定义的【eviction】驱逐类都不会被初始化。
其余的装饰类,譬如ScheduleCache、SerializeCache等,以及自定义的驱逐类,都只有在implementation指定为PerpetualCache类时,才会生效。