Mybatis中的二级缓存原理

一、执行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);
  }

关于二级缓存的知识大概就这些了,细节分析还需要继续学习,等待持续更新

上一篇:swift5学习之旅之代理通知block的使用


下一篇:C#委托及事件