首先需要说明一些在二级缓存中的基本概念
1、缓存空间:底层就是一个HashMap,开启缓存的Mapper就有一个独立的缓存空间,本质上就是Cache责任链,所有会话共享,在进行mybatis解析时就生成了
2、暂存区(TransactionalCache):暂时存放一些未命中的记录(不确定),通过commit会更新缓存空间,每个会话有独立的暂存区
3、暂存区管理器(TransactionalCacheManager):底层是一个HashMap<Cache, TransactionalCache>,主要负责暂存区和缓存空间的映射
CachingExecutor实现了Executor接口,主要负责二级缓存的存取操作,并通过委派模式(Delegate)让具体的BaseExecutor集成类执行cruid操作。
1 private final Executor delegate; // 具体委派执行增删改查操作的执行器
2 private final TransactionalCacheManager tcm = new TransactionalCacheManager(); // 暂存区管理,主要负责存放暂存区
3
4 public CachingExecutor(Executor delegate) {
5 this.delegate = delegate;
6 delegate.setExecutorWrapper(this);
7 }
通过责任链和委派模式分别实现Cache接口的同步(synchronized)、记录命中(logging)、溢出策略(LRU)、定期清理策略(scheduled)、防穿透(block)、内存存储(perpetual)等功能。具体实现类的功能在后面写。
1、在SqlSessionFactory的openSession中有说明CachingExecutor的定义
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); // 此处就是给session配置执行器的方法 // 参数说明: // tx: 事务管理器,执行器获取连接都是通过tx获取的,因此可以实现Mybatis的事务操作 // execType:执行器的具体类型:Simple Reuse Batch
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
} // 关于CachingExecutor的定义:如果开启了二级缓存逻辑,就返回一个CachingExecutor // 一旦使用了该执行器,就会走二级缓存(如果声明了二级缓存空间!!)
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
2、CachingExecutor的query方法中的二级缓存代码
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException { // 每个Mapper如果打开了缓存(xml配置或者注解),都有一个自己的缓存空间 // 可以通过MappedStatement获取每个ms的缓存空间
Cache cache = ms.getCache();
if (cache != null) { // 如果配置了flushCache,就会去清理暂存区(注意,在未提交前,都只是处理暂存区),清理后留下一个标记 // 该标记后面会提到
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked") // 每个会话session对应一个独立的暂存区管理器,也就是tcm // tcm的作用就是存放暂存区,本质是一个HashMap<Cache, TransactionalCache> // 一个暂存区对应一个缓存空间,所以以ms的缓存空间为键,可以获得对应的暂存区
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
3、关于暂存区的创建,如果当前session不存在对应的暂存区,就需要被创建,创建是在tcm.getObject中进行的
public Object getObject(Cache cache, CacheKey key) {
return getTransactionalCache(cache).getObject(key);
}
private TransactionalCache getTransactionalCache(Cache cache) {
// transactionalCaches就是缓存空间与暂存区映射的HashMap存放地
// 通过Map.computeIfAbsent方法实例化一个暂存区TransactionalCache
return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
}
default V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction) {
Objects.requireNonNull(mappingFunction);
V v;
// 如果对应Key有值,则直接返回
if ((v = get(key)) == null) {
V newValue;
// 否则就以key为参数实例化一个对应的value
// 对应暂存区而言,也就是 new TransactionalCache(cache)
if ((newValue = mappingFunction.apply(key)) != null) {
put(key, newValue);
return newValue;
}
}
return v;
}
4、通过暂存区对应的Cache缓存责任链获取二级缓存空间中的记录
public Object getObject(Object key) {
// issue #116 // 这里的delegate就是典型的委托者
Object object = delegate.getObject(key);
if (object == null) {
entriesMissedInCache.add(key);
}
// issue #146 // 这里就对应之前提到的清理暂存区的标记符号 clearOnCommit,如果暂存区被清理了,那么无法获得缓存空间中的记录 // 且最终通过commit会对缓存空间进行处理 // 这里的标记相当巧妙,保证了数据在未提交之前的一致性
if (clearOnCommit) {
return null;
} else {
return object;
}
}
Cache责任链是非常具有代表性的处理缓存的方式,值得学习
1、负责线程安全的同步缓存SynchronizedCache,主要在Cache的实现方法前加了Synchronized关键字
@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();
}
2、记录命中率的LoggingCache,核心代码主要在于getObject时统计命中率
public Object getObject(Object key) {// requests是总的请求数
requests++;
final Object value = delegate.getObject(key);
if (value != null) {// 命中则hits++
hits++;
}
if (log.isDebugEnabled()) {
log.debug("Cache Hit Ratio [" + getId() + "]: " + getHitRatio());
}
return value;
}// 计算命中率,没啥可说的private double getHitRatio() {
return (double) hits / (double) requests;
}
3、防止溢出的LruCache,通过一些策略,比如FIFO,LRU等来防止内存的溢出,这里就不看了,有时间可以再追一下
4、后面的过期清理等Cache都是差不多的字面意思
5、真正执行存取操作的PerpetualCache,具体的存取操作就是Map的基本操作
// cache是缓存空间中存储数据的数据结构
// 这里不需要线程安全的hashmap,因为synchronizedCache是安全的
private Map<Object, Object> cache = new HashMap<>();
public PerpetualCache(String id) {
this.id = id;
}
@Override
public String getId() {
return id;
}
@Override
public int getSize() {
return cache.size();
}
@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);
}
关于二级缓存的知识大概就这些了,细节分析还需要继续学习,等待持续更新
评论