Java 装饰模式 · MyBatis 缓存模块
本文 Mybatis 版本为 2.2.0
一、概述
MyBatis中的Cache以SPI实现,给需要集成其它Cache或者自定义Cache提供了接口。
Mybatis支持一级缓存 和 二级缓存,一级缓存 默认开启,而 二建缓存 需要进行配置。
Mybatis提供的缓存大大提升了查询效率,而这两级缓存都依赖cache模块。
cache 模块应用了设计模式中的装饰模式,我们可以从包命名 上区分,Cache 的实现类只有位于impl
包下的PerpetualCache
类,其余位于decorators
包下的类均为装饰者类。
接口 Cache
该模块提供了 Cache
接口,Mybatis 在内部定义了很多实现该接口的装饰类,用户也可以通过实现该接口定义自己针对缓存的 策略包装类、辅助包装类。
public interface Cache {
/* 获取缓存元素标识(唯一ID) */
String getId();
/* 将元素以映射方式(key-value)放入缓存(类似Map的put方法) */
void putObject(Object var1, Object var2);
/* 根据key获取对应的value */
Object getObject(Object var1);
/* 移除key指定的元素 */
Object removeObject(Object var1);
/* 清空缓存 */
void clear();
/* 获取大小:缓存中元素数量 */
int getSize();
/* 默认方法:读写锁(可根据需要重写) */
default ReadWriteLock getReadWriteLock() {
return null;
}
}
二、组成
1. 实现类 - PerpetualCache
/* 最终的缓存类:代表所有装饰者所修饰的最终类 */
public class PerpetualCache implements Cache {
//缓存元素标识(唯一ID)
private final String id;
//Mybatis底层缓存由HashMap实现
private final Map<Object, Object> cache = new HashMap();
/* 唯一构造器:设定ID */
public PerpetualCache(String id) {
this.id = id;
}
/* 获取缓存元素标识(唯一ID) */
@Override
public String getId() {
return this.id;
}
/* 获取大小:缓存中元素数量 */
@Override
public int getSize() {
return this.cache.size();
}
/* 将元素以映射方式(key-value)放入缓存(类似Map的put方法) */
@Override
public void putObject(Object key, Object value) {
this.cache.put(key, value);
}
/* 根据key获取对应的value */
@Override
public Object getObject(Object key) {
return this.cache.get(key);
}
/* 移除key指定的元素 */
@Override
public Object removeObject(Object key) {
return this.cache.remove(key);
}
/* 清空缓存 */
@Override
public void clear() {
this.cache.clear();
}
/* 重写equals 和 hashCode方法 */
@Override
public boolean equals(Object o) {
if (this.getId() == null) {
throw new CacheException("Cache instances require an ID.");
} else if (this == o) {
return true;
} else if (!(o instanceof Cache)) {
return false;
}
Cache otherCache = (Cache)o;
return this.getId().equals(otherCache.getId());
}
@Override
public int hashCode() {
if (this.getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
return this.getId().hashCode();
}
}
2. 装饰类
☯ 回收策略装饰
① FifoCache - FIFO策略
FIFO(First Input First Output, 先进先出)回收策略,内部维护了一个基于 LinkedList 实现的队列,并给定队列大小上限(默认为1024),超出限制从队列中获取 Key 并从装饰的 Cache 中移除该元素。
/* FIFO策略实现缓存 */
public class FifoCache implements Cache {
//装饰对象
private final Cache delegate;
//FIFO队列
private final Deque<Object> keyList;
//队列大小上限
private int size;
/* 唯一构造器:初始化 */
public FifoCache(Cache delegate) {
this.delegate = delegate;
this.keyList = new LinkedList(); //LinkedList实现队列功能
this.size = 1024; //默认限定队列大小为1024
}
/* 获取缓存元素标识(唯一ID)*/
public String getId() {
return this.delegate.getId();
}
/* 获取大小:缓存中元素数量 */
public int getSize() {
return this.delegate.getSize();
}
/* 设定大小:更改队列大小 */
public void setSize(int size) {
this.size = size;
}
/* 放入元素(k-v) */
public void putObject(Object key, Object value) {
cycleKeyList(key); //策略保障
delegate.putObject(key, value); //放入元素
}
/* 获取元素(k) */
public Object getObject(Object key) {
return delegate.getObject(key);
}
/* 移除元素(k) */
public Object removeObject(Object key) {
return delegate.removeObject(key);
}
/* 清空缓存 */
public void clear() {
delegate.clear(); //清空缓存
keyList.clear(); //清空队列
}
/* 策略保障方法 */
private void cycleKeyList(Object key) {
keyList.addLast(key); //将 key 加入队列
if (this.keyList.size() > size) { //队列大小超出限制
Object oldestKey = keyList.removeFirst(); //删除队头元素
delegate.removeObject(oldestKey); //删除缓存元素(k)
}
}
}
② LruCache - LRU策略
LRU(Least Recently Used, 最近最少使用) 回收策略,底层基于 继承 LinkedHashMap 的匿名内部类 实现,重新规定了 序列化ID serialVersionUID、重写了 removeEldestEntry(移除最近最少使用元素) 方法。
/* LRU策略实现缓存 */
public class LruCache implements Cache {
//装饰对象
private final Cache delegate;
//LRU实现
private Map<Object, Object> keyMap;
//最近最少使用元素的key
private Object eldestKey;
/* 唯一构造器:初始化 */
public LruCache(Cache delegate) {
this.delegate = delegate;
this.setSize(1024); //默认限制大小为1024
}
/* 获取缓存元素标识(唯一ID)*/
@Override
public String getId() {
return delegate.getId();
}
/* 获取大小:缓存中元素数量 */
@Override
public int getSize() {
return delegate.getSize(); //初始化 keyMap
}
/* 设定大小*/
public void setSize(final int size) {
/** LRU 保障
* initialCapacity:1024 初始大小
* loadFactor: 负载因子 0.75F(与默认一致)
* accessOrder:true,将访问过的元素置于链表末端(flase:依据插入顺序)
*
* 匿名内部类:继承自 LinkedHashMap
*/
this.keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
// 重新规定序列号
private static final long serialVersionUID = 4267176411845948333L;
/* 重写 removeEldestEntry 方法 */
@Override
protected boolean removeEldestEntry(Entry<Object, Object> eldest) {
boolean tooBig = size() > size; //元素数量超出初始化大小
if (tooBig) {
eldestKey = eldest.getKey(); //获取键
}
return tooBig; //返回布尔值
}
};
}
/* 放入元素(k-v) */
public void putObject(Object key, Object value) {
delegate.putObject(key, value); //放入元素
cycleKeyList(key); //策略保障
}
/* 获取元素(k) */
public Object getObject(Object key) {
keyMap.get(key);
return delegate.getObject(key);
}
/* 移除元素(k) */
public Object removeObject(Object key) {
return delegate.removeObject(key);
}
/* 清空缓存 */
public void clear() {
delegate.clear(); //清空缓存
keyMap.clear(); //清空 keyMap
}
/* 策略保障方法 */
private void cycleKeyList(Object key) {
this.keyMap.put(key, key); //放置元素
if (this.eldestKey != null) { //出现最近最少使用元素
delegate.removeObject(this.eldestKey); //移除对应元素
eldestKey = null; //最近最少使用元素置空
}
}
}
③ WeakCache - 弱引用策略
弱引用 回收策略,弱引用的对象一旦被垃圾收集器发现,则会被回收,无论内存是否足够。
/* 弱引用 回收策略 */
public class WeakCache implements Cache {
//引用保留队列
private final Deque<Object> hardLinksToAvoidGarbageCollection;
//引用队列
private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;
//装饰对象
private final Cache delegate;
//引用保留数
private int numberOfHardLinks;
/* 唯一构造方法:初始化 */
public WeakCache(Cache delegate) {
this.delegate = delegate;
this.numberOfHardLinks = 256; //引用保留队列(默认256)
this.hardLinksToAvoidGarbageCollection = new LinkedList<>();
this.queueOfGarbageCollectedEntries = new ReferenceQueue<>();
}
/* 获取缓存元素标识(唯一ID)*/
@Override
public String getId() {
return delegate.getId();
}
/* 获取大小:缓存中元素数量 */
@Override
public int getSize() {
removeGarbageCollectedItems();
return delegate.getSize();
}
public void setSize(int size) {
this.numberOfHardLinks = size;
}
/* 放入元素(k-v) */
@Override
public void putObject(Object key, Object value) {
removeGarbageCollectedItems(); //移除 GC 项
//以弱键值放入缓存
delegate.putObject(key, new WeakEntry(key, value, queueOfGarbageCollectedEntries));
}
/* 获取元素(k) */
@Override
public Object getObject(Object key) {
Object result = null; //结果
//从缓存中取出弱引用
@SuppressWarnings("unchecked") // assumed delegate cache is totally managed by this cache
WeakReference<Object> weakReference = (WeakReference<Object>) delegate.getObject(key);
if (weakReference != null) { //弱引用对象不为空
result = weakReference.get(); //从弱引用中取出被缓存元素
if (result == null) { //结果为null
delegate.removeObject(key); //从缓存中移除这个键
} else { //结果不为null
synchronized (hardLinksToAvoidGarbageCollection) {
hardLinksToAvoidGarbageCollection.addFirst(result); //向引用保留队列头部添加元素
//引用保留队列元素超出数量限制
if (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) {
hardLinksToAvoidGarbageCollection.removeLast(); //移除引用保留队列尾部元素
}
}
}
}
return result; //返回结果
}
/* 移除元素(k) */
@Override
public Object removeObject(Object key) {
removeGarbageCollectedItems();
return delegate.removeObject(key);
}
/* 清空缓存 */
@Override
public void clear() {
synchronized (hardLinksToAvoidGarbageCollection) {
hardLinksToAvoidGarbageCollection.clear(); //清空引用保留队列
}
removeGarbageCollectedItems(); //移除 GC 项
delegate.clear();
}
/* 移除 GC 项 */
private void removeGarbageCollectedItems() {
WeakEntry sv; //弱引用键值对
while ((sv = (WeakEntry) queueOfGarbageCollectedEntries.poll()) != null) { //从中依次弹出
delegate.removeObject(sv.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 - 软引用策略
软引用 回收策略,软引用只有当内存不足时才会被垃圾收集器回收。
内部使用了一个双向队列保证一定数量的值即使内存不足也不会被回收,但是没有保存在该双向队列的值则有可能会被回收。
public class SoftCache implements Cache {
//引用保留队列
private final Deque<Object> hardLinksToAvoidGarbageCollection;
//引用队列
private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;
//装饰对象
private final Cache delegate;
//引用保留数
private int numberOfHardLinks;
/* 唯一构造方法:初始化 */
public SoftCache(Cache delegate) {
this.delegate = delegate;
this.numberOfHardLinks = 256;
this.hardLinksToAvoidGarbageCollection = new LinkedList<>();
this.queueOfGarbageCollectedEntries = new ReferenceQueue<>();
}
/* 获取缓存元素标识(唯一ID)*/
@Override
public String getId() {
return delegate.getId();
}
/* 获取大小:缓存中元素数量 */
@Override
public int getSize() {
removeGarbageCollectedItems();
return delegate.getSize();
}
/* 设置引用保留队列大小 */
public void setSize(int size) {
this.numberOfHardLinks = size;
}
/* 放入元素(k-v) */
@Override
public void putObject(Object key, Object value) {
removeGarbageCollectedItems();
delegate.putObject(key, new SoftEntry(key, value, queueOfGarbageCollectedEntries));
}
/* 获取元素(k) */
@Override
public Object getObject(Object key) {
Object result = null; //结果
//获取软引用对应元素
@SuppressWarnings("unchecked") // assumed delegate cache is totally managed by this cache
SoftReference<Object> softReference = (SoftReference<Object>) delegate.getObject(key);
if (softReference != null) { //软引用不为null
result = softReference.get(); //将元素赋予result
if (result == null) { //元素为null
delegate.removeObject(key); //移除key
} else { //不为空
// See #586 (and #335) modifications need more than a read lock
synchronized (hardLinksToAvoidGarbageCollection) { //对引用保留队列加锁
hardLinksToAvoidGarbageCollection.addFirst(result); //向引用保留队列首部添加元素
if (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) { //引用保留队列元素数量超出限制
hardLinksToAvoidGarbageCollection.removeLast(); //移除引用保留队列尾部元素
}
}
}
}
return result;
}
/* 移除元素(k) */
@Override
public Object removeObject(Object key) {
removeGarbageCollectedItems(); //移除 GC 项
return delegate.removeObject(key); //返回移除元素
}
/* 清空元素 */
@Override
public void clear() {
synchronized (hardLinksToAvoidGarbageCollection) { //对引用保留队列加同步锁
hardLinksToAvoidGarbageCollection.clear(); //清空引用保留队列
}
removeGarbageCollectedItems(); //移除 GC 项
delegate.clear(); //清空缓存
}
}
☯ 辅助装饰
① LogginCache - 命中率记录
用于记录缓存的命中率,如果开启了DEBUG模式,则会输出命中率日志。
public class LoggingCache implements Cache {
//Mybatis内置日志模块
private final Log log;
//装饰对象
private final Cache delegate;
//请求数
protected int requests = 0;
//命中数
protected int hits = 0;
/* 唯一构造器:初始化 */
public LoggingCache(Cache delegate) {
this.delegate = delegate;
this.log = LogFactory.getLog(this.getId()); //通过工厂类LogFactory获取Log实例
}
/* 获取缓存元素标识(唯一ID)*/
@Override
public String getId() {
return delegate.getId();
}
/* 获取大小:缓存中元素数量 */
@Override
public int getSize() {
return delegate.getSize();
}
/* 放入元素(k-v) */
@Override
public void putObject(Object key, Object object) {
delegate.putObject(key, object);
}
/* 获取元素(k) */
@Override
public Object getObject(Object key) {
//请求数++
requests++;
//记录元素value
final Object value = delegate.getObject(key);
//value不为空:命中
if (value != null) {
hits++; //命中数++
}
//输出日志记录:调用 getHitRatio 方法计算命中率
if (log.isDebugEnabled()) {
log.debug("Cache Hit Ratio [" + getId() + "]: " + this.getHitRatio());
}
return value;
}
/* 移除元素(k) */
@Override
public Object removeObject(Object key) {
return delegate.removeObject(key);
}
/* 清空缓存 */
@Override
public void clear() {
delegate.clear();
}
/* 重写hashCode 和 equals方法(调用被装饰对象方法)*/
@Override
public int hashCode() {
return delegate.hashCode();
}
@Override
public boolean equals(Object obj) {
return delegate.equals(obj);
}
/* 获取命中率:命中数 / 请求数 */
private double getHitRatio() {
return (double)hits / (double)requests;
}
}
② SynchronizedCache - 缓存同步
同步Cache,直接使用 synchronized
关键字 修饰方法实现。
/* 缓存同步 */
public class SynchronizedCache implements Cache {
//装饰对象
private final Cache delegate;
/* 唯一构造器:初始化 */
public SynchronizedCache(Cache delegate) {
this.delegate = delegate;
}
/* 获取缓存元素标识(唯一ID)*/
@Override
public String getId() {
return delegate.getId();
}
/* 获取大小:缓存中元素数量 */
@Override
public synchronized int getSize() {
return delegate.getSize();
}
/* 放入元素(k-v) */
@Override
public synchronized void putObject(Object key, Object object) {
delegate.putObject(key, object);
}
/* 获取元素(k) */
@Override
public synchronized Object getObject(Object key) {
return delegate.getObject(key);
}
/* 移除元素(k) */
@Override
public synchronized Object removeObject(Object key) {
return delegate.removeObject(key);
}
/* 清空元素 */
@Override
public synchronized void clear() {
delegate.clear();
}
/* 重写hashCode 和 equals方法(调用被装饰对象方法)*/
@Override
public int hashCode() {
return delegate.hashCode();
}
@Override
public boolean equals(Object obj) {
return delegate.equals(obj);
}
}
③ SerializedCache - 缓存序列化
序列化功能,将值序列化后存到缓存中,要求元素实现序列化接口。该功能用于缓存返回一份实例的Copy,用于保证线程安全。
/* 缓存序列化 */
public class SerializedCache implements Cache {
//装饰对象
private final Cache delegate;
/* 唯一构造器:初始化 */
public SerializedCache(Cache delegate) {
this.delegate = delegate;
}
/* 获取缓存元素标识(唯一ID)*/
@Override
public String getId() {
return delegate.getId();
}
/* 获取大小:缓存中元素数量 */
@Override
public int getSize() {
return delegate.getSize();
}
/* 放入元素(k-v) */
@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);
}
}
/* 获取元素(k) */
@Override
public Object getObject(Object key) {
Object object = delegate.getObject(key);
return object == null ? null : deserialize((byte[]) object);
}
/* 移除元素(k) */
@Override
public Object removeObject(Object key) {
return delegate.removeObject(key);
}
/* 清空元素 */
@Override
public void clear() {
delegate.clear();
}
/* 重写hashCode 和 equals方法(调用被装饰对象方法)*/
@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) {
SerialFilterChecker.check(); //序列化检查
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);
}
//根据名称返回对应的 Class对象
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws ClassNotFoundException {
return Resources.classForName(desc.getName());
}
}
}
④ ScheduledCache - 定时清空
定时清空Cache,在使用Cache时,检查清空时长(清理间隔默认为1小时)
/* 定时清空缓存 */
public class ScheduledCache implements Cache {
//装饰对象
private final Cache delegate;
//定期清理间隔时长
protected long clearInterval;
//最后一次清理时间
protected long lastClear;
/* 唯一构造器:初始化 */
public ScheduledCache(Cache delegate) {
this.delegate = delegate;
this.clearInterval = TimeUnit.HOURS.toMillis(1L); //设置定期清理间隔时长,默认为1小时,并将其转换为毫秒
this.lastClear = System.currentTimeMillis(); //记录清理时间
}
/* 设置定期清理间隔时长 */
public void setClearInterval(long clearInterval) {
this.clearInterval = clearInterval;
}
/* 获取缓存元素标识(唯一ID)*/
@Override
public String getId() {
return delegate.getId();
}
/* 获取大小:缓存中元素数量 */
@Override
public int getSize() {
clearWhenStale();
return delegate.getSize();
}
/* 放入元素(k-v) */
@Override
public void putObject(Object key, Object object) {
clearWhenStale();
delegate.putObject(key, object);
}
/* 获取元素(k) */
@Override
public Object getObject(Object key) {
return clearWhenStale() ? null : this.delegate.getObject(key);
}
/* 移除元素(k) */
@Override
public Object removeObject(Object key) {
clearWhenStale();
return delegate.removeObject(key);
}
/* 清空缓存 */
@Override
public void clear() {
lastClear = System.currentTimeMillis(); //设置最后一次清除时间
delegate.clear(); //清空缓存
}
/* 重写hashCode 和 equals 方法(调用被装饰对象方法)*/
@Override
public int hashCode() {
return delegate.hashCode();
}
@Override
public boolean equals(Object obj) {
return delegate.equals(obj);
}
/* 检查间隔时长并清理缓存 */
private boolean clearWhenStale() {
//时间间隔达到设定时长
if (System.currentTimeMillis() - lastClear > clearInterval) {
clear(); //清理缓存
return true; //清理成功
}
return false; //清理失败
}
}
⑤ TransactionalCache - 事务缓存
事务Cache,事务提交时,放入缓存中,回滚时清除。
/* 事务缓存 */
public class TransactionalCache implements Cache {
//通过 LogFactory 获取 Mybatis 内置 日志模块 log 对象
private static final Log log = LogFactory.getLog(TransactionalCache.class);
//装饰对象
private final Cache delegate;
//提交时清除
private boolean clearOnCommit;
//事务提交 --> 元素加入缓存 映射
private final Map<Object, Object> entriesToAddOnCommit;
//缓存未出现元素集
private final Set<Object> entriesMissedInCache;
/* 唯一构造器:初始化 */
public TransactionalCache(Cache delegate) {
this.delegate = delegate;
this.clearOnCommit = false;
this.entriesToAddOnCommit = new HashMap();
this.entriesMissedInCache = new HashSet();
}
/* 获取缓存元素标识(唯一ID)*/
@Override
public String getId() {
return delegate.getId();
}
/* 获取大小:缓存中元素数量 */
@Override
public int getSize() {
return delegate.getSize();
}
/* 获取元素(k) */
@Override
public Object getObject(Object key) {
// issue #116
Object object = delegate.getObject(key);
if (object == null) {
entriesMissedInCache.add(key);
}
// issue #146
if (clearOnCommit) {
return null;
} else {
return object;
}
}
/* 放入元素(k-v) */
@Override
public void putObject(Object key, Object object) {
entriesToAddOnCommit.put(key, object);
}
/* 移除元素(k):默认返回null */
@Override
public Object removeObject(Object key) {
return null;
}
/* 清空缓存 */
@Override
public void clear() {
clearOnCommit = true;
entriesToAddOnCommit.clear();
}
/* 提交 */
public void commit() {
if (this.clearOnCommit) { //提交时清除
delegate.clear();
}
flushPendingEntries(); //提交事务:将元素加入缓存
reset(); //重新设置
}
/* 回滚 */
public void rollback() {
unlockMissedEntries(); //事务回滚:将元素移除缓存
reset(); //重新设置
}
/* 重新设置 */
private void reset() {
clearOnCommit = false;
entriesToAddOnCommit.clear();
entriesMissedInCache.clear();
}
/* 放入缓存 */
private void flushPendingEntries() {
for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {//从 事务提交映射 中取出键值对
delegate.putObject(entry.getKey(), entry.getValue()); //将 事务提交映射 中元素放入缓存中
}
for (Object entry : entriesMissedInCache) { //从 缓存未出现元素集 中取出键值对
if (!entriesToAddOnCommit.containsKey(entry)) { //事务提交映射 中不含有该键值对
delegate.putObject(entry, null); //将该值放入缓存中
}
}
}
/* 尝试从缓存中移除 */
private void unlockMissedEntries() {
for (Object entry : entriesMissedInCache) {
try {
delegate.removeObject(entry); //尝试从缓存中移除元素
} catch (Exception e) {
log.warn("Unexpected exception while notifying a rollback to the cache adapter. "
+ "Consider upgrading your cache adapter to the latest version. Cause: " + e);
}
}
}
}
该类配合 TransactionalCacheManager 类使用:
public class TransactionalCacheManager {
//缓存映射
private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();
/* 清空 */
public void clear(Cache cache) {
getTransactionalCache(cache).clear();
}
/* 获取元素(c:k) */
public Object getObject(Cache cache, CacheKey key) {
return getTransactionalCache(cache).getObject(key);
}
/* 放入元素(c:k-v) */
public void putObject(Cache cache, CacheKey key, Object value) {
getTransactionalCache(cache).putObject(key, value);
}
/* 事务提交 */
public void commit() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.commit();
}
}
/* 事务回滚 */
public void rollback() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.rollback();
}
}
/* 获取事务型缓存 */
private TransactionalCache getTransactionalCache(Cache cache) {
return MapUtil.computeIfAbsent(transactionalCaches, cache, TransactionalCache::new);
}
}
⑥ BlockingCache - 缓存阻塞
阻塞装饰器,当元素在缓存中找不到的时候,对缓存键设置一个锁,其他线程不到达数据库,等待这个元素被填满;BlockingCache 使用不当时可能会导致死锁。
核心方法 acquireLock 流程图:
类代码:
public class BlockingCache implements Cache {
//设置超时限制
private long timeout;
//装饰对象
private final Cache delegate;
//ConcurrentHashMap:存储对象对应的锁
private final ConcurrentHashMap<Object, CountDownLatch> locks;
/* 唯一构造方法:初始化 */
public BlockingCache(Cache delegate) {
this.delegate = delegate;
this.locks = new ConcurrentHashMap<>();
}
/* 获取缓存元素标识(唯一ID)*/
@Override
public String getId() {
return delegate.getId();
}
/* 获取大小:缓存中元素数量 */
@Override
public int getSize() {
return delegate.getSize();
}
/* 放入元素(k-v) */
@Override
public void putObject(Object key, Object value) {
try {
delegate.putObject(key, value); //放入元素
} finally {
releaseLock(key); //释放锁
}
}
/* 获取元素(k) */
@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();
}
/* 锁需求 */
private void acquireLock(Object key) {
//计数门栅:使一个线程等待其他线程各自执行完毕后再执行
CountDownLatch newLatch = new CountDownLatch(1); //线程通过之前必须调用countDown方法的次数为1 (计数值)
while (true) {
// 假设key=1,第一次进入循环,调用 CountDownLatch latch = locks.putIfAbsent(key, newLatch) ,latch 的值的是null,从while循环跳出
// 同样key=1,第二次进入循环,若第一次进入的 newLatch 没有释放锁正在执行缓存操作时,那么 locks.putIfAbsent(key, newLatch) 返回第一次设置的 newLatch
// 此时 latch 不为null,向后执行:等待/等待超时 抛出异常
// 第一次进入循环的 newLatch 操作完毕,执行releaseLock方法,删除locks中对应的键key=1,返回第一次的 newLatch,调用 latch.countDown()方法,计数值变成0
// 第二次进入循环的 newLatch 调用 latch.await() 方法,程序继续执行
// 第二次进入循环的 key=1,执行 locks.putIfAbsent(key, newLatch) ,latch 的值为null,从while循环跳出
CountDownLatch latch = locks.putIfAbsent(key, newLatch); //将门删放入锁映射(拒绝重复)
if (latch == null) { //null:同key -->锁 已释放 / key 不存在
break; //跳出循环
}
try {
if (timeout > 0) { //手动设置超时时间
//latch 的计数值变成0的时候,返回true
boolean acquired = latch.await(timeout, TimeUnit.MILLISECONDS); //按设定时间等待释放
if (!acquired) { //超时但计数值仍未变成0 --> false --> 抛出异常。
throw new CacheException(
"Couldn't get a lock in " + timeout + " for the key " + key + " at the cache " + delegate.getId());
}
} else { //未手动设置超时时间
latch.await(); //等待释放
}
} catch (InterruptedException e) {
throw new CacheException("Got interrupted while trying to acquire lock for key " + key, e);
}
}
}
/* 锁释放 */
private void releaseLock(Object key) {
CountDownLatch latch = locks.remove(key); //根据key从映射中移除该门删
if (latch == null) { //映射中不存在该key
throw new IllegalStateException("Detected an attempt at releasing unacquired lock. This should never happen.");
}
latch.countDown(); //线程开启并运行,完成后计数器 -1
}
/* 获取时长 */
public long getTimeout() {
return timeout;
}
/* 设定时长 */
public void setTimeout(long timeout) {
this.timeout = timeout;
}
}
3. 异常类 - CacheException
Mybatis 缓存模块提供了自定义异常 CacheException
并在模块中得到广泛应用:
/* 缓存异常 */
public class CacheException extends PersistenceException {
//序列化ID
private static final long serialVersionUID = -193202262468464650L;
/* 无参构造器 */
public CacheException() {
super();
}
/* 单参构造器:异常信息 */
public CacheException(String message) {
super(message);
}
/* 双参构造器:异常信息、异常 */
public CacheException(String message, Throwable cause) {
super(message, cause);
}
/* 单参构造器:异常 *
* ---------------------------------
* 引起这个异常的异常:
* 若值为null:该异常为源头;
* 若值为自身:该异常未被初始化
*/
public CacheException(Throwable cause) {
super(cause);
}
}
4. 缓存键 - CacheKey
CacheKey 是 cache 的 key:
由于 Mybatis 中可以使用动态SQL,所以缓存项不能单纯使用一个 String 来表示,所以通过 CacheKey 封装缓存的 key 值,CacheKey 可以封装多个影响因素:
- namespace.id
- 指定查询集范围:分页
- 查询使用 SQL 语句
- 用户向 SQL 语句传递的参数
/* 缓存key : 实现Cloneable、Serializable 接口 */
public class CacheKey implements Cloneable, Serializable {
//序列化ID
private static final long serialVersionUID = 1146682552656046210L;
//匿名内部类 NULL_CACHE_KEY(缓存空key):继承自 CacheKey:
public static final CacheKey NULL_CACHE_KEY = new CacheKey() {
/* [重写] update 抛异常:不允许向 null-k --> v 执行更新操作 */
@Override
public void update(Object object) {
throw new CacheException("Not allowed to update a null cache key instance.");
}
/* [重写] update 抛异常:不允许向 null-k --> v 执行更新操作 */
@Override
public void updateAll(Object[] objects) {
throw new CacheException("Not allowed to update a null cache key instance.");
}
};
//默认 hashcode 计算乘数
private static final int DEFAULT_MULTIPLIER = 37;
// 默认hashcode
private static final int DEFAULT_HASHCODE = 17;
//hashcode 计算乘数
private final int multiplier;
//hashcode
private int hashcode;
//校验总和:hashcode 值的和
private long checksum;
//updateList 元素个数
private int count;
//更新操作列表
private List<Object> updateList;
/* 无参构造器:默认初始化 */
public CacheKey() {
this.hashcode = DEFAULT_HASHCODE;
this.multiplier = DEFAULT_MULTIPLIER;
this.count = 0;
this.updateList = new ArrayList<>();
}
/* 单参构造器:传入需要操作的元素数组*/
public CacheKey(Object[] objects) {
this(); //调用无参构造器
updateAll(objects); //执行更新全部操作
}
/* 获取更新数 */
public int getUpdateCount() {
return updateList.size();
}
/* 更新操作 */
public void update(Object object) {
//计算 base-hashcode:元素非空计算元素 hashCode,否则规定为1
int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);
//更新 count、hashcode、checksum 值
count++; //元素数量增加
checksum += baseHashCode; //检查总和 += base-hashcode
baseHashCode *= count; //base-hashcode *= 元素数量
hashcode = multiplier * hashcode + baseHashCode; //新hashcode = 倍数 * 旧hashcode + base-hashcode
updateList.add(object); //向更新列表内添加元素
}
/* 更新全部 */
public void updateAll(Object[] objects) {
for (Object o : objects) { //foreach 循环依次调用 update 方法
update(o);
}
}
/* 重写 equals 和 hashCode 方法*/
@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) { //hashcode 是否相等
return false;
}
if (checksum != cacheKey.checksum) { //checksum 是否相等
return false;
}
if (count != cacheKey.count) { //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;
}
/* 重写同 String 方法 */
@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();
}
/* 重写clone方法 */
@Override
public CacheKey clone() throws CloneNotSupportedException {
CacheKey clonedCacheKey = (CacheKey) super.clone();
clonedCacheKey.updateList = new ArrayList<>(updateList);
return clonedCacheKey;
}
}
三、两级缓存
将查询出来的数据,加入缓存,再次使用时无需从关系数据库里面查询,可提高查询效率。
1. 一级缓存
- Mybatis 在开启一个数据库会话时,创建一个新的 SqlSession对象,内含一个持有PerpetualCache 的 Executor 对象;会话结束时,缓存随着 SqlSession 的释放一并释放。
- SqlSession 调用 close() 方法 会释放一级缓存 PerprtualCache 对象,缓存失效。
- SqlSession 调用 clearCache() 方法 会清空 PerprtualCache 对象中的数据,但对象可用。
- SqlSession 执行任何一个 update 操作(update、delete、insert)都会清空数据,但对象可用。
2. 二级缓存
Mybatis 的二级缓存是 namespace 级别的缓存,它可以提高对数据库查询的效率。二级缓存默认不开启,需进行配置(映射xml文件中添加cache
),二级缓存 要求实体实现 Serializable
接口。
配置二级缓存后:
- SELECT语句会被缓存,UPDATE类(UPDATE、INSERT、DELETE)语句会刷新缓存。
- 缓存默认使用LRU(Least Recently Used,最近最少使用)算法回收。
- 缓存会存储集合 / 对象 的1024 个引用。