本文 Mybatis 版本为 2.2.0
一、概述
MyBatis中的Cache以SPI实现,给需要集成其它Cache或者自定义Cache提供了接口。
Mybatis支持一级缓存 和 二级缓存,一级缓存 默认开启,而 二建缓存 需要进行配置。
Mybatis提供的缓存大大提升了查询效率,而这两级缓存都依赖cache模块。
cache 模块应用了设计模式中的装饰模式,我们可以从包命名 上区分,Cache 的实现类只有位于impl 包下的PerpetualCache 类,其余位于decorators 包下的类均为装饰者类。
接口 Cache
该模块提供了 Cache 接口,Mybatis 在内部定义了很多实现该接口的装饰类,用户也可以通过实现该接口定义自己针对缓存的 策略包装类、辅助包装类。
public interface Cache {
String getId();
void putObject(Object var1, Object var2);
Object getObject(Object var1);
Object removeObject(Object var1);
void clear();
int getSize();
default ReadWriteLock getReadWriteLock() {
return null;
}
}
二、组成
1. 实现类 - PerpetualCache
public class PerpetualCache implements Cache {
private final String id;
private final Map<Object, Object> cache = new HashMap();
public PerpetualCache(String id) {
this.id = id;
}
@Override
public String getId() {
return this.id;
}
@Override
public int getSize() {
return this.cache.size();
}
@Override
public void putObject(Object key, Object value) {
this.cache.put(key, value);
}
@Override
public Object getObject(Object key) {
return this.cache.get(key);
}
@Override
public Object removeObject(Object key) {
return this.cache.remove(key);
}
@Override
public void clear() {
this.cache.clear();
}
@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 中移除该元素。
public class FifoCache implements 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;
}
public String getId() {
return this.delegate.getId();
}
public int getSize() {
return this.delegate.getSize();
}
public void setSize(int size) {
this.size = size;
}
public void putObject(Object key, Object value) {
cycleKeyList(key);
delegate.putObject(key, value);
}
public Object getObject(Object key) {
return delegate.getObject(key);
}
public Object removeObject(Object key) {
return delegate.removeObject(key);
}
public void clear() {
delegate.clear();
keyList.clear();
}
private void cycleKeyList(Object key) {
keyList.addLast(key);
if (this.keyList.size() > size) {
Object oldestKey = keyList.removeFirst();
delegate.removeObject(oldestKey);
}
}
}
② LruCache - LRU策略
LRU(Least Recently Used, 最近最少使用) 回收策略,底层基于 继承 LinkedHashMap 的匿名内部类 实现,重新规定了 序列化ID serialVersionUID、重写了 removeEldestEntry(移除最近最少使用元素) 方法。
public class LruCache implements Cache {
private final Cache delegate;
private Map<Object, Object> keyMap;
private Object eldestKey;
public LruCache(Cache delegate) {
this.delegate = delegate;
this.setSize(1024);
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
return delegate.getSize();
}
public void setSize(final int size) {
this.keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
private static final long serialVersionUID = 4267176411845948333L;
@Override
protected boolean removeEldestEntry(Entry<Object, Object> eldest) {
boolean tooBig = size() > size;
if (tooBig) {
eldestKey = eldest.getKey();
}
return tooBig;
}
};
}
public void putObject(Object key, Object value) {
delegate.putObject(key, value);
cycleKeyList(key);
}
public Object getObject(Object key) {
keyMap.get(key);
return delegate.getObject(key);
}
public Object removeObject(Object key) {
return delegate.removeObject(key);
}
public void clear() {
delegate.clear();
keyMap.clear();
}
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;
this.hardLinksToAvoidGarbageCollection = new LinkedList<>();
this.queueOfGarbageCollectedEntries = new ReferenceQueue<>();
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
removeGarbageCollectedItems();
return delegate.getSize();
}
public void setSize(int size) {
this.numberOfHardLinks = size;
}
@Override
public void putObject(Object key, Object value) {
removeGarbageCollectedItems();
delegate.putObject(key, new WeakEntry(key, value, queueOfGarbageCollectedEntries));
}
@Override
public Object getObject(Object key) {
Object result = null;
@SuppressWarnings("unchecked")
WeakReference<Object> weakReference = (WeakReference<Object>) delegate.getObject(key);
if (weakReference != null) {
result = weakReference.get();
if (result == null) {
delegate.removeObject(key);
} else {
synchronized (hardLinksToAvoidGarbageCollection) {
hardLinksToAvoidGarbageCollection.addFirst(result);
if (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) {
hardLinksToAvoidGarbageCollection.removeLast();
}
}
}
}
return result;
}
@Override
public Object removeObject(Object key) {
removeGarbageCollectedItems();
return delegate.removeObject(key);
}
@Override
public void clear() {
synchronized (hardLinksToAvoidGarbageCollection) {
hardLinksToAvoidGarbageCollection.clear();
}
removeGarbageCollectedItems();
delegate.clear();
}
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<>();
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
removeGarbageCollectedItems();
return delegate.getSize();
}
public void setSize(int size) {
this.numberOfHardLinks = size;
}
@Override
public void putObject(Object key, Object value) {
removeGarbageCollectedItems();
delegate.putObject(key, new SoftEntry(key, value, queueOfGarbageCollectedEntries));
}
@Override
public Object getObject(Object key) {
Object result = null;
@SuppressWarnings("unchecked")
SoftReference<Object> softReference = (SoftReference<Object>) delegate.getObject(key);
if (softReference != null) {
result = softReference.get();
if (result == null) {
delegate.removeObject(key);
} else {
synchronized (hardLinksToAvoidGarbageCollection) {
hardLinksToAvoidGarbageCollection.addFirst(result);
if (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) {
hardLinksToAvoidGarbageCollection.removeLast();
}
}
}
}
return result;
}
@Override
public Object removeObject(Object key) {
removeGarbageCollectedItems();
return delegate.removeObject(key);
}
@Override
public void clear() {
synchronized (hardLinksToAvoidGarbageCollection) {
hardLinksToAvoidGarbageCollection.clear();
}
removeGarbageCollectedItems();
delegate.clear();
}
}
? 辅助装饰
① LogginCache - 命中率记录
用于记录缓存的命中率,如果开启了DEBUG模式,则会输出命中率日志。
public class LoggingCache implements Cache {
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());
}
@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) {
requests++;
final Object value = delegate.getObject(key);
if (value != null) {
hits++;
}
if (log.isDebugEnabled()) {
log.debug("Cache Hit Ratio [" + getId() + "]: " + this.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);
}
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;
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public synchronized int getSize() {
return delegate.getSize();
}
@Override
public synchronized void putObject(Object key, Object object) {
delegate.putObject(key, object);
}
@Override
public synchronized Object getObject(Object key) {
return delegate.getObject(key);
}
@Override
public synchronized Object removeObject(Object key) {
return delegate.removeObject(key);
}
@Override
public synchronized void clear() {
delegate.clear();
}
@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;
}
@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) {
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);
}
@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);
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 : this.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);
}
private boolean clearWhenStale() {
if (System.currentTimeMillis() - lastClear > clearInterval) {
clear();
return true;
}
return false;
}
}
⑤ TransactionalCache - 事务缓存
事务Cache,事务提交时,放入缓存中,回滚时清除。
public class TransactionalCache implements Cache {
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();
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
return delegate.getSize();
}
@Override
public Object getObject(Object key) {
Object object = delegate.getObject(key);
if (object == null) {
entriesMissedInCache.add(key);
}
if (clearOnCommit) {
return null;
} else {
return object;
}
}
@Override
public void putObject(Object key, Object object) {
entriesToAddOnCommit.put(key, object);
}
@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();
}
public Object getObject(Cache cache, CacheKey key) {
return getTransactionalCache(cache).getObject(key);
}
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;
private final ConcurrentHashMap<Object, CountDownLatch> 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) {
releaseLock(key);
return null;
}
@Override
public void clear() {
delegate.clear();
}
private void acquireLock(Object key) {
CountDownLatch newLatch = new CountDownLatch(1);
while (true) {
CountDownLatch latch = locks.putIfAbsent(key, newLatch);
if (latch == null) {
break;
}
try {
if (timeout > 0) {
boolean acquired = latch.await(timeout, TimeUnit.MILLISECONDS);
if (!acquired) {
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);
if (latch == null) {
throw new IllegalStateException("Detected an attempt at releasing unacquired lock. This should never happen.");
}
latch.countDown();
}
public long getTimeout() {
return timeout;
}
public void setTimeout(long timeout) {
this.timeout = timeout;
}
}
3. 异常类 - CacheException
Mybatis 缓存模块提供了自定义异常 CacheException 并在模块中得到广泛应用:
public class CacheException extends PersistenceException {
private static final long serialVersionUID = -193202262468464650L;
public CacheException() {
super();
}
public CacheException(String message) {
super(message);
}
public CacheException(String message, Throwable cause) {
super(message, cause);
}
public CacheException(Throwable cause) {
super(cause);
}
}
4. 缓存键 - CacheKey
CacheKey 是 cache 的 key: 由于 Mybatis 中可以使用动态SQL,所以缓存项不能单纯使用一个 String 来表示,所以通过 CacheKey 封装缓存的 key 值,CacheKey 可以封装多个影响因素:
- namespace.id
- 指定查询集范围:分页
- 查询使用 SQL 语句
- 用户向 SQL 语句传递的参数
public class CacheKey implements Cloneable, Serializable {
private static final long serialVersionUID = 1146682552656046210L;
public static final CacheKey NULL_CACHE_KEY = new CacheKey() {
@Override
public void update(Object object) {
throw new CacheException("Not allowed to update a null cache key instance.");
}
@Override
public void updateAll(Object[] objects) {
throw new CacheException("Not allowed to update a null cache key instance.");
}
};
private static final int DEFAULT_MULTIPLIER = 37;
private static final int DEFAULT_HASHCODE = 17;
private final int multiplier;
private int hashcode;
private long checksum;
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) {
int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);
count++;
checksum += baseHashCode;
baseHashCode *= count;
hashcode = multiplier * hashcode + baseHashCode;
updateList.add(object);
}
public void updateAll(Object[] objects) {
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 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 个引用。
|