《Mybatis源码》第11章 Cache缓存

一、基础使用

1.概述

MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。缓存可以极大的提升查询效率,当开启了缓存以后,数据库的查询流程是二级缓存 --> 一级缓存 --> 数据库

Mybatis系统中默认定义了两级缓存

  1. 一级缓存:默认情况下,只有一级缓存开启
  2. 二级缓存:二级缓存需要手动开启和配置,他是基于namespace级别的缓存
  3. 为了提高扩展性,Mybatis定义了缓存接口cache,我们可以通过实现Cache接口来自定义二级缓存

2.一级缓存

1)基础

一级缓存为本地缓存,作用域默认为sqlSession,当Session flush或close后,该Session中的所有Cache将被清空

本地缓存不能关闭,但可以调用clearCache()来清空本地缓存,或者改变缓存的作用域

2)缓存失效

  1. 不同的SqlSession对应不同的一级缓存
  2. 同一个SqlSession但是查询条件不同
  3. 同一个SqlSession两次查询期间执行了任何一次增删改操作
  4. 同一个SqlSession两次查询期间手动清空了缓存

3.二级缓存

1)基础

  • 二级缓存,全局作用域缓存
  • 二级缓存默认是开启的,类似于一个总阀门,但是每个映射文件的二级缓存,需要手动配置,类似于你要使用就单独开启
  • Mybatis提供二级缓存的接口以及实现,缓存实现要求POJO实现Serializable接口
  • 二级缓存在SqlSession关闭或提交之后才会生效
  • 二级缓存是事务性的

2)二级缓存配置表

名称作用域描述
cacheEnabled全局配置开启二级缓存,默认开始
useCache<select|update|insert|delete>指定的statement是否开启,默认开启
flushCache<select|update|insert|delete>执行SQL前是否清空当前二级缓存空间,update默认true,query默认false
或 @CacheNamespace声明缓存空间
或 @CacheNamespaceRef引用缓存空间

3)操作步骤

  1. 全局配置文件中开启二级缓存

    // 默认开启的
    <setting name="cacheEnabled" value="true" />
    
  2. 注意:POJO需要实现Serializable接口

  3. 映射文件配置二级缓存

    <cache />
    

    通过这样一个简单的配置,这个简单语句的效果如下:

  • 映射语句文件中的所有select语句的结果在commit之后将会保存到缓存区
  • 映射语句文件中的所有insert、update和delete语句在commit之会刷新缓存
  • 缓存会使用最近最少使用算法(LRU)算法来清除不需要的缓存
  • 缓存不会定时进行刷新
  • 缓存会保存列表或对象的1024个引用
  • 缓存会被是为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改

4)cache标签

<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>

cache标签可以设置的属性为:

属性描述
eviction缓存回收策略,默认LRU,最近最少使用的被清除
flushInterval(刷新间隔),默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新
size(引用数目)代表缓存最多可以存储多少个对象,太大容易导致内存溢出。默认值是 1024。
readOnly(只读)true:只读缓存;会给所有调用者返回缓存对象的相同实例。false:读写缓存;会返回缓存对象的拷贝。这会慢一些,但是安全,因此默认是 false。

二、缓存实现原理

1.一级缓存分析

实现原理

我们通过执行器Executor来执行查询,这个时候如果三个子类执行器都添加缓存代码,就会造成代码重复,这个时候我们直接将一级缓存的逻辑添加到BaseExecutor中,这样就可以共用了,在BaseExecutor执行器的构造方法中,我们创建了2个PerpetualCache,其意思为永久缓存,用来实现一级缓存

所有执行器都必须经过BaseExecutor,这体现了一级缓存是默认开启的

  protected BaseExecutor(Configuration configuration, Transaction transaction) {
    this.transaction = transaction;
    this.deferredLoads = new ConcurrentLinkedQueue<DeferredLoad>();
    // 创建一级缓存
    this.localCache = new PerpetualCache("LocalCache");
    this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
    this.closed = false;
    this.configuration = configuration;
    this.wrapper = this;
  }

创建的类型为PerpetualCache,在其内部有1个HashMap,用它来实现一级缓存,我们看到Map的key/value都是Object类型,其实我们在存储的时候,key使用CacheKey缓存key,保持不重复性,value就是缓存的查询结果

