本文从以下几个方面介绍:
- 1、如何开启 Mybatis 的缓存
- 2、缓存的核心接口以及底层实现
- 3、一级缓存的实现过程
- 4、二级缓存的实现过程
- 5、缓存的装饰器
前言
为了提高查询速度,减少数据库压力;Mybatis 提供了缓存功能,它分为一级缓存和二级缓存。
Mybatis 缓存系统的实现使用了 模板方法模式 和 装饰器模式
1、如何开启 Mybatis 的缓存
1.1、一级缓存的作用
Mybatis 的一级缓存是会话级别的缓存,Mybatis 每创建一个 SqlSession 对象,就表示打开一次数据库会话,在一次会话中,应用程序很可能在短时间内反复执行相同的查询语句,如果不对数据进行缓存,则每查询一次就要执行一次数据库查询,这就造成数据库资源的浪费。又因为通过 SqlSession 执行的操作,实际上由 Executor 来完成数据库操作的,所以在 Executor 中会建立一个简单的缓存,即一级缓存;将每次的查询结果缓存起来,再次执行查询的时候,会先查询一级缓存,如果命中,则直接返回,否则再去查询数据库并放入缓存中。
1.2、一级缓存的开启以及生命周期
一级缓存的生命周期与 SqlSession 的生命周期相同,当调用 Executor.close 方法的时候,缓存变得不可用。一级缓存是默认开启的,一般情况下不需要特殊的配置,如果需要特殊配置,则可以通过插件的形式来实现
1.3、二级缓存的开启以及生命周期
Mybatis 提供的二级缓存是应用级别的缓存,它的生命周期和应用程序的生命周期相同,且与二级缓存相关的配置有以下 3 个
- 1)、 mybatis-config.xml 配置文件中的 cacheEnabled 配置,它是二级缓存的总开关,只有该配置为 true ,后面的缓存配置才会生效。默认为 true,即二级缓存默认是开启的。
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
- 2)、Mapper.xml 配置文件中配置的 和 标签,如果 Mapper.xml 配置文件中配置了这两个标签中的任何一个,则表示开启了二级缓存的功能,如果配置了 标签,则在解析配置文件的时候,会为该配置文件指定的 namespace 创建相应的 Cache 对象作为其二级缓存(默认为 PerpetualCache 对象),如果配置了 节点,则通过 ref 属性的namespace值引用别的Cache对象作为其二级缓存。通过 和 标签来管理其在namespace中二级缓存功能的开启和关闭
- 3)、 节点中的 useCache 属性也可以开启二级缓存,该属性表示查询的结果是否要存入到二级缓存中,该属性默认为 true,也就是说 标签默认会把查询结果放入到二级缓存中
2、缓存的核心接口
2.1、Cache
Mybatis 使用 Cache 来表示缓存,它是一个接口,定义了缓存需要的一些方法
public interface Cache {
//获取缓存的id,即 namespace
String getId();
// 添加缓存
void putObject(Object key, Object value);
//根据key来获取缓存对应的值
Object getObject(Object key);
// 删除key对应的缓存
Object removeObject(Object key);
// 清空缓存
void clear();
// 获取缓存中数据的大小
int getSize();
//取得读写锁, 从3.2.6开始没用了
ReadWriteLock getReadWriteLock();
}
2.2、PerpetualCache
Mybatis 为 Cache 接口提供的真正的实现类是 PerpetualCache,其他的只是应用装饰器模式,提供能额外功能,
如线程安全缓存 SynchronizedCache,它的底层存储还是 PerpetualCache。
PerpetualCache 的实现就是一个简单的 HashMap
public class PerpetualCache implements Cache {
// id,一般对应mapper.xml 的namespace 的值
private String id;
// 用来存放数据,即缓存底层就是使用 map 来实现的
private Map<Object, Object> cache = new HashMap<Object, Object>();
public PerpetualCache(String id) {
this.id = id;
}
//......其他的getter方法.....
// 添加缓存
@Override
public void putObject(Object key, Object value) {
cache.put(key, value);
}
// 获取缓存
@Override
public Object getObject(Object key) {
return cache.get(key);
}
// 删除缓存
@Override
public Object removeObject(Object key) {
return cache.remove(key);
}
// 清空缓存
@Override
public void clear() {
cache.clear();
}
}
2.3、CacheKey
Mybatis 的缓存使用了 key-value 的形式存入到 HashMap 中,而 key 的话,Mybatis 使用了 CacheKey 来表示 key,
它的生成规则为:mappedStementId + offset + limit + SQL + queryParams + environment生成一个哈希码.
public class CacheKey implements Cloneable, Serializable {
private static final int DEFAULT_MULTIPLYER = 37;
private static final int DEFAULT_HASHCODE = 17;
// 参与计算hashcode,默认值为37
private int multiplier;
// CacheKey 对象的 hashcode ,默认值 17
private int hashcode;
// 检验和
private long checksum;
// updateList 集合的个数
private int count;
// 由该集合中的所有对象来共同决定两个 CacheKey 是否相等
private List<Object> updateList;
public int getUpdateCount() {
return updateList.size();
}
// 调用该方法,向 updateList 集合添加对应的对象
public void update(Object object) {
if (object != null && object.getClass().isArray()) {
// 如果是数组,则循环处理每一项
int length = Array.getLength(object);
for (int i = 0; i < length; i++) {
Object element = Array.get(object, i);
doUpdate(element);
}
} else {
doUpdate(object);
}
}
// 计算 count checksum hashcode 和把对象添加到 updateList 集合中
private void doUpdate(Object object) {
int baseHashCode = object == null ? 1 : object.hashCode();
count++;
checksum += baseHashCode;
baseHashCode *= count;
hashcode = multiplier * hashcode + baseHashCode;
updateList.add(object);
}
// 判断两个 CacheKey 是否相等
@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;
}
// 如果前几项都不满足,则循环遍历 updateList 集合,判断每一项是否相等,如果有一项不相等则这两个CacheKey不相等
for (int i = 0; i < updateList.size(); i++) {
Object thisObject = updateList.get(i);
Object thatObject = cacheKey.updateList.get(i);
if (thisObject == null) {
if (thatObject != null) {
return false;
}
} else {
if (!thisObject.equals(thatObject)) {
return false;
}
}
}
return true;
}
@Override
public int hashCode() {
return hashcode;
}
}
CacheKey 的创建
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
//cacheKey 对象
CacheKey cacheKey = new CacheKey();
// 向 updateList 存入id
cacheKey.update(ms.getId());
// 存入offset
cacheKey.update(rowBounds.getOffset());
// 存入limit
cacheKey.update(rowBounds.getLimit());
// 存入sql
cacheKey.update(boundSql.getSql());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
for (ParameterMapping parameterMapping : parameterMappings) {
if (parameterMapping.getMode() != ParameterMode.OUT) {
String propertyName = parameterMapping.getProperty();
MetaObject metaObject = configuration.newMetaObject(parameterObject);
Object value = metaObject.getValue(propertyName);
// 存入每一个参数
cacheKey.update(value);
}
}
if (configuration.getEnvironment() != null) {
// 存入 environmentId
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
3、一级缓存的实现
Mybatis 的一级缓存是在 BaseExecutor 中实现的
3.1、Executor
Executor 接口定义了操作数据库的基本方法,我们用 SqlSession 来执行 sql时,其实是操作了 Executor 的相关方法
public interface Executor {
ResultHandler NO_RESULT_HANDLER = null;
// insert | update | delete 的操作方法
int update(MappedStatement ms, Object parameter) throws SQLException;
// 查询,带分页,带缓存
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
// 查询,带分页
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
// 查询存储过程
<E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;
//刷新批处理语句
List<BatchResult> flushStatements() throws SQLException;
// 事务提交
void commit(boolean required) throws SQLException;
// 事务回滚
void rollback(boolean required) throws SQLException;
// 创建缓存的key
CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);
// 是否缓存
boolean isCached(MappedStatement ms, CacheKey key);
// 清空缓存
void clearLocalCache();
// 延迟加载
void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);
// 获取事务
Transaction getTransaction();
}
3.2、BaseExecutor
BaseExecutor 是一个抽象类,实现了 Executor 接口的所有方法,使用了模板模式和策略模式,抽象了 (doUpdate, doQuery, doQueryCursor, doFlushStatement)4个核心方法用来执行sql,它们由不同的子类实现
Mybatis 的一级缓存就是在该类中实现的。
具体代码实现如下
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
// 从缓存中获取结果
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 缓存中获取不到,则调用queryFromDatabase()方法从数据库中查询
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// 调用doQuery()方法查询
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
// 缓存查询结果
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
4、二级缓存的实现
Mybatis 的二级缓存是用 CachingExecutor 来实现的,它是 Executor 的一个装饰器类。为 Executor 对象添加了缓存的功能。
在介绍 CachingExecutor 之前,先来看看 CachingExecutor 依赖的两个类,TransactionalCacheManager 和 TransactionalCache。
4.1、TransactionalCache
TransactionalCache 实现了 Cache 接口,主要用于保存在某个 SqlSession 的某个事务中需要向某个二级缓存中添加的数据,代码如下:
public class TransactionalCache implements Cache {
// 底层封装的二级缓存对应的Cache对象
private Cache delegate;
// 为true时,表示当前的 TransactionalCache 不可查询,且提交事务时会清空缓存
private boolean clearOnCommit;
// 存放需要添加到二级缓存中的数据
private Map<Object, Object> entriesToAddOnCommit;
// 存放未命中缓存的 CacheKey 对象
private Set<Object> entriesMissedInCache;
public TransactionalCache(Cache delegate) {
this.delegate = delegate;
this.clearOnCommit = false;
this.entriesToAddOnCommit = new HashMap<Object, Object>();
this.entriesMissedInCache = new HashSet<Object>();
}
// 添加缓存数据的时候,先暂时放到 entriesToAddOnCommit 集合中,在事务提交的时候,再把数据放入到二级缓存中,避免脏数据
@Override
public void putObject(Object key, Object object) {
entriesToAddOnCommit.put(key, object);
}
// 提交事务,
public void commit() {
if (clearOnCommit) {
delegate.clear();
}
// 把 entriesToAddOnCommit 集合中的数据放入到二级缓存中
flushPendingEntries();
reset();
}
// 把 entriesToAddOnCommit 集合中的数据放入到二级缓存中
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);
}
}
}
// 事务回滚
public void rollback() {
// 把未命中缓存的数据清除掉
unlockMissedEntries();
reset();
}
private void unlockMissedEntries() {
for (Object entry : entriesMissedInCache) {
delegate.removeObject(entry);
}
}
4.2、TransactionalCacheManager
TransactionalCacheManager 用于管理 CachingExecutor 使用的二级缓存:
public class TransactionalCacheManager {
//用来管理 CachingExecutor 使用的二级缓存
// key 为对应的CachingExecutor 使用的二级缓存
// value 为对应的 TransactionalCache 对象
private Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>();
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();
}
}
// 所有的调用都会调用 TransactionalCache 的方法来实现
private TransactionalCache getTransactionalCache(Cache cache) {
TransactionalCache txCache = transactionalCaches.get(cache);
if (txCache == null) {
txCache = new TransactionalCache(cache);
transactionalCaches.put(cache, txCache);
}
return txCache;
}
}
4.3、CachingExecutor
接下来看下 二级缓存的实现 CachingExecutor :主要是通过 TransactionalCache 来操作二级缓存的。
public class CachingExecutor implements Executor {
// 底层的 Executor
private Executor delegate;
private TransactionalCacheManager tcm = new TransactionalCacheManager();
// 查询方法
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 获取 SQL
BoundSql boundSql = ms.getBoundSql(parameterObject);
// 创建缓存key,在CacheKey中已经分析过创建过程
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
// 查询
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
// 获取查询语句所在namespace对应的二级缓存
Cache cache = ms.getCache();
// 是否开启了二级缓存
if (cache != null) {
// 根据 <select> 的属性 useCache 的配置,决定是否需要清空二级缓存
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
// 二级缓存不能保存输出参数,否则抛异常
ensureNoOutParams(ms, parameterObject, boundSql);
// 从二级缓存中查询对应的值
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
// 如果二级缓存没有命中,则调用底层的 Executor 查询,其中会先查询一级缓存,一级缓存也未命中,才会去查询数据库
list = delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// 查询到的数据放入到二级缓存中去
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
// 如果没有开启二级缓存,则直接调用底层的 Executor 查询,还是会先查一级缓存
return delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
5、缓存的装饰器
Cache 接口由很多的装饰器类,共 10 个,添加了不同的功能,如下所示:后面列了几个,具体详情请参考源码
5.1、SynchronizedCache
- 线程安全的缓存,使用关键字 synchronized 来保证线程安全
public class SynchronizedCache implements Cache {
private Cache delegate;
public SynchronizedCache(Cache delegate) {
this.delegate = delegate;
}
@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);
}
// ............
}
5.2、LruCache
- 最近最少使用缓存:最近使用最少的缓存优先删除
package com.luo.ibatis.cache.decorators;
import com.luo.ibatis.cache.Cache;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
/**
* @author :archer
* @date :Created in 2021/6/30 21:01
* @description:
* Lru (least recently used) cache decorator
* 最近最少使用缓存
* 最近没怎么使用的的缓存优先删除
*/
public class LruCache implements Cache {
private final Cache delegate;
private Map<Object, Object> keyMap;
private Object eldestKey;
public LruCache(Cache delegate) {
this.delegate = delegate;
setSize(1024);
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
return delegate.getSize();
}
public void setSize(final int size) {
keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
private static final long serialVersionUID = 4267176411845948333L;
@Override
protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
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();
}
@Override
public ReadWriteLock getReadWriteLock() {
return null;
}
private void cycleKeyList(Object key) {
keyMap.put(key, key);
if (eldestKey != null) {
delegate.removeObject(eldestKey);
eldestKey = null;
}
}
}
5.3、FifoCache
- 先进先出 先添加的缓存优先删除
package com.luo.ibatis.cache.decorators;
import com.luo.ibatis.cache.Cache;
import java.util.Deque;
import java.util.LinkedList;
import java.util.concurrent.locks.ReadWriteLock;
/**
* @author :archer
* @date :Created in 2021/6/30 21:23
* @description: 先进先出 先添加的缓存优先删除
* FIFO (first in, first out) cache decorator
*/
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<Object>();
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) {
cycleKeyList(key);
delegate.putObject(key, value);
}
@Override
public Object getObject(Object key) {
return delegate.getObject(key);
}
@Override
public Object removeObject(Object key) {
return delegate.removeObject(key);
}
@Override
public void clear() {
delegate.clear();
keyList.clear();
}
@Override
public ReadWriteLock getReadWriteLock() {
return null;
}
private void cycleKeyList(Object key) {
keyList.addLast(key);
if (keyList.size() > size) {
Object oldestKey = keyList.removeFirst();
delegate.removeObject(oldestKey);
}
}
}
5.4、SoftCache
- 软引用缓存:jvm 堆内存不够时,自动删除
- 软引用:用来描述一些还有用但并非必须的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。
package com.luo.ibatis.cache.decorators;
import com.luo.ibatis.cache.Cache;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.Deque;
import java.util.LinkedList;
import java.util.concurrent.locks.ReadWriteLock;
/**
* @author :archer
* @date :Created in 2021/6/30 21:27
* @description:软引用缓存装饰器
* Soft Reference cache decorator
* Thanks to Dr. Heinz Kabutz for his guidance(指导) here.
*/
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<Object>();
this.queueOfGarbageCollectedEntries = new ReferenceQueue<Object>();
}
@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") // assumed delegate cache is totally managed by this cache
SoftReference<Object> softReference = (SoftReference<Object>) delegate.getObject(key);
if (softReference != null) {
result = softReference.get();
if (result == null) {
delegate.removeObject(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;
}
@Override
public Object removeObject(Object key) {
removeGarbageCollectedItems();
return delegate.removeObject(key);
}
@Override
public void clear() {
synchronized (hardLinksToAvoidGarbageCollection) {
hardLinksToAvoidGarbageCollection.clear();
}
removeGarbageCollectedItems();
delegate.clear();
}
@Override
public ReadWriteLock getReadWriteLock() {
return null;
}
private void removeGarbageCollectedItems() {
SoftEntry sv;
while ((sv = (SoftEntry) queueOfGarbageCollectedEntries.poll()) != null) {
delegate.removeObject(sv.key);
}
}
private static class SoftEntry extends SoftReference<Object> {
private final Object key;
SoftEntry(Object key, Object value, ReferenceQueue<Object> garbageCollectionQueue) {
super(value, garbageCollectionQueue);
this.key = key;
}
}
}
5.5、WeakCache
- 弱引用缓存
- 弱引用:用来描述非必须对象的,他的强度比软引用更弱一些,被弱引用关联的对象,在垃圾回收时,如果这个对象只被弱引用关联(没有任何强引用关联他),那么这个对象就会被回收。
package com.luo.ibatis.cache.decorators;
import com.luo.ibatis.cache.Cache;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.Deque;
import java.util.LinkedList;
import java.util.concurrent.locks.ReadWriteLock;
/**
* @author :archer
* @date :Created in 2021/6/30 22:02
* @description: 软引用缓存装饰器
* Weak Reference cache decorator.
* Thanks to Dr. Heinz Kabutz for his guidance here.
*/
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<Object>();
this.queueOfGarbageCollectedEntries = new ReferenceQueue<Object>();
}
@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") // 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) {
delegate.removeObject(key);
} else {
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() {
hardLinksToAvoidGarbageCollection.clear();
removeGarbageCollectedItems();
delegate.clear();
}
@Override
public ReadWriteLock getReadWriteLock() {
return null;
}
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;
}
}
}
5.6、ScheduledCache
- 超时自动失效的缓存:自动失效的时间点不是时间到了,而是下次查询缓存时,如果判断时间过期才会失效,所以并不是真正的超时自动失效的缓存
package com.luo.ibatis.cache.decorators;
import com.luo.ibatis.cache.Cache;
import java.util.concurrent.locks.ReadWriteLock;
/**
* @author :archer
* @date :Created in 2021/6/30 21:26
* @description:
*/
public class ScheduledCache implements 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 ReadWriteLock getReadWriteLock() {
return null;
}
@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;
}
}
5.7、BlockingCache
- 阻塞缓存:当缓存不存在阻塞,直到有缓存才返回
package com.luo.ibatis.cache.decorators;
import com.luo.ibatis.cache.Cache;
import com.luo.ibatis.cache.CacheException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author :archer
* @date :Created in 2021/6/30 21:22
* @description: 简单阻塞,缓存中找不到会阻塞其他线程(效率低下)
* Simple blocking decorator
*
* Simple and inefficient version of EhCache's BlockingCache decorator.
* It sets a lock over a cache key when the element is not found in cache.
* This way, other threads will wait until this element is filled instead of hitting the database.
*/
public class BlockingCache implements Cache {
private long timeout;
private final Cache delegate;
private final ConcurrentHashMap<Object, ReentrantLock> locks;
public BlockingCache(Cache delegate) {
this.delegate = delegate;
this.locks = new ConcurrentHashMap<Object, ReentrantLock>();
}
@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();
}
@Override
public ReadWriteLock getReadWriteLock() {
return null;
}
private ReentrantLock getLockForKey(Object key) {
ReentrantLock lock = new ReentrantLock();
ReentrantLock previous = locks.putIfAbsent(key, lock);
return previous == null ? lock : previous;
}
private void acquireLock(Object key) {
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 lock = locks.get(key);
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
public long getTimeout() {
return timeout;
}
public void setTimeout(long timeout) {
this.timeout = timeout;
}
}