缓存实现
Mybatis的缓存实现应用了装饰器模式,基础缓存为PerpetualCache
,其余的缓存类都在装饰PerpetualCache
来实现不同的功能,下面图是部分实现
1. PerpetualCache
这个缓存比较简单,使用Map来达到缓存的目的。下边是他的源码,比较简单,只贴出了部分
public class PerpetualCache implements Cache {
// 缓存id
private final String id;
// 存储缓存内容
private final Map<Object, Object> cache = new HashMap<>();
}
2. 装饰器
2.1 FifoCache
先进先出式缓存,在PerpetualCache
的基础上添加了一个队列集合来实现
插入缓存时:
- 将缓存的key放入到keyList中,检查keyList的长度是否越界(越界移除掉队列头元素,并移除delegate中的kv)
- 将kv插入到delegate中
public class FifoCache implements Cache {
// PerpetualCache
private final Cache delegate;
// 存放delegate中的key集合实现先进先出
private final Deque<Object> keyList;
// keyList的大小
private int size;
public FifoCache(Cache delegate) {
this.delegate = delegate;
// 用LinkedList做队列使用
this.keyList = new LinkedList<>();
// 设置keyList的最大值为1024
this.size = 1024;
}
// 添加缓存
public void putObject(Object key, Object value) {
cycleKeyList(key);
delegate.putObject(key, value);
}
// 对key的操作
private void cycleKeyList(Object key) {
// 添加到队列尾
keyList.addLast(key);
if (keyList.size() > size) {
// 若keyList的size超过最大容量时,移除队列头元素
Object oldestKey = keyList.removeFirst();
// 移除delegate中的key
delegate.removeObject(oldestKey);
}
}
}
2.2 LruCache
最近最少使用缓存,通过重写LinkedHashMap
的removeEldestEntry()
来实现
public class LruCache implements Cache {
// PerpetualCache
private final Cache delegate;
// 用于维护最近最少使用key
private Map<Object, Object> keyMap;
// 需要移除的key(也就是最近最少使用的key)
private Object eldestKey;
public LruCache(Cache delegate) {
this.delegate = delegate;
setSize(1024);
}
public void setSize(final int size) {
// 维护LinkedHashMap的访问顺序,这里弄懂需要跟一下LinkedListMap的put源码
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) {
// 当长度越界后,返回LinkedList的头结点的key
eldestKey = eldest.getKey();
}
return tooBig;
}
};
}
public void putObject(Object key, Object value) {
delegate.putObject(key, value);
cycleKeyList(key);
}
private void cycleKeyList(Object key) {
// 将key存入keyMap
keyMap.put(key, key);
if (eldestKey != null) {
// delegate中移除eldestKey并将eldestKey置null
delegate.removeObject(eldestKey);
eldestKey = null;
}
}
}
2.3 BlockingCache
阻塞式缓存,使用ReentrantLock
来实现的,同一时刻只有一个线程可以key的缓存
线程A访问缓存时,
- 通过**getObject()**方法加锁,当缓存命中,返回值不为null(释放锁)
- 当缓存未命中,返回null(不释放锁),mybatis查询数据库,将结果放入缓存(putObject()方法),此时释放锁
线程A操作缓存时,线程B进入获取和A相同的key时,只能等待
public class BlockingCache implements Cache {
// 超时时间
private long timeout;
// PerpetualCache
private final Cache delegate;
// 存储锁
private final ConcurrentHashMap<Object, ReentrantLock> locks;
public BlockingCache(Cache delegate) {
this.delegate = delegate;
this.locks = new ConcurrentHashMap<>();
}
public String getId() {
return delegate.getId();
}
public int getSize() {
return delegate.getSize();
}
public void putObject(Object key, Object value) {
try {
// 查询缓存
delegate.putObject(key, value);
} finally {
// 释放锁
releaseLock(key);
}
}
public Object getObject(Object key) {
// 尝试加锁
acquireLock(key);
Object value = delegate.getObject(key);
if (value != null) {
// value不为null时才会释放锁
releaseLock(key);
}
return value;
}
public Object removeObject(Object key) {
// despite of its name, this method is called only to release locks
releaseLock(key);
return null;
}
public void clear() {
delegate.clear();
}
private ReentrantLock getLockForKey(Object key) {
// 获取锁对象
return locks.computeIfAbsent(key, k -> new ReentrantLock());
}
private void acquireLock(Object key) {
Lock lock = getLockForKey(key);
if (timeout > 0) {
try {
// 在timeout时间内尝试加锁
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;}
}
CacheKey
CacheKey主要用来充当Mybatis一二级缓存(PerpetualCache
存储)的key,value肯定为数据库的返回结果,这点毋容置疑。那么key的选择会麻烦一些,需要考虑到方法id、参数列表、是否分页、执行的SQL等因素。所以有了CacheKey
public class CacheKey implements Cloneable, Serializable {
private static final long serialVersionUID = 1146682552656046210L;
public static final CacheKey NULL_CACHE_KEY = new CacheKey() {
public void update(Object object) {
throw new CacheException("Not allowed to update a null cache key instance.");
}
public void updateAll(Object[] objects) {
throw new CacheException("Not allowed to update a null cache key instance.");
}
};
// 默认乘子
private static final int DEFAULT_MULTIPLIER = 37;
// 默认hash
private static final int DEFAULT_HASHCODE = 17;
// 实际乘子
private final int multiplier;
// 实际hash
private int hashcode;
// 校验和
private long checksum;
// 影响因子个数
private int count;
// 影响因子(像方法id,参数列表这些都是影响因子)
private List<Object> updateList;
public CacheKey() {
this.hashcode = 17;
this.multiplier = 37;
this.count = 0;
this.updateList = new ArrayList();
}
public CacheKey(Object[] objects) {
this();
this.updateAll(objects);
}
// 获取影响因子个数
public int getUpdateCount() {
return this.updateList.size();
}
// 更新操作(目的是为了hashcode更随机,因为这是需要作为map的key的)
public void update(Object object) {
// 获取基础hashcode
int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);
// count自增1
++this.count;
// 校验和更新
this.checksum += (long)baseHashCode;
// 基础hash更新
baseHashCode *= this.count;
// 实际hash更新
this.hashcode = this.multiplier * this.hashcode + baseHashCode;
// 添加影响因子到列表
this.updateList.add(object);
}
// 将objects逐一放入到udateList中
public void updateAll(Object[] objects) {
Object[] var2 = objects;
int var3 = objects.length;
for(int var4 = 0; var4 < var3; ++var4) {
Object o = var2[var4];
this.update(o);
}
}
// 因为要作为缓存的key,所以重写了hash和equals
public boolean equals(Object object) {
if (this == object) {
// 比较内存地址
return true;
} else if (!(object instanceof CacheKey)) {
// 比较类型
return false;
} else {
CacheKey cacheKey = (CacheKey)object;
if (this.hashcode != cacheKey.hashcode) {
// 比较hash
return false;
} else if (this.checksum != cacheKey.checksum) {
// 比较校验和
return false;
} else if (this.count != cacheKey.count) {
// 比较影响因子个数
return false;
} else {
// 比较每个影响因子
for(int i = 0; i < this.updateList.size(); ++i) {
Object thisObject = this.updateList.get(i);
Object thatObject = cacheKey.updateList.get(i);
if (!ArrayUtil.equals(thisObject, thatObject)) {
return false;
}
}
return true;
}
}
}
public int hashCode() {
return this.hashcode;
}
}
一级缓存
Mybatis的一级缓存是使用PerpetualCache
来做的,key为CacheKey,value为数据库返回的结果集。会在INSERT|UPDATE|DELETE、事务提交、回滚时清空
public abstract class BaseExecutor implements Executor {
// 一级缓存
protected PerpetualCache localCache;
// 查询方法
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 获取绑定SQL
BoundSql boundSql = ms.getBoundSql(parameter);
// 获取cacheKey
CacheKey key = this.createCacheKey(ms, parameter, rowBounds, boundSql);
// 调用重载,真正逻辑
return this.query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
// 返回cacheKey对象
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (this.closed) {
throw new ExecutorException("Executor was closed.");
} else {
CacheKey cacheKey = new CacheKey();
// 传入ms的id:com.xyn.mapper.Rolemapper.getRole
cacheKey.update(ms.getId());
// 分页参数
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
// sql
cacheKey.update(boundSql.getSql());
// 参数列表
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
Iterator var8 = parameterMappings.iterator();
while(var8.hasNext()) {
ParameterMapping parameterMapping = (ParameterMapping)var8.next();
if (parameterMapping.getMode() != ParameterMode.OUT) {
String propertyName = parameterMapping.getProperty();
Object value;
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = this.configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
// 传入运行时参数:#{xx}
cacheKey.update(value);
}
}
if (this.configuration.getEnvironment() != null) {
// 存在环境变量时传入环境变量
cacheKey.update(this.configuration.getEnvironment().getId());
}
return cacheKey;
}
}
// 真正的查询业务逻辑
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 (this.closed) {
throw new ExecutorException("Executor was closed.");
} else {
// 若查询栈深度为1 且 ms是否清空缓存(根据ms类型决定,select类型为false,其余为true)
if (this.queryStack == 0 && ms.isFlushCacheRequired()) {
// 清空一级缓存
this.clearLocalCache();
}
List list;
try {
// 栈深度自增
++this.queryStack;
// 从一级缓存中取,若为null,则去数据库查询
list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
if (list != null) {
// 存储过程逻辑
this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 从数据库中取
list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
// 栈深度自减
--this.queryStack;
}
if (this.queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
// 检查本地缓存使用范围,若为(STATEMENT)则清空localCache
if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
this.clearLocalCache();
}
}
return list;
}
}
// 从数据库查询
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
// TODO 插入占位符 没搞太懂这个操作
this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);
List list;
try {
list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
// 移除占位符
this.localCache.removeObject(key);
}
// 查询结果放入到localCache中
this.localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
// 若为存储过程,放入到localOutputParameterCache中
this.localOutputParameterCache.putObject(key, parameter);
}
return list;
}
二级缓存
TODO