MyBatis的二级缓存
MyBatis的缓存分为一级缓存和二级缓存,一级缓存是 SqlSession 级别的缓存,二级缓存是mapper级别的缓存。但是这篇博客主要是介绍mybaits中缓存接口和缓存键接口,以及一些缓存实现。
之前写过一篇博客简单介绍了一下Hibernate的两级缓存。
链接: https://blog.csdn.net/Let_me_tell_you/article/details/80876767
Cache接口
源码位置:org.apache.ibatis.cache.Cache
缓存容器接口,自定义操作方法,其他缓存实现类需要实现这个接口。
UML类图
上图中列出来的就是Cache接口的实现类,实现不同的缓存功能。
接口的源码很简单,就是定义了一些增删改查缓存的方法,和一个获取容器中缓存数量和获得读写锁的方法。我精简了下注释,源码内容如下。Cache接口其实是一个缓存容器,有点类似于一个HashMap(有一些实现类就是使用HashMap来保存操作缓存数据的)。
public interface Cache {
/**
* 获取标识
*/
String getId();
/**
* 添加指定的键
*/
void putObject(Object key, Object value);
/**
* 获取指定的键的值
*/
Object getObject(Object key);
/**
* 删除指定的键的值
*/
Object removeObject(Object key);
/**
* 清空缓存
*/
void clear();
/**
* 获取容器中缓存的数量
*/
int getSize();
/**
* 获得读写锁
*/
default ReadWriteLock getReadWriteLock() {
return null;
}
}
PerpetualCache
源码位置:org.apache.ibatis.cache.impl.PerpetualCache
永不过期的缓存,使用HashMap来保存和操作数据,重写了 equals()
和 hashCode()
方法,其他缓存操作都是直接调用的HashMap方法。
LoggingCache
源码位置:org.apache.ibatis.cache.decorators.LoggingCache
这是一个支持打印日志的 Cache 实现,代码也很简单,加了一些注释。
public class LoggingCache implements Cache {
/**
* mybaits log 对象
*/
private final Log log;
/**
* 装饰的 Cache 对象
*/
private final Cache delegate;
/**
* 统计请求缓存的次数
*/
protected int requests = 0;
/**
* 命中缓存的次数
*/
protected int hits = 0;
public LoggingCache(Cache delegate) {
this.delegate = delegate;
this.log = LogFactory.getLog(getId());
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
return delegate.getSize();
}
@Override
public void putObject(Object key, Object object) {
delegate.putObject(key, object);
}
@Override
public Object getObject(Object key) {
//请求次数加 1
requests++;
final Object value = delegate.getObject(key);
if (value != null) {
//命中缓存,命中次数加 1
hits++;
}
if (log.isDebugEnabled()) {
//打印该缓存命中次数
log.debug("Cache Hit Ratio [" + getId() + "]: " + getHitRatio());
}
return value;
}
@Override
public Object removeObject(Object key) {
return delegate.removeObject(key);
}
@Override
public void clear() {
delegate.clear();
}
@Override
public int hashCode() {
return delegate.hashCode();
}
@Override
public boolean equals(Object obj) {
return delegate.equals(obj);
}
/**
* 计算命中比例
* @return
*/
private double getHitRatio() {
//算法: 命中次数 / 请求缓存次数
return (double) hits / (double) requests;
}
}
BlockingCache
源码位置:org.apache.ibatis.cache.decorators.BlockingCache
阻塞的Cache实现类,这个实现不同的逻辑是在加锁上。当一个线程去获取缓存事,缓存不存在则会阻塞后续线程获取,当前线程则去添加缓存值,避免后续线程重复添加缓存。需要注意的是这个实现里 removeObject
方法并不是删除缓存值,而是移除锁。
public class BlockingCache implements Cache {
/**
* 阻塞等待超时时间
*/
private long timeout;
/**
* 装饰的Cache对象
*/
private final Cache delegate;
/**
* 缓存键 与 ReentrantLock 对象映射
*/
private final ConcurrentHashMap<Object, ReentrantLock> locks;
public BlockingCache(Cache delegate) {
this.delegate = delegate;
this.locks = new ConcurrentHashMap<>();
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
return delegate.getSize();
}
@Override
public void putObject(Object key, Object value) {
try {
//添加缓存
delegate.putObject(key, value);
} finally {
//释放锁
releaseLock(key);
}
}
@Override
public Object getObject(Object key) {
//获得锁
acquireLock(key);
//获得缓存执
Object value = delegate.getObject(key);
if (value != null) {
//释放锁
releaseLock(key);
}
return value;
}
@Override
public Object removeObject(Object key) {
// despite of its name, this method is called only to release locks
//释放该键对应的锁
releaseLock(key);
return null;
}
@Override
public void clear() {
delegate.clear();
}
/**
* 获得 ReentrantLock 对象,如果不存在,则进行添加
* @param key
* @return
*/
private ReentrantLock getLockForKey(Object key) {
return locks.computeIfAbsent(key, k -> new ReentrantLock());
}
private void acquireLock(Object key) {
//获得键对应的 ReentrantLock 对象
Lock lock = getLockForKey(key);
//获得锁,直到超时
if (timeout > 0) {
try {
boolean acquired = lock.tryLock(timeout, TimeUnit.MILLISECONDS);
if (!acquired) {
throw new CacheException("Couldn't get a lock in " + timeout + " for the key " + key + " at the cache " + delegate.getId());
}
} catch (InterruptedException e) {
throw new CacheException("Got interrupted while trying to acquire lock for key " + key, e);
}
} else {
//释放锁
lock.lock();
}
}
private void releaseLock(Object key) {
//获得 ReentrantLock 对象
ReentrantLock lock = locks.get(key);
if (lock.isHeldByCurrentThread()) {
//如果当前线程持有锁,进行释放
lock.unlock();
}
}
public long getTimeout() {
return timeout;
}
public void setTimeout(long timeout) {
this.timeout = timeout;
}
}
SynchronizedCache
源码位置:org.apache.ibatis.cache.decorators.SynchronizedCache
同步Cache实现,内部也是使用装饰的Cache来实现缓存操作。不过这个实现在 getSize
putObject
getObject
removeObject
clear
这几个方法上添加了 synchronized
关键字。
SerializedCache
源码位置:org.apache.ibatis.cache.decorators.SerializedCache
支持序列化值,其实就是在添加缓存的时候对值进行序列化,获取值的时候反序列化。
public class SerializedCache implements Cache {
//装饰的 Cache
private final Cache delegate;
public SerializedCache(Cache delegate) {
this.delegate = delegate;
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
return delegate.getSize();
}
@Override
public void putObject(Object key, Object object) {
if (object == null || object instanceof Serializable) {
//存值进行序列化
delegate.putObject(key, serialize((Serializable) object));
} else {
throw new CacheException("SharedCache failed to make a copy of a non-serializable object: " + object);
}
}
@Override
public Object getObject(Object key) {
Object object = delegate.getObject(key);
//取值进行反序列化
return object == null ? null : deserialize((byte[]) object);
}
@Override
public Object removeObject(Object key) {
return delegate.removeObject(key);
}
@Override
public void clear() {
delegate.clear();
}
@Override
public int hashCode() {
return delegate.hashCode();
}
@Override
public boolean equals(Object obj) {
return delegate.equals(obj);
}
private byte[] serialize(Serializable value) {
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos)) {
oos.writeObject(value);
oos.flush();
return bos.toByteArray();
} catch (Exception e) {
throw new CacheException("Error serializing object. Cause: " + e, e);
}
}
private Serializable deserialize(byte[] value) {
Serializable result;
try (ByteArrayInputStream bis = new ByteArrayInputStream(value);
ObjectInputStream ois = new CustomObjectInputStream(bis)) {
result = (Serializable) ois.readObject();
} catch (Exception e) {
throw new CacheException("Error deserializing object. Cause: " + e, e);
}
return result;
}
public static class CustomObjectInputStream extends ObjectInputStream {
public CustomObjectInputStream(InputStream in) throws IOException {
super(in);
}
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws ClassNotFoundException {
return Resources.classForName(desc.getName());
}
}
}
ScheduledCache
源码位置:org.apache.ibatis.cache.decorators.ScheduledCache
定时清空整个 Cache 的缓存,在每次操作缓存之前判断是否全部清空缓存
public class ScheduledCache implements Cache {
/**
* 装饰的 Cache 对象
*/
private final Cache delegate;
/**
* 清空间隔,单位:毫秒
*/
protected long clearInterval;
/**
* 最后清空时间,单位:毫秒
*/
protected long lastClear;
public ScheduledCache(Cache delegate) {
this.delegate = delegate;
//默认清空间隔 一小时
this.clearInterval = 60 * 60 * 1000; // 1 hour
this.lastClear = System.currentTimeMillis();
}
public void setClearInterval(long clearInterval) {
this.clearInterval = clearInterval;
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
//判断是否需要全部清空
clearWhenStale();
return delegate.getSize();
}
@Override
public void putObject(Object key, Object object) {
//判断是否需要全部清空
clearWhenStale();
delegate.putObject(key, object);
}
@Override
public Object getObject(Object key) {
//判断是否需要全部清空
return clearWhenStale() ? null : delegate.getObject(key);
}
@Override
public Object removeObject(Object key) {
//判断是否需要全部清空
clearWhenStale();
return delegate.removeObject(key);
}
@Override
public void clear() {
//记录清空时间
lastClear = System.currentTimeMillis();
delegate.clear();
}
@Override
public int hashCode() {
return delegate.hashCode();
}
@Override
public boolean equals(Object obj) {
return delegate.equals(obj);
}
/**
* 判断是否需要全部清空
* @return
*/
private boolean clearWhenStale() {
if (System.currentTimeMillis() - lastClear > clearInterval) {
//全部清空
clear();
return true;
}
return false;
}
}
FifoCache
源码位置:org.apache.ibatis.cache.decorators.FifoCache
基于先进先出淘汰机制的Cache实现,此实现在删除缓存时并不会删除缓存key,所以旧的 key 也依旧会继续存在。
除此之外在添加key的时候该实现也不会去判断key是否已经存在,只会判断当前长度是否超过了队列上上限,所以重复添加就会在队列里存在多个相同的key,这个不能说是bug,只能说是允许重复的key,但是如果你在使用中发现同一个key拿到的缓存不止一个,可能就需要检查一下你使用的是不是 FifoCache
实现了。
public class FifoCache implements Cache {
/**
* 装饰的 Cache 对象
*/
private final Cache delegate;
/**
* 双端队列,记录键值的添加
*/
private final Deque<Object> keyList;
/**
* 队列上限
*/
private int size;
public FifoCache(Cache delegate) {
this.delegate = delegate;
this.keyList = new LinkedList<>();
this.size = 1024;
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
return delegate.getSize();
}
public void setSize(int size) {
this.size = size;
}
@Override
public void putObject(Object key, Object value) {
//循环 keyList
cycleKeyList(key);
delegate.putObject(key, value);
}
@Override
public Object getObject(Object key) {
return delegate.getObject(key);
}
@Override
public Object removeObject(Object key) {
//删除缓存,未清空keyList中的数据
return delegate.removeObject(key);
}
@Override
public void clear() {
//清空缓存时,同时清空维护的 keyList
delegate.clear();
keyList.clear();
}
private void cycleKeyList(Object key) {
//添加到 keyList
keyList.addLast(key);
if (keyList.size() > size) {
//如果添加新 key 之后 ketList 的长度大于 队列上线,将队列的首位移除
Object oldestKey = keyList.removeFirst();
delegate.removeObject(oldestKey);
}
}
}
LruCache
源码位置:org.apache.ibatis.cache.decorators.LruCache
此实现是基于最少使用的淘汰机制的 Cache 实现,简单说就是当添加缓存时发现已经达到上限的时候,淘汰掉最少使用的 key 以及对应的缓存。使用 LinkedHashMap 的淘汰机制,具体源码的解析请参考下面的源码。
public class LruCache implements Cache {
/**
* 装饰的 Cache 对象
*/
private final Cache delegate;
/**
* 基于 LinkedHashMap 实现淘汰机制
*/
private Map<Object, Object> keyMap;
/**
* 最老/最少被使用的键,即将被淘汰的 key
*/
private Object eldestKey;
public LruCache(Cache delegate) {
this.delegate = delegate;
//初始化 keyMap 对象
setSize(1024);
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
return delegate.getSize();
}
/**
* 初始化keyMap,但是此方法的权限标识是 public ,也就是说可以通过这个方法来指定 keyMap的长度,默认是1024,可以根据自己的需求来变更
* @param size
*/
public void setSize(final int size) {
//LinkedHasmp的一个构造函数,当参数 accessOrder 为 true 时,即会按照访问的顺序排序,最近访问的在最前,最早访问的在最后
keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
private static final long serialVersionUID = 4267176411845948333L;
@Override
protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
//重写 LinkedHashMapLinkedHashMap 的删除元素方法,当满足一定条件的时候删除元素,LinkedHashMap中默认是不删除
//这里判断的条件就是 keyMap 长度大于初始化 keyMap 时给定的值,满足条件时将最少使用的 key 设置为待删除,
// 等下次添加新key的时候判断 eldestKey 参数不为空则对 eldestKey 进行移除
boolean tooBig = size() > size;
if (tooBig) {
eldestKey = eldest.getKey();
}
return tooBig;
}
};
}
@Override
public void putObject(Object key, Object value) {
delegate.putObject(key, value);
cycleKeyList(key);
}
@Override
public Object getObject(Object key) {
keyMap.get(key); //touch
return delegate.getObject(key);
}
@Override
public Object removeObject(Object key) {
return delegate.removeObject(key);
}
@Override
public void clear() {
delegate.clear();
keyMap.clear();
}
private void cycleKeyList(Object key) {
//添加 key 到keyMap中
keyMap.put(key, key);
//如果超过上限,则删除最少使用的可以
if (eldestKey != null) {
//移除 eldestKey
delegate.removeObject(eldestKey);
//置空
eldestKey = null;
}
}
}
WeakCache
源码位置:org.apache.ibatis.cache.decorators.WeakCache
基于 java.lang.ref.WeakReference
的Cache实现类,主要是基于内部维护的一个强引用和mybatis基于 java.lang.ref.WeakReference
扩展的WeakEntry来实现缓存淘汰。在这个实现里,一样存在key多次添加会导致重复key的问题。
public class WeakCache implements Cache {
//强引用的键的队列
private final Deque<Object> hardLinksToAvoidGarbageCollection;
//被GC回收的 WeakEntry 集合,避免被 GC
private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;
//装饰的 Cache 对象
private final Cache delegate;
//hardLinksToAvoidGarbageCollection 的大小
private int numberOfHardLinks;
public WeakCache(Cache delegate) {
this.delegate = delegate;
this.numberOfHardLinks = 256;
this.hardLinksToAvoidGarbageCollection = new LinkedList<>();
this.queueOfGarbageCollectedEntries = new ReferenceQueue<>();
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
//移除已经被 GC 回收的WeakEntry
removeGarbageCollectedItems();
return delegate.getSize();
}
public void setSize(int size) {
this.numberOfHardLinks = size;
}
@Override
public void putObject(Object key, Object value) {
//移除已经被 GC 回收的 WeakEntry
removeGarbageCollectedItems();
//添加缓存
delegate.putObject(key, new WeakEntry(key, value, queueOfGarbageCollectedEntries));
}
@Override
public Object getObject(Object key) {
Object result = null;
@SuppressWarnings("unchecked") // assumed delegate cache is totally managed by this cache
//获得值的 WeakReference 对象
WeakReference<Object> weakReference = (WeakReference<Object>) delegate.getObject(key);
if (weakReference != null) {
//获得值
result = weakReference.get();
if (result == null) {
//值为空,表示已经被GC回收,移除缓存
delegate.removeObject(key);
} else {
//非空,添加到 hardLinksToAvoidGarbageCollection 队列首部,未做key唯一性判断,所以存在重复添加的情况 ,避免被 GC
hardLinksToAvoidGarbageCollection.addFirst(result);
if (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) {
//如果长度超出上限,则移除队列尾部的元素
hardLinksToAvoidGarbageCollection.removeLast();
}
}
}
return result;
}
@Override
public Object removeObject(Object key) {
//移除已经被 GC 回收的 WeakEntry
removeGarbageCollectedItems();
//移除缓存
return delegate.removeObject(key);
}
@Override
public void clear() {
//清空 hardLinksToAvoidGarbageCollection
hardLinksToAvoidGarbageCollection.clear();
//移除已经被 GC 回收的 WeakEntry
removeGarbageCollectedItems();
//清空缓存
delegate.clear();
}
/**
* 移除已经被 GC 回收的键
*/
private void removeGarbageCollectedItems() {
WeakEntry sv;
while ((sv = (WeakEntry) queueOfGarbageCollectedEntries.poll()) != null) {
delegate.removeObject(sv.key);
}
}
/**
* 继承自 WeakReference ,增加缓存key属性,
*/
private static class WeakEntry extends WeakReference<Object> {
//键
private final Object key;
private WeakEntry(Object key, Object value, ReferenceQueue<Object> garbageCollectionQueue) {
super(value, garbageCollectionQueue);
this.key = key;
}
}
}
SoftCache
源码位置:org.apache.ibatis.cache.decorators.SoftCache
基于java.lang.ref.SoftReference
的Cache实现,SoftCache内部实现了SoftEntry,其他基本上与WeakCache基本上是差不多的,只是在一些操作中会对 hardLinksToAvoidGarbageCollection 加锁。
public class WeakCache implements Cache {
//强引用的键的队列
private final Deque<Object> hardLinksToAvoidGarbageCollection;
//被GC回收的 WeakEntry 集合,避免被 GC
private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;
//装饰的 Cache 对象
private final Cache delegate;
//hardLinksToAvoidGarbageCollection 的大小
private int numberOfHardLinks;
public WeakCache(Cache delegate) {
this.delegate = delegate;
this.numberOfHardLinks = 256;
this.hardLinksToAvoidGarbageCollection = new LinkedList<>();
this.queueOfGarbageCollectedEntries = new ReferenceQueue<>();
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
//移除已经被 GC 回收的WeakEntry
removeGarbageCollectedItems();
return delegate.getSize();
}
public void setSize(int size) {
this.numberOfHardLinks = size;
}
@Override
public void putObject(Object key, Object value) {
//移除已经被 GC 回收的 WeakEntry
removeGarbageCollectedItems();
//添加缓存
delegate.putObject(key, new WeakEntry(key, value, queueOfGarbageCollectedEntries));
}
@Override
public Object getObject(Object key) {
Object result = null;
@SuppressWarnings("unchecked") // assumed delegate cache is totally managed by this cache
//获得值的 WeakReference 对象
WeakReference<Object> weakReference = (WeakReference<Object>) delegate.getObject(key);
if (weakReference != null) {
//获得值
result = weakReference.get();
if (result == null) {
//值为空,表示已经被GC回收,移除缓存
delegate.removeObject(key);
} else {
//非空,添加到 hardLinksToAvoidGarbageCollection 队列首部,未做key唯一性判断,所以存在重复添加的情况 ,避免被 GC
hardLinksToAvoidGarbageCollection.addFirst(result);
if (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) {
//如果长度超出上限,则移除队列尾部的元素
hardLinksToAvoidGarbageCollection.removeLast();
}
}
}
return result;
}
@Override
public Object removeObject(Object key) {
//移除已经被 GC 回收的 WeakEntry
removeGarbageCollectedItems();
//移除缓存
return delegate.removeObject(key);
}
@Override
public void clear() {
//清空 hardLinksToAvoidGarbageCollection
hardLinksToAvoidGarbageCollection.clear();
//移除已经被 GC 回收的 WeakEntry
removeGarbageCollectedItems();
//清空缓存
delegate.clear();
}
/**
* 移除已经被 GC 回收的键
*/
private void removeGarbageCollectedItems() {
WeakEntry sv;
while ((sv = (WeakEntry) queueOfGarbageCollectedEntries.poll()) != null) {
delegate.removeObject(sv.key);
}
}
/**
* 继承自 WeakReference ,增加缓存key属性,
*/
private static class WeakEntry extends WeakReference<Object> {
//键
private final Object key;
private WeakEntry(Object key, Object value, ReferenceQueue<Object> garbageCollectionQueue) {
super(value, garbageCollectionQueue);
this.key = key;
}
}
}
CachKey
源码位置:org.apache.ibatis.cache.CacheKey
mybaits中的缓存键,不只是单纯的String字符串,而是由多个对象组成,共同计算缓存键。CacheKey中封装了多个影响缓存的属性。
public class CacheKey implements Cloneable, Serializable {
private static final long serialVersionUID = 1146682552656046210L;
//单例 空缓存键
public static final CacheKey NULL_CACHE_KEY = new NullCacheKey();
//multiplier 的值
private static final int DEFAULT_MULTIPLYER = 37;
//hashcode 的值
private static final int DEFAULT_HASHCODE = 17;
//hashcode求值的系数
private final int multiplier;
//缓存键的hashcode
private int hashcode;
//校验和
private long checksum;
//updateList 的长度
private int count;
// 8/21/2017 - Sonarlint flags this as needing to be marked transient. While true if content is not serializable, this is not always true and thus should not be marked transient.
//计算 hashcode 的对象的集合
private List<Object> updateList;
public CacheKey() {
this.hashcode = DEFAULT_HASHCODE;
this.multiplier = DEFAULT_MULTIPLYER;
this.count = 0;
this.updateList = new ArrayList<>();
}
public CacheKey(Object[] objects) {
this();
//基于 objects ,更新相关属性
updateAll(objects);
}
public int getUpdateCount() {
return updateList.size();
}
public void update(Object object) {
//方法参数 object 的 hashcode
int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);
count++;
//checksum 为 baseHashCode 求和
checksum += baseHashCode;
//计算hashcode值
baseHashCode *= count;
hashcode = multiplier * hashcode + baseHashCode;
//添加 object(缓存key) 到updateList中
updateList.add(object);
}
public void updateAll(Object[] objects) {
//遍历 objects 数组,调用 update 方法,更新相关属性
for (Object o : objects) {
update(o);
}
}
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (!(object instanceof CacheKey)) {
return false;
}
final CacheKey cacheKey = (CacheKey) object;
if (hashcode != cacheKey.hashcode) {
return false;
}
if (checksum != cacheKey.checksum) {
return false;
}
if (count != cacheKey.count) {
return false;
}
for (int i = 0; i < updateList.size(); i++) {
Object thisObject = updateList.get(i);
Object thatObject = cacheKey.updateList.get(i);
if (!ArrayUtil.equals(thisObject, thatObject)) {
return false;
}
}
return true;
}
@Override
public int hashCode() {
return hashcode;
}
@Override
public String toString() {
StringJoiner returnValue = new StringJoiner(":");
returnValue.add(String.valueOf(hashcode));
returnValue.add(String.valueOf(checksum));
updateList.stream().map(ArrayUtil::toString).forEach(returnValue::add);
return returnValue.toString();
}
@Override
public CacheKey clone() throws CloneNotSupportedException {
//克隆 CacheKey 对象
CacheKey clonedCacheKey = (CacheKey) super.clone();
//创建 updateList 数组,避免原数组修改
clonedCacheKey.updateList = new ArrayList<>(updateList);
return clonedCacheKey;
}
}
NullCacheKey
源码位置:org.apache.ibatis.cache.NullCacheKey
继承自 CacheKey ,空缓存键。
public final class NullCacheKey extends CacheKey {
private static final long serialVersionUID = 3704229911977019465L;
public NullCacheKey() {
super();
}
@Override
public void update(Object object) {
throw new CacheException("Not allowed to update a NullCacheKey instance.");
}
@Override
public void updateAll(Object[] objects) {
throw new CacheException("Not allowed to update a NullCacheKey instance.");
}
}public final class NullCacheKey extends CacheKey {
private static final long serialVersionUID = 3704229911977019465L;
public NullCacheKey() {
super();
}
@Override
public void update(Object object) {
throw new CacheException("Not allowed to update a NullCacheKey instance.");
}
@Override
public void updateAll(Object[] objects) {
throw new CacheException("Not allowed to update a NullCacheKey instance.");
}
}
本文由博客一文多发平台 OpenWrite 发布!