Map的Key主要通过6种数据来保证不重复性:

  1. 会话ID
  2. 分页起始页
  3. 分页结束页
  4. SQL语句
  5. 参数
  6. 环境 例如: dev
public class PerpetualCache implements Cache {
  
  // 一级缓存是会话级别的 这个是缓存的唯一id
  private final String id;

  private Map<Object, Object> cache = new HashMap<Object, Object>();

  public PerpetualCache(String id) {
    this.id = id;
  }
  
  // 省略一些代码...
}

BaseExecutor 一级缓存执行器

BaseExecutor中的重要代码复制出来,关于一级缓存的我加了注释,大体思想就是缓存中获取直接返回,获取不到再去查询,然后存入一级缓存

@Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      // 尝试从缓存中获取
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        // 缓存中没有则查询数据库
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }


  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 {
      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;
  }

  protected Connection getConnection(Log statementLog) throws SQLException {
    Connection connection = transaction.getConnection();
    if (statementLog.isDebugEnabled()) {
      return ConnectionLogger.newInstance(connection, statementLog, queryStack);
    } else {
      return connection;
    }
  }

缓存失效

清除缓存通过clearLocalCache(),看到代码很简单,直接清空Map,那么哪里调用了这个方法,就代表会清空缓存

  @Override
  public void clearLocalCache() {
    if (!closed) {
      localCache.clear();
      localOutputParameterCache.clear();
    }
  }

情况1:修改会清空一级缓存

  @Override
  public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    // 清空一级缓存
    clearLocalCache();
    return doUpdate(ms, parameter);
  }

情况2:标签设置了每次查询清空缓存 && 作用域不是STATEMENT

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    // 不是嵌套查询 && 设置每次查询清空一级缓存
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      // 缓存作用域不是STATEMENT
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

情况3:提交/回滚 都会清空一级缓存

@Override
  public void commit(boolean required) throws SQLException {
    if (closed) {
      throw new ExecutorException("Cannot commit, transaction is already closed");
    }
    clearLocalCache();
    flushStatements();
    if (required) {
      transaction.commit();
    }
  }

  @Override
  public void rollback(boolean required) throws SQLException {
    if (!closed) {
      try {
        clearLocalCache();
        flushStatements(true);
      } finally {
        if (required) {
          transaction.rollback();
        }
      }
    }
  }

2.二级缓存分析

概述

二级缓存也称作是应用级缓存,与一级缓存不同的,是它的作用范围是整个映射文件,而且可以跨线程使用。所以二级缓存有更高的命中率,适合缓存一些修改较少的数据。在流程上是先访问二级缓存,在访问一级缓存。

图片名称

缓存需求

二级缓存是一个完整的缓存解决方案,那应该包含哪些功能呢?这里我们分为核心功能和非核心功能两类:

  1. 存储数据 【核心功能】
  2. 溢出淘汰 【核心功能】 例如: FIFO先进先出、LRU最近最少使用、WeakReference弱引用、SoftReference软件引用,
  3. 过期清理
  4. 线程安全
  5. 命中率统计
  6. 序列化

Cache 【缓存区】

public interface Cache {

  // 获取id
  String getId();

  void putObject(Object key, Object value);

  Object getObject(Object key);

  Object removeObject(Object key);
  
  void clear();
  // 获取缓存数量
  int getSize();
  
  ReadWriteLock getReadWriteLock();
}

装饰+责任链设计思想

像上面提到的,缓存需要实现这些功能,如何才能简单的实现,并保证它的灵活性与扩展性呢?这里MyBatis抽像出Cache接口,其只定义了缓存中最基本的功能方法:

  • 设置缓存
  • 获取缓存
  • 清除缓存
  • 获取缓存数量然后Mybatis提供了很多子类,分别继承Cache接口,不同的子类代表不同的功能,然后子类之间可以串联,这样就形成了责任链模式。在执行缓存的基本功能时,其它的缓存逻辑会沿着这个责任链依次往下传递。

这样设计有以下优点:

  1. 职责单一:各个节点只负责自己的逻辑,不需要关心其它节点。
  2. 扩展性强:可根据需要扩展节点、删除节点,还可以调换顺序保证灵活性。
  3. 松耦合:各节点之间不没强制依赖其它节点。而是通过顶层的Cache接口进行间接依赖。

