Mybatis剖析--2-缓存

190 阅读4分钟

缓存

如下图所示,在开启了二级缓存的情况下:在执行 select 查询的时候,MyBatis 会先从二级缓存中取输入,其次才是一级缓存,即 MyBatis 查询数据的顺序是:二级缓存 ———> 一级缓存 ——> 数据库。在数据库查询到数据是会第一时间保存到一级缓存,然后会调用tcm.putObject(cache, key, list)方法暂存到暂存TransactionalCache中,等到SqlSession commit 再保存到二级缓存中。

缓存的key

  • Mapper的Id,即Mapper命名空间与<select|update|insert|delete>标签的Id组成的全局限定名。
  • 查询结果的偏移量及查询的条数。
  • 具体的SQL语句及SQL语句中需要传递的所有参数。
  • MyBatis主配置文件中,通过标签配置的环境信息对应的Id属性值。

综上所述,CacheKey 由以下条件决定:statementId + rowBounds + 传递给 JDBC 的 SQL + 传递给 JDBC 的参数值

一级缓存

BaseExecutor 有一个PerpetualCache属性

如果查不到的话,就从数据库查,在queryFromDatabase中,会对localcache进⾏写⼊。 localcache 对象的put⽅法最终交给Map进⾏存放

如果中间sqlSession去执⾏commit操作(执⾏插⼊、更新、删除),则会清空SqlSession中的 ⼀ 级缓存,这样做的⽬的为了让缓存中存储的是最新的信息,避免脏读。

private Map<Object, Object> cache = new HashMap<Object, Object>();
 @Override
 public void putObject(Object key, Object value) { cache.put(key, value);
}
一级缓存的生命周期
  1. MyBatis 在开启一个数据库会话时,会创建一个新的 SqlSession 对象,SqlSession 对象中会有一个新的 Executor 对象,Executor 对象中持有一个新的 PerpetualCache 对象;当会话结束时,SqlSession 对象及其内部的 Executor 对象还有 PerpetualCache 对象也一并释放掉

  2. 如果 SqlSession 调用了 close() 方法,会释放掉一级缓存 PerpetualCache 对象,一级缓存将不可用;

  3. 如果 SqlSession 调用了 clearCache() ,会清空 PerpetualCache 对象中的数据,但是该对象仍可使用;

  4. SqlSession 中执行了任何一个 update 操作 (update()、delete()、insert()),都会清空 PerpetualCache 对象的数据,但是该对象可以继续使用;

二级缓存

如下代码可以看出CachingExecutor 是 Executor 的装饰者,以增强 Executor 的功能,使其具有缓存查询的功能,这里用到了设计模式中的装饰者模式

private TransactionalCache getTransactionalCache(Cache cache) {
    return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
}

对于每个 Cache 而言,都有一个容量限制,MyBatis 提供了各种策略来对 Cache 缓存的容量进行控制,以及对 Cache 中的数据进行刷新和置换。MyBatis 主要提供了以下几个刷新和置换策略:

LRU:(Least Recently Used), 最近最少使用算法,即如果缓存中容量已经满了,会将缓存中最近最少被使用的缓存记录清除掉,然后添加新的记录;

FIFO:(First in first out), 先进先出算法,如果缓存中的容量已经满了,那么会将最先进入缓存中的数据清除掉;

Scheduled:指定时间间隔清空算法,该算法会以指定的某一个时间间隔将 Cache 缓存中的数据清空;

二级缓存查询流程如下:

 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
            throws SQLException {

        // 从 MappedStatement 中获取 Cache,注意这里的 Cache 是从MappedStatement中获取的
        // 也就是我们上面解析Mapper中<cache/>标签中创建的,它保存在Configration中
        // 我们在初始化解析xml时分析过每一个MappedStatement都有一个Cache对象,就是这里
        Cache cache = ms.getCache();

        // 如果配置文件中没有配置 <cache>,则 cache 为空
        if (cache != null) {
            //如果需要刷新缓存的话就刷新:flushCache="true"
            flushCacheIfRequired(ms);
            if (ms.isUseCache() && resultHandler == null) {
                // 暂时忽略,存储过程相关
                ensureNoOutParams(ms, boundSql);
                @SuppressWarnings("unchecked")
                // 从二级缓存中,获取结果
                List<E> list = (List<E>) tcm.getObject(cache, key);
                if (list == null) {
                    // 如果没有值,则执行查询,这个查询实际也是先走一级缓存查询,一级缓存也没有的话,则进行DB查询
                    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);
    }