一、执行mybatis二级缓存的核心类
1.1 CachingExecutor
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 }
1.2 Cache责任链
通过责任链和委派模式分别实现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责任链
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); }
关于二级缓存的知识大概就这些了,细节分析还需要继续学习,等待持续更新