测试代码:

	@Test
    public void m3(){
        try {
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLConfigBuilder builder = new XMLConfigBuilder(inputStream);
            Configuration configuration = builder.parse();
            Cache cache = configuration.getCache("com.jianan.springtest.dao.CarMapper");
            cache.putObject("hello","world");
            System.out.println(cache.getObject("hello"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

查看Cache的debug,发现Cache应用了多层嵌套,一层套一层,这就是责任链模式

缓存命中

二级缓存的命中场景与一级缓存类似,不同在于二级可以跨线程使用,还有就是二级缓存的更新,必须是在会话提交之后。

  1. 会话提交后

  2. Sql语句、参数相同

  3. 相同的StatementID

  4. RowBounds 相同

为什么要提交之后才能命中缓存

上面提到的4个二级缓存命中的条件,后三个很好理解,必须是相同的查询,才能使用缓存,那像第一个,必须commit之后才能缓存命中,是因为我们的二级缓存是跨线程的,所以必须提交之后才能命中缓存,因为如果产生回滚的话,会发生脏读的

二级缓存核心点

  1. 二级缓存是跨线程使用,所以缓存数据存在MappedStatement
  2. 因为操作是存在回滚的,所以我们的操作不能立刻同步到【缓存区】,需要先存储在【暂存区】
  3. 暂存区的实现就是TransactionalCache
  4. 一个会话可能存在多个暂存区,所以需要一个管理器,于是就存在了TransactionalCacheManager
  5. 所以: 提供了这些类都是为了实现二级缓存跨线程

二级缓存结构

在缓存命中的时候提到过,会话必须提交,才能更新二级缓存,那么Mybatis为了实现这一点,为每个会话设立了若干个暂存区,当前会话对指定缓存空间的变更,都存放在对应的暂存区,当会话提交之后才会提交到每个暂存区对应的缓存空间。为了统一管理这些暂存区,每个会话都一个唯一的事物缓存管理 器。所以这里暂存区也可叫做事物缓存。

上面简单介绍了会话、缓存管理器、暂存区之间的关系,那么通过下图继续了解一下

  1. 两个会话,每个会话存在唯一的事务缓存管理器
  2. 事务缓存管理器存在多个暂存区
  3. 暂存区提交之后会同步到缓存区,缓存区是共用的,这体现了跨线程的特点
  4. 当会话关闭以后,对应的都会消失,但是缓存区是不会消失的
  5. 比例关系为: 1个会话 : 1个事务缓存管理器 : n个暂存区

通过代码来看一下嵌套关系

二级缓存执行流程

原本会话是通过Executor实现SQL调用,这里基于装饰器模式使用CachingExecutor对SQL调用逻辑进行拦截。以嵌入二级缓存相关逻辑,也就是我们二级缓存的逻辑存在于CachingExecutor

关于这个执行流程描述几点:

  1. CachingExecutor拦截查询请求,处理二级缓存

  2. 查询和修改操作都会清空【暂存区】

  3. 查询操作时,如果【缓存区】存在直接返回,如果没有,查询完数据库,存入【暂存区】

  4. 当commit之后,会将【暂存区】的数据存入【缓存区】

CachingExecutor 二级缓存执行器

二级缓存会借助CachingExecutor类实现,它将三种基础执行器封装在类中,采用了装饰者模式,然后在其中进行二级缓存处理

public class CachingExecutor implements Executor {
    
  // 装饰的执行器
  private final Executor delegate;
  // 事务缓存管理器 这里只创建一个,代表一个会话,只有唯一的事务缓存管理器
  private final TransactionalCacheManager tcm = new TransactionalCacheManager();
    
  // 构造方法 将执行器按照参数传入,然后进行了装饰
  public CachingExecutor(Executor delegate) {
    this.delegate = delegate;
    delegate.setExecutorWrapper(this);
  }
  
  @Override
  public Transaction getTransaction() {
    return delegate.getTransaction();
  }
  // 关闭
  @Override
  public void close(boolean forceRollback) {
    try {
      // 底层都是调用的
      //issues #499, #524 and #573
      if (forceRollback) { 
        tcm.rollback();
      } else {
        tcm.commit();
      }
    } finally {
      delegate.close(forceRollback);
    }
  }

  @Override
  public boolean isClosed() {
    return delegate.isClosed();
  }
  // 增/删/改
  @Override
  public int update(MappedStatement ms, Object parameterObject) throws SQLException {
    // 刷新【暂存区】
    flushCacheIfRequired(ms);
    return delegate.update(ms, parameterObject);
  }
  
  // 查询过程
  // (1)获取 BoundSql 对象,创建查询语句对应的 CacheKey 对象
  // (2)检测是否开启了二级缓存,如果没有开启二级缓存,则直接调用底层 Executor 对象的 query() 方法查询数据库。如果开启了二级缓存,则继续后面的步骤
  // (3)检测查询操作是否包含输出类型的参数,如果是这种情况,则报错
  // (4)调用 TransactionalCacheManager.getObject()方法查询二级缓存,如果二级缓存中查找到相应的结果对象,则直接将该结果对象返回。
  // (5)如果二级缓存没有相应的结果对象,则调用底层 Executor 对象的 query() 方法,正如前面介绍的 ,它会先查询一级缓存,一级缓存未命中时,才会查询数库。   //     最后还会将得到的结果对象放入 TransactionalCache.entriesToAddOnCommit 集合中保存。
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 步骤1 创建BoundSql对象
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    // 创建缓存key
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

  @Override
  public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
    flushCacheIfRequired(ms);
    return delegate.queryCursor(ms, parameter, rowBounds);
  }

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    // 获取Cache 也就是我们的【缓存区】 从MappedStatement获取,正好体现了线程共用
    Cache cache = ms.getCache();
    // 步骤2 是否开启了二级缓存
    if (cache != null) {
      // 根据配置判断是否清空二级缓存
      flushCacheIfRequired(ms);
      // isUseCache() 判断是否使用二级缓存 对select元素为 true
      // resultHandler == null 也就是结果处理不能自定义 如果你自定义 可能和缓存中的结果不一样
      if (ms.isUseCache() && resultHandler == null) {
        // 步骤3 检测查询操作是否包含输出类型的参数
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        // 步骤4 【缓存区】中获取
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          // 步骤5 二级缓存没有 则继续查询
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          // 存入【暂存区】
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    // 这里代表没有开启二级缓存 直接查询数据库
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

  @Override
  public List<BatchResult> flushStatements() throws SQLException {
    return delegate.flushStatements();
  }

  @Override
  public void commit(boolean required) throws SQLException {
    delegate.commit(required);
    // 这里是事务缓存管理器提交,也就是把【暂存区】的数据 同步到【缓存区】
    tcm.commit();
  }

  @Override
  public void rollback(boolean required) throws SQLException {
    try {
      delegate.rollback(required);
    } finally {
      if (required) {
        tcm.rollback();
      }
    }
  }
  
  @Override
  public void clearLocalCache() {
    delegate.clearLocalCache();
  }
  
  // 根据当前DML标签设置的内容 也就是DML标签设置的 flushCache 属性 默认为false
  // 如果为true 则清空数据
  private void flushCacheIfRequired(MappedStatement ms) {
    Cache cache = ms.getCache();
    if (cache != null && ms.isFlushCacheRequired()) {      
      tcm.clear(cache);
    }
  }

  // 省略一些代码....
}

TransactionalCacheManager 事务缓存管理器

public class TransactionalCacheManager {

  // 这里创建了一个Map,代表就是一个事务缓存管理器存在多个暂存区
  // Key为【缓存区】value为【暂存区】
  // key是从CachingExecutor中传递过来的,从MapperStatement获取的,因为MapperStatement是在Mybatis初始化的时候就已经加载好的
  // 所以多个线程共用1份
  // 综合下来就体现了:key为【缓存区】多个线程共用,value为缓存区,为会话私有的
  private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>();

  public void clear(Cache cache) {
    getTransactionalCache(cache).clear();
  }
  
  public Object getObject(Cache cache, CacheKey key) {
    return getTransactionalCache(cache).getObject(key);
  }
  
  // cache=【缓存区】  key=缓存Key  value=查询结果
  public void putObject(Cache cache, CacheKey key, Object value) {
    // 通过缓存区找到对应的【暂存区】 然后存入
    getTransactionalCache(cache).putObject(key, value);
  }

  // 提交的是遍历然后依次提交
  public void commit() {
    for (TransactionalCache txCache : transactionalCaches.values()) {
      txCache.commit();
    }
  }

  // 回滚也是遍历回滚
  public void rollback() {
    for (TransactionalCache txCache : transactionalCaches.values()) {
      txCache.rollback();
    }
  }

  // 获取暂存区 没有就创建个空的
  private TransactionalCache getTransactionalCache(Cache cache) {
    TransactionalCache txCache = transactionalCaches.get(cache);
    if (txCache == null) {
      txCache = new TransactionalCache(cache);
      transactionalCaches.put(cache, txCache);
    }
    return txCache;
  }

}

TransactionalCache 事务缓存【暂存区】

public class TransactionalCache implements Cache {

  private static final Log log = LogFactory.getLog(TransactionalCache.class);

  // 真正存放数据的缓存对象
  private final Cache delegate;
  // 一个标记字段 当修改的时候,不会立刻清空【缓存区】,因为如果回滚了的话,数据就无法恢复了
  // 所以增加了一个标记 当修改commit的时候,才会真正的刷新【缓存区】
  private boolean clearOnCommit;
  // 【暂存区】 commit时才能提交到【缓存区】
  private final Map<Object, Object> entriesToAddOnCommit;
  // 缓存未命中的数据,事务commit时候,也会放入【缓存区】
  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>();
  }
  
  @Override
  public String getId() {
    return delegate.getId();
  }

  @Override
  public int getSize() {
    return delegate.getSize();
  }

  @Override
  public Object getObject(Object key) {
    // 【缓存区】获取数据
    Object object = delegate.getObject(key);
    if (object == null) {
      // 未命中,添加到集合里面
      entriesMissedInCache.add(key);
    }
    // clearOnCommit上面介绍是一个标记
    // 当update但未commit的时候,此时不能立刻清空【缓存区】,因为要考虑回滚
    // 所以如果 clearOnCommit = true 代表update了,但是没有commit,因为修改操作会刷新缓存区,此时【缓存区】不能用
    // 但是【缓存区】不能清空,因为如果回滚了 数据就没了 所以通过一个标记来判断
    if (clearOnCommit) {
      // 因为【缓存区】不能用,所以返回null
      return null;
    } else {
      // 代表【缓存区】可以正常使用 直接返回
      return object;
    }
  }

  @Override
  public ReadWriteLock getReadWriteLock() {
    return null;
  }

  // 存数据的时候 存到【暂存区】
  @Override
  public void putObject(Object key, Object object) {
    entriesToAddOnCommit.put(key, object);
  }

  @Override
  public Object removeObject(Object key) {
    return null;
  }
  
  // 当修改的时候 会调用这个方法
  @Override
  public void clear() {
    // 设置为true
    clearOnCommit = true;
    // 清空【暂存区】
    entriesToAddOnCommit.clear();
  }
 
  // 提交
  public void commit() {
    if (clearOnCommit) {// 判断清空缓存
      delegate.clear();
    }
    // 刷新数据到二级缓存
    flushPendingEntries();
    // 充值
    reset();
  }

  public void rollback() {
    unlockMissedEntries();
    reset();
  }

  // 重置清空
  private void reset() {
    clearOnCommit = false;
    entriesToAddOnCommit.clear();
    entriesMissedInCache.clear();
  }

  // 刷新【缓存区】
  private void flushPendingEntries() {
    // 存入【缓存区】
    for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
      delegate.putObject(entry.getKey(), entry.getValue());
    }
    // 未命中也存入
    for (Object entry : entriesMissedInCache) {
      if (!entriesToAddOnCommit.containsKey(entry)) {
        delegate.putObject(entry, null);
      }
    }
  }

  private void unlockMissedEntries() {
    for (Object entry : entriesMissedInCache) {
      try {
        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);
      }
    }
  }
}

3.二级缓存源码

上面我们提到了,一级缓存是通过PerpetualCache来实现,二级缓存是通过CachingExecutor来操作,但是真正的数据不是存在这里,它只负责执行二级缓存,想到二级缓存是跨线程的,所以肯定不能保存在单独的会话里,所以它是保存在DML标签封装的MappedStatement里面,那么接下来介绍一下它的结构和创建流程

调用链

org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement 在解析Mapper映射文件的时候会解析 cache 标签

 private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      // 解析缓存引用标签
      cacheRefElement(context.evalNode("cache-ref"));
      // 解析缓存标签
      cacheElement(context.evalNode("cache"));
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      sqlElement(context.evalNodes("/mapper/sql"));
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }

  // 解析缓存标签
  private void cacheElement(XNode context) throws Exception {
    if (context != null) {
      String type = context.getStringAttribute("type", "PERPETUAL");
      Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
      // 默认LRU溢出清除策略
      String eviction = context.getStringAttribute("eviction", "LRU");
      Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
      Long flushInterval = context.getLongAttribute("flushInterval");
      Integer size = context.getIntAttribute("size");
      boolean readWrite = !context.getBooleanAttribute("readOnly", false);
      boolean blocking = context.getBooleanAttribute("blocking", false);
      Properties props = context.getChildrenAsProperties();
      // 这里借助助理类创建Cache
      builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }
  }

org.apache.ibatis.builder.MapperBuilderAssistant#useNewCache()

 public Cache useNewCache(Class<? extends Cache> typeClass,
      Class<? extends Cache> evictionClass,
      Long flushInterval,
      Integer size,
      boolean readWrite,
      boolean blocking,
      Properties props) {
    // 通过CacheBuilder来创建
    Cache cache = new CacheBuilder(currentNamespace)
        .implementation(valueOrDefault(typeClass, PerpetualCache.class))
        .addDecorator(valueOrDefault(evictionClass, LruCache.class))
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        .build();
    configuration.addCache(cache);
    currentCache = cache;
    return cache;
  }

CacheBuilder

从上面的调用链看到,最终创建Cache还是通过CacheBuilder,该类通过配置创建缓存类,并且形成装饰+责任链模式

public class CacheBuilder {
    
  private final String id;
  // 缓存底层实现类
  private Class<? extends Cache> implementation;
  // 缓存溢出清除策略
  private final List<Class<? extends Cache>> decorators;
  // 缓存的资源数量
  private Integer size;
  // 刷新间隔
  private Long clearInterval;
  // 只读 默认false
  private boolean readWrite;
  // 属性
  private Properties properties;
  private boolean blocking;

  // 构造方法初始化
  public CacheBuilder(String id) {
    this.id = id;
    this.decorators = new ArrayList<Class<? extends Cache>>();
  }

  // 默认PerpetualCache.class,底层还是和一级缓存一样,借助其实现存储
  // 不过一级缓存必须是这个 二级缓存可以自定义
  public CacheBuilder implementation(Class<? extends Cache> implementation) {
    this.implementation = implementation;
    return this;
  }

  // 缓存溢出策略
  public CacheBuilder addDecorator(Class<? extends Cache> decorator) {
    if (decorator != null) {
      this.decorators.add(decorator);
    }
    return this;
  }

  public CacheBuilder size(Integer size) {
    this.size = size;
    return this;
  }
 
  public CacheBuilder clearInterval(Long clearInterval) {
    this.clearInterval = clearInterval;
    return this;
  }
  
  public CacheBuilder readWrite(boolean readWrite) {
    this.readWrite = readWrite;
    return this;
  }

  public CacheBuilder blocking(boolean blocking) {
    this.blocking = blocking;
    return this;
  }
    
  public CacheBuilder properties(Properties properties) {
    this.properties = properties;
    return this;
  }
  
  // 建造
  public Cache build() {
    // 1.设置默认的实现类和溢出策略
    setDefaultImplementations();
    // 2.通过构造方法创建实现类
    Cache cache = newBaseCacheInstance(implementation, id);
    // 3.设置缓存属性
    setCacheProperties(cache);
    // issue #352, do not apply decorators to custom caches
    if (PerpetualCache.class.equals(cache.getClass())) {
      for (Class<? extends Cache> decorator : decorators) {
        // 4.装饰缓存 形成责任链模式
        cache = newCacheDecoratorInstance(decorator, cache);
        setCacheProperties(cache);
      }
      cache = setStandardDecorators(cache);
    } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
      cache = new LoggingCache(cache);
    }
    return cache;
  }
  
  // 设置默认的实现类和溢出策略
  private void setDefaultImplementations() {
    if (implementation == null) {
      implementation = PerpetualCache.class;
      if (decorators.isEmpty()) {
        decorators.add(LruCache.class);
      }
    }
  }

  // 设置其它装饰  通过属性判断  继续添加责任链
  private Cache setStandardDecorators(Cache cache) {
    try {
      MetaObject metaCache = SystemMetaObject.forObject(cache);
      if (size != null && metaCache.hasSetter("size")) {
        metaCache.setValue("size", size);
      }
      if (clearInterval != null) {
        cache = new ScheduledCache(cache);
        ((ScheduledCache) cache).setClearInterval(clearInterval);
      }
      if (readWrite) {
        cache = new SerializedCache(cache);
      }
      // LoggingCache
      cache = new LoggingCache(cache);
      // SynchronizedCache
      cache = new SynchronizedCache(cache);
      if (blocking) {
        cache = new BlockingCache(cache);
      }
      return cache;
    } catch (Exception e) {
      throw new CacheException("Error building standard cache decorators.  Cause: " + e, e);
    }
  }

  // 上面的方法只是把属性设置到了CacheBuilder这个类  这个方法是把属性设置到Cache里面
  private void setCacheProperties(Cache cache) {
    if (properties != null) {
      MetaObject metaCache = SystemMetaObject.forObject(cache);
      for (Map.Entry<Object, Object> entry : properties.entrySet()) {
        String name = (String) entry.getKey();
        String value = (String) entry.getValue();
        if (metaCache.hasSetter(name)) {
          Class<?> type = metaCache.getSetterType(name);
          if (String.class == type) {
            metaCache.setValue(name, value);
          } else if (int.class == type
              || Integer.class == type) {
            metaCache.setValue(name, Integer.valueOf(value));
          } else if (long.class == type
              || Long.class == type) {
            metaCache.setValue(name, Long.valueOf(value));
          } else if (short.class == type
              || Short.class == type) {
            metaCache.setValue(name, Short.valueOf(value));
          } else if (byte.class == type
              || Byte.class == type) {
            metaCache.setValue(name, Byte.valueOf(value));
          } else if (float.class == type
              || Float.class == type) {
            metaCache.setValue(name, Float.valueOf(value));
          } else if (boolean.class == type
              || Boolean.class == type) {
            metaCache.setValue(name, Boolean.valueOf(value));
          } else if (double.class == type
              || Double.class == type) {
            metaCache.setValue(name, Double.valueOf(value));
          } else {
            throw new CacheException("Unsupported property type for cache: '" + name + "' of type " + type);
          }
        }
      }
    }
    if (InitializingObject.class.isAssignableFrom(cache.getClass())){
      try {
        ((InitializingObject) cache).initialize();
      } catch (Exception e) {
        throw new CacheException("Failed cache initialization for '" +
            cache.getId() + "' on '" + cache.getClass().getName() + "'", e);
      }
    }
  }
  
  // 初始化缓存底层类
  private Cache newBaseCacheInstance(Class<? extends Cache> cacheClass, String id) {
    Constructor<? extends Cache> cacheConstructor = getBaseCacheConstructor(cacheClass);
    try {
      return cacheConstructor.newInstance(id);
    } catch (Exception e) {
      throw new CacheException("Could not instantiate cache implementation (" + cacheClass + "). Cause: " + e, e);
    }
  }

  private Constructor<? extends Cache> getBaseCacheConstructor(Class<? extends Cache> cacheClass) {
    try {
      return cacheClass.getConstructor(String.class);
    } catch (Exception e) {
      throw new CacheException("Invalid base cache implementation (" + cacheClass + ").  " +
          "Base cache implementations must have a constructor that takes a String id as a parameter.  Cause: " + e, e);
    }
  }
  
  // 创建缓存实现  形成责任链模式
  private Cache newCacheDecoratorInstance(Class<? extends Cache> cacheClass, Cache base) {
    Constructor<? extends Cache> cacheConstructor = getCacheDecoratorConstructor(cacheClass);
    try {
      return cacheConstructor.newInstance(base);
    } catch (Exception e) {
      throw new CacheException("Could not instantiate cache decorator (" + cacheClass + "). Cause: " + e, e);
    }
  }

  private Constructor<? extends Cache> getCacheDecoratorConstructor(Class<? extends Cache> cacheClass) {
    try {
      return cacheClass.getConstructor(Cache.class);
    } catch (Exception e) {
      throw new CacheException("Invalid cache decorator (" + cacheClass + ").  " +
          "Cache decorators must have a constructor that takes a Cache instance as a parameter.  Cause: " + e, e);
    }
  }
}

