Mybatis3.4.x技术内幕(二十二):Mybatis一级、二级缓存原理分析

↘锁芯ラ 提交于 2019-11-29 11:18:48

Mybatis的一级缓存,指的是SqlSession级别的缓存,默认开启;Mybatis的二级缓存,指的是SqlSessionFactory级别的缓存,需要配置。缓存是针对select来说的。

1、一级缓存

<configuration>
	<settings>
		<setting name="localCacheScope" value="SESSION|STATEMENT" />
	</settings>
</configuration>

localCacheScope用于配置一级缓存的范围,默认值是SESSION,表示SqlSession范围;

如果配置为STATEMENT,则表示SqlSession范围内的一个查询范围,但它并不是一个Statement实例范围。

STATEMENT举例:查询Student对象发送一次sql查询,紧接着再发一次sql查询关联的Teacher对象,这个完整过程称之为一个查询。

一级缓存默认开启,且没有全局关闭的配置开关。

<select ... flushCache="false" useCache="true|false"/>

flushCache:同时影响了一级、二级缓存,flushCache=true,会导致清空本条sql(当前MappedStatement)的一级、二级缓存,注意是当前的,不影响其他的MappedStatement。

useCache:配置本条MappedStatement是否使用二级缓存,useCache=true,从二级缓存中获取,没有获取到,才从数据库中获取。

org.apache.ibatis.executor.CachingExecutor#query()方法源码:

Cache cache = ms.getCache();
    if (cache != null) {
      // flushCache作用于二级缓存
      flushCacheIfRequired(ms);
      // useCache作用于二级缓存
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, parameterObject, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          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);

org.apache.ibatis.executor.BaseExecutor#query()方法源码:

if (queryStack == 0 && ms.isFlushCacheRequired()) {
      // flushCache作用于一级缓存
      clearLocalCache();
}

在执行update、insert、delete、flushCache="true"、commit、rollback、LocalCacheScope.STATEMENT等情况下,一级缓存就都会被清空。

缓存其实基本数据结构就是一个HashMap,缓存中是否存在缓存数据,依赖key的生成策略。

org.apache.ibatis.executor.BaseExecutor.createCacheKey()源码。

  @Override
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    CacheKey cacheKey = new CacheKey();
    cacheKey.update(ms.getId());
    cacheKey.update(Integer.valueOf(rowBounds.getOffset()));
    cacheKey.update(Integer.valueOf(rowBounds.getLimit()));
    cacheKey.update(boundSql.getSql());
    for (int i = 0; i < parameterMappings.size(); i++) {
      //...
        cacheKey.update(value);
      }
    }
    if (configuration.getEnvironment() != null) {
      // issue #176
      cacheKey.update(configuration.getEnvironment().getId());
    }
    return cacheKey;
  }

id:com.mybatis3.mappers.TeacherMapper.findTeacherById

key的生成策略:id + offset + limit + sql + param value + environment id,这些值都相同,生成的key就相同。

2、二级缓存

<configuration>
	<settings>
		<setting name="cacheEnabled" value="true|false" />
	</settings>
</configuration>

cacheEnabled=true表示二级缓存可用,但是要开启话,需要在Mapper.xml内配置。

<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
if (cacheEnabled) {
      executor = new CachingExecutor(executor);
}

二级缓存通过CachingExecutor来实现,原理是缓存里存在,就返回,没有就调用Executor delegate到数据库中查询。

org.apache.ibatis.executor.CachingExecutor.query()源码。

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, 
                           ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    Cache cache = ms.getCache();
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, parameterObject, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          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);
  }

FIFO:First In First Out先进先出队列。

flushInterval="60000",间隔60秒清空缓存,这个间隔60秒,是被动触发的,而不是定时器轮询的。

size=512,表示队列最大512个长度,大于则移除队列最前面的元素,这里的长度指的是CacheKey的个数。

CacheKey的生成策略,和一级缓存相同,id + offset + limit + sql + param value + environment id。

readOnly="true",表示任何获取对象的操作,都将返回同一实例对象。如果readOnly="false",则每次返回该对象的拷贝对象,简单说就是序列化复制一份返回。

二级缓存有一个非常重要的空间划分策略:

namespace="com.mybatis3.mappers.TeacherMapper"

namespace="com.mybatis3.mappers.StudentMapper"

即,按照namespace划分,同一个namespace,同一个Cache空间,不同的namespace,不同的Cache空间。

每当执行insert、update、delete,flushCache=true时,二级缓存都会被清空。

版权提示:文章出自开源中国社区,若对文章感兴趣,可关注我的开源中国社区博客(http://my.oschina.net/zudajun)。(经过网络爬虫或转载的文章,经常丢失流程图、时序图,格式错乱等,还是看原版的比较好)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!