转载

Mybatis中的二级缓存原理


首先需要说明一些在二级缓存中的基本概念

1、缓存空间:底层就是一个HashMap,开启缓存的Mapper就有一个独立的缓存空间,本质上就是Cache责任链,所有会话共享,在进行mybatis解析时就生成了

2、暂存区(TransactionalCache):暂时存放一些未命中的记录(不确定),通过commit会更新缓存空间,每个会话有独立的暂存区

3、暂存区管理器(TransactionalCacheManager):底层是一个HashMap<Cache, TransactionalCache>,主要负责暂存区和缓存空间的映射

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

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

Mybatis

评论