LruCache

当缓存溢出的时候,通过LRU算法移除

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);
  }

  @Override
  public String getId() {
    return delegate.getId();
  }

  @Override
  public int getSize() {
    return delegate.getSize();
  }

  public void setSize(final int size) {
    keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
      private static final long serialVersionUID = 4267176411845948333L;

      @Override
      protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
        boolean tooBig = size() > size;
        if (tooBig) {
          eldestKey = eldest.getKey();
        }
        return tooBig;
      }
    };
  }

  @Override
  public void putObject(Object key, Object value) {
    delegate.putObject(key, value);
    cycleKeyList(key);
  }

  @Override
  public Object getObject(Object key) {
    keyMap.get(key); //touch
    return delegate.getObject(key);
  }

  @Override
  public Object removeObject(Object key) {
    return delegate.removeObject(key);
  }

  @Override
  public void clear() {
    delegate.clear();
    keyMap.clear();
  }

  @Override
  public ReadWriteLock getReadWriteLock() {
    return null;
  }

  private void cycleKeyList(Object key) {
    keyMap.put(key, key);
    if (eldestKey != null) {
      delegate.removeObject(eldestKey);
      eldestKey = null;
    }
  }

}

LoggingCache 日志缓存类

public class LoggingCache implements Cache {

