水煮MyBatis(十八)- 自定义缓存实现

266 阅读2分钟

前言

到此为止,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接口时新建缓存对象

image.png

使用案例

image.png

自定义驱除策略

驱除类也比较极端,在成功取用一次以后,就删除对应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> {
 // ...
}

使用案例

image.png

自定义缓存的局限性

上面这两个自定义的类SingleCacheOnceCache,可以一起使用吗?其实是不能一起出现的,如果这么写的话,只有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类时,才会生效。