MyBatis 缓存原理 - litter-fish/ReadSource GitHub Wiki
关系说明:
MyBatis 在实现缓存模块的过程中,使用了装饰模式。在以上几种缓存实现类中,PerpetualCache 相当于装饰模式中的 ConcreteComponent。LruCache、SynchronizedCache 和 BlockingCache 等相当于装饰模式中的 ConcreteDecorator。
PerpetualCache 是一个具有基本功能的缓存类,内部使用了 HashMap 实现缓存功能
public class PerpetualCache implements Cache {
public void putObject(Object key, Object value) {
// 存储键值对到 HashMap
cache.put(key, value);
}
public Object getObject(Object key) {
// 查找缓存项
return cache.get(key);
}
public Object removeObject(Object key) {
// 移除缓存项
return cache.remove(key);
}
}
LruCache,是一种具有 LRU 策略的缓存实现类, LruCache 的 keyMap 属性,该属性类型继承自 LinkedHashMap,并覆盖了 removeEldestEntry 方法。LinkedHashMap 可保持键值对的插入顺序,当插入一个新的键值对时,LinkedHashMap 内部的 tail 节点会指向最新插入的节点。head 节点则指向第一个被插入的键值对,也就是最久未被访问的那个键值对。
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);
}
public void setSize(final int size) {
/*
* 初始化 keyMap,注意,keyMap 的类型继承自 LinkedHashMap,
* 并覆盖了 removeEldestEntry 方法
*
* 构造方法将 LinkedHashMap 的 accessOrder 属性设为 true,此时 LinkedHashMap 会维护键值对的访问顺序。
* keyMap.get(key),刷新 key 对应的键值对在 LinkedHashMap 的位置,最少访问的放到队尾
*/
keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
private static final long serialVersionUID = 4267176411845948333L;
// 覆盖 LinkedHashMap 的 removeEldestEntry 方法
@Override
protected boolean removeEldestEntry(Map.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);
}
private void cycleKeyList(Object key) {
// 存储 key 到 keyMap 中
keyMap.put(key, key);
if (eldestKey != null) {
// 从被装饰类中移除相应的缓存项
delegate.removeObject(eldestKey);
eldestKey = null;
}
}
public Object getObject(Object key) {
// 刷新 key 在 keyMap 中的位置
keyMap.get(key); //touch
// 从被装饰类中获取相应缓存项
return delegate.getObject(key);
}
public Object removeObject(Object key) {
// 从被装饰类中移除相应的缓存项
return delegate.removeObject(key);
}
}
BlockingCache BlockingCache 实现了阻塞特性,该特性是基于 Java 重入锁实现的。同一时刻下,BlockingCache 仅允许一个线程访问指定 key 的缓存项,其他线程将会被阻塞住
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>();
}
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) {
// 释放锁
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;
}
private ReentrantLock getLockForKey(Object key) {
ReentrantLock lock = new ReentrantLock();
// 存储 <key, Lock> 键值对到 locks 中
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 class CacheKey implements Cloneable, Serializable {
private static final int DEFAULT_MULTIPLYER = 37;
private static final int DEFAULT_HASHCODE = 17;
// 乘子,默认为37
private final int multiplier;
// CacheKey 的 hashCode,综合了各种影响因子
private int hashcode;
// 校验和
private long checksum;
// 影响因子个数
private int count;
// 影响因子个数
private List<Object> updateList;
public CacheKey() {
this.hashcode = DEFAULT_HASHCODE;
this.multiplier = DEFAULT_MULTIPLYER;
this.count = 0;
this.updateList = new ArrayList<Object>();
}
/** 每当执行更新操作时,表示有新的影响因子参与计算 */
public void update(Object object) {
int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);
// 自增 count
count++;
// 计算校验和
checksum += baseHashCode;
// 更新 baseHashCode
baseHashCode *= count;
// 计算 hashCode
hashcode = multiplier * hashcode + baseHashCode;
// 保存影响因子
updateList.add(object);
}
public boolean equals(Object object) {
// 检测是否为同一个对象
if (this == object) {
return true;
}
// 检测 object 是否为 CacheKey
if (!(object instanceof CacheKey)) {
return false;
}
final CacheKey cacheKey = (CacheKey) object;
// 检测 hashCode 是否相等
if (hashcode != cacheKey.hashcode) {
return false;
}
// 检测校验和是否相同
if (checksum != cacheKey.checksum) {
return false;
}
// 检测 count 是否相同
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;
}
}
一级缓存在 BaseExecutor 中被初始化
public abstract class BaseExecutor implements Executor {
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 首先根据传递的参数获取BoundSql对象,对于不同类型的SqlSource,对应的getBoundSql实现不同
BoundSql boundSql = ms.getBoundSql(parameter);
// 创建缓存key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// 委托给重载的query
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
// 省略部分代码
List<E> list;
try {
queryStack++;
// 从一级缓存中获取缓存项
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
// 只处理存储过程和函数调用的出参, 因为存储过程和函数的返回不是通过ResultMap而是ParameterMap来的,所以只要把缓存的非IN模式参数取出来设置到parameter对应的属性上即可
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 一级缓存未命中,则从数据库中查询
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
// 省略部分代码
return list;
}
}
通过MappedStatement 的 id 字段,SQL 语句,分页参数,运行时变量,Environment 的 id 字段等参与影响因子计算,可以区别不同查询请求
public abstract class BaseExecutor implements Executor {
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 创建 CacheKey 对象
CacheKey cacheKey = new CacheKey();
// 将 MappedStatement 的 id 作为影响因子进行计算
cacheKey.update(ms.getId());
// RowBounds 用于分页查询,下面将它的两个字段作为影响因子进行计算
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
// 获取 sql 语句,并进行计算
cacheKey.update(boundSql.getSql());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// mimic DefaultParameterHandler logic
for (ParameterMapping parameterMapping : parameterMappings) {
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
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 = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
cacheKey.update(value);
}
}
if (configuration.getEnvironment() != null) {
// 获取 Environment id 遍历,并让其参与计算
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
}
一级缓存未命中,查询数据库,并将结果写入一级缓存中
public abstract class BaseExecutor implements Executor {
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;
}
}
二级缓存构建在一级缓存上,当发起一个查询请求的时候 mybatis 会先去二级缓存中查找,如果未命中则继续在一级缓存中查找,如果还是未命中则查询数据库 一级缓存与 SqlSession 绑定,二级缓存与 Mapper 绑定。 二级缓存可以在多个命名空间中共享。这种情况下,会存在并发问题,因此需要针对性去处理。除了并发问题,二级缓存还存在事务问题
MappedStatement 存在于全局配置中,可以多个 CachingExecutor 获取到,这样就会出现线程安全问题,可以通过 SynchronizedCache 装饰类解决,该装饰类会在 Cache 实例构造期间被添加上 多个事务共用一个缓存实例,会导致脏读问题,使用 TransactionalCache 处理
二级缓存相关类图
访问二级缓存
public class CachingExecutor implements Executor {
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 获取 BoundSql
BoundSql boundSql = ms.getBoundSql(parameterObject);
// 创建 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 {
// 从 MappedStatement 中获取 Cache,注意这里的 Cache 并非是在 CachingExecutor 中创建的
Cache cache = ms.getCache();
// 若映射文件中未配置缓存或参照缓存,此时 cache = null
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
// 如果二级缓存中找到了记录就直接返回,否则到DB查询后进行缓存
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
// 若缓存未命中,则调用被装饰类的 query 方法
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
// 调用被装饰类的 query 方法
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
}
事务缓存管理器的创建
/**事务缓存管理器 管理二级缓存对象Cache和TransactionCache */
public class TransactionalCacheManager {
// Cache 与 TransactionalCache 的映射关系表
private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>();
}
获取二级缓存
public class TransactionalCacheManager {
public Object getObject(Cache cache, CacheKey key) {
return getTransactionalCache(cache).getObject(key);
}
// 缓存装饰器,可以为 Cache 实例增加事务功能
private TransactionalCache getTransactionalCache(Cache cache) {
// 从映射表中获取 TransactionalCache
TransactionalCache txCache = transactionalCaches.get(cache);
if (txCache == null) {
// TransactionalCache 也是一种装饰类,为 Cache 增加事务功能
txCache = new TransactionalCache(cache);
transactionalCaches.put(cache, txCache);
}
return txCache;
}
}
创建缓存装饰器 TransactionalCache
public class TransactionalCache implements Cache {
private final Cache delegate;
private boolean clearOnCommit;
// 在事务被提交前,所有从数据库中查询的结果将缓存在此集合中
private final Map<Object, Object> entriesToAddOnCommit;
// 在事务被提交前,当缓存未命中时,CacheKey 将会被存储在此集合中
private final Set<Object> entriesMissedInCache;
public TransactionalCache(Cache delegate) {
this.delegate = delegate;
this.clearOnCommit = false;
this.entriesToAddOnCommit = new HashMap<Object, Object>();
this.entriesMissedInCache = new HashSet<Object>();
}
// 省略其他代码
}
缓存装饰器 TransactionalCache 中获取对象
public class TransactionalCache implements Cache {
// 省略其他代码
public Object getObject(Object key) {
// issue #116
// 用到了装饰器模式,从PerpetualCache中取出数据
Object object = delegate.getObject(key);
if (object == null) {
// 缓存未命中,则将 key 存入到 entriesMissedInCache 中
entriesMissedInCache.add(key);
}
// issue #146
if (clearOnCommit) {
return null;
} else {
return object;
}
}
// 省略其他代码
}
缓存查询结果,此步骤只会将需要缓存的数据暂存在 entriesToAddOnCommit 中当执行事务提交及 commit 的时候才会真正将缓存写入二级缓存对象中
public class TransactionalCacheManager {
public void putObject(Cache cache, CacheKey key, Object value) {
getTransactionalCache(cache).putObject(key, value);
}
}
public class TransactionalCache implements Cache {
// 省略其他代码
public void putObject(Object key, Object object) {
// 将键值对存入到 entriesToAddOnCommit 中,而非 delegate 缓存中
entriesToAddOnCommit.put(key, object);
}
// 省略其他代码
}
public class SqlSessionManager implements SqlSessionFactory, SqlSession {
private class SqlSessionInterceptor implements InvocationHandler {
public SqlSessionInterceptor() {
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
if (sqlSession != null) {
try {
// 执行select、update、delete等
return method.invoke(sqlSession, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
} else {
final SqlSession autoSqlSession = openSession();
try {
final Object result = method.invoke(autoSqlSession, args);
// 事务提交
autoSqlSession.commit();
return result;
} catch (Throwable t) {
autoSqlSession.rollback();
throw ExceptionUtil.unwrapThrowable(t);
} finally {
autoSqlSession.close();
}
}
}
}
}
执行事务提交时将 TransactionalCache 中暂存的缓存对象存入二级缓存中
public class TransactionalCache implements Cache {
// 省略其他代码
public void commit() {
// 根据 clearOnCommit 的值决定是否清空 delegate
if (clearOnCommit) {
delegate.clear();
}
// 刷新未缓存的结果到 delegate 缓存中
flushPendingEntries();
// 重置 entriesToAddOnCommit 和 entriesMissedInCache
reset();
}
private void flushPendingEntries() {
// 把之前存放到 entriesToAddOnCommit 中的数据提交到二级缓存中,具体的说是存放到PerpetualCache类的一个HashMap中
for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
// 将 entriesToAddOnCommit 中的内容转存到 delegate 中
delegate.putObject(entry.getKey(), entry.getValue());
}
for (Object entry : entriesMissedInCache) {
if (!entriesToAddOnCommit.containsKey(entry)) {
// 存入空值
delegate.putObject(entry, null);
}
}
}
private void reset() {
clearOnCommit = false;
// 清空集合
entriesToAddOnCommit.clear();
entriesMissedInCache.clear();
}
public void clear() {
clearOnCommit = true;
// 清空 entriesToAddOnCommit,但不清空 delegate 缓存
entriesToAddOnCommit.clear();
}
// 省略其他代码
}
事务回滚
public class TransactionalCacheManager {
public void rollback() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.rollback();
}
}
}
public class TransactionalCache implements Cache {
// 省略其他代码
public void rollback() {
unlockMissedEntries();
reset();
}
private void unlockMissedEntries() {
for (Object entry : entriesMissedInCache) {
try {
// 调用 removeObject 进行解锁
delegate.removeObject(entry);
} catch (Exception e) {
log.warn("Unexpected exception while notifiying a rollback to the cache adapter."
+ "Consider upgrading your cache adapter to the latest version. Cause: " + e);
}
}
}
private void reset() {
clearOnCommit = false;
// 清空集合
entriesToAddOnCommit.clear();
entriesMissedInCache.clear();
}
// 省略其他代码
}