  private final Log log;
  private final Cache delegate;
  // 请求次数
  protected int requests = 0;
  // 命中次数
  protected int hits = 0;

  public LoggingCache(Cache delegate) {
    this.delegate = delegate;
    this.log = LogFactory.getLog(getId());
  }

  @Override
  public String getId() {
    return delegate.getId();
  }

  @Override
  public int getSize() {
    return delegate.getSize();
  }

  @Override
  public void putObject(Object key, Object object) {
    delegate.putObject(key, object);
  }

  @Override
  public Object getObject(Object key) {
    requests++;
    final Object value = delegate.getObject(key);
    if (value != null) {
      hits++;
    }
    // 在这里会输出缓存命中率   是缓存命中次数 / 请求次数
    if (log.isDebugEnabled()) {
      log.debug("Cache Hit Ratio [" + getId() + "]: " + getHitRatio());
    }
    return value;
  }

  @Override
  public Object removeObject(Object key) {
    return delegate.removeObject(key);
  }

  @Override
  public void clear() {
    delegate.clear();
  }

  @Override
  public ReadWriteLock getReadWriteLock() {
    return null;
  }

  @Override
  public int hashCode() {
    return delegate.hashCode();
  }

  @Override
  public boolean equals(Object obj) {
    return delegate.equals(obj);
  }
  // 这里看到命中率的除法
  private double getHitRatio() {
    return (double) hits / (double) requests;
  }
}

SynchronizedCache 同步缓存类

同步缓存 只是方法都加了synchronized标识

public class SynchronizedCache implements Cache {

  private final Cache delegate;
  
  public SynchronizedCache(Cache delegate) {
    this.delegate = delegate;
  }

  @Override
  public String getId() {
    return delegate.getId();
  }

  @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();
  }

  @Override
  public int hashCode() {
    return delegate.hashCode();
  }

  @Override
  public boolean equals(Object obj) {
    return delegate.equals(obj);
  }

  @Override
  public ReadWriteLock getReadWriteLock() {
    return null;
  }

}

ScheduledCache

设置了flushInterval刷新间隔属性,才会通过其进行装饰

SerializedCache

设置了readOnly只读属性,才会通过其进行装饰

BlockingCache

设置了blocking属性,才会通过其进行装饰

s;
}
}


### SynchronizedCache 同步缓存类

同步缓存 只是方法都加了synchronized标识

```java
public class SynchronizedCache implements Cache {

  private final Cache delegate;
  
  public SynchronizedCache(Cache delegate) {
    this.delegate = delegate;
  }

  @Override
  public String getId() {
    return delegate.getId();
  }

  @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();
  }

  @Override
  public int hashCode() {
    return delegate.hashCode();
  }

  @Override
  public boolean equals(Object obj) {
    return delegate.equals(obj);
  }

  @Override
  public ReadWriteLock getReadWriteLock() {
    return null;
  }

}

ScheduledCache

设置了flushInterval刷新间隔属性,才会通过其进行装饰

SerializedCache

设置了readOnly只读属性,才会通过其进行装饰

BlockingCache

设置了blocking属性,才会通过其进行装饰

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

为人师表好少年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值