MyBatis 包含一个非常强大的查询缓存特性, 它可以非常方便地配置和定制. 缓存可以极大的提升查询效率.
MyBatis 系统中默认定义了两级缓存, 一级 缓存和 二级缓存.
- 1, 默认情况下, 只有一级缓存 (SqlSession 级别的缓存, 也称为本地缓存) 开启, 一级缓存默认实现类 org.apache.ibatis.cache.impl.PerpetualCache.
- 2, 二级缓存需要手动开启和配置, 他是基于 namespace 级别的缓存.
- 3, 为了提高扩展性. MyBatis 定义了缓存接口 Cache, 我们可以通过实现 Cache 接口来自定义二级缓存.
一级缓存
一级缓存是 Local Cache, 即本地缓存, 默认作用域为 SqlSesson 级别, mybatis3.1 之后, 可以配置本地缓存的作用域(SESSION|STATEMENT), 在全局配置文件中可以设置 localCacheScope.Mybatis 默认的一级缓存默认是通过 org.apache.ibatis.cache.impl.PerpetualCache 来实现的, PerpetualCache 内里有一个 HashMap, 用来存储本地缓存.
Map 的 CacheKey 由方法的 ID,RowBounds,SQL 语句, 参数, 环境因素影响; Map 的 Value 存放具体的结果对象.
格式: 影响因子的 HashCode1: 影响因子的 HashCode2: 方法 ID:offset:limit:SQL 语句: 参数: 环境 ID
- -1623117942:1735139101:com.kancy.mapper.PersonMapper.selectPersonById:0:2147483647:select * from t_person where id = ? and last_name = ? and sex = ?:1:emma:0:test
- public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
- if (closed) {
- throw new ExecutorException("Executor was closed.");
- }
- CacheKey cacheKey = new CacheKey();
- cacheKey.update(ms.getId());
- cacheKey.update(rowBounds.getOffset());
- cacheKey.update(rowBounds.getLimit());
- cacheKey.update(boundSql.getSql());
- List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
- TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
- // mimic DefaultParameterHandler logic
- for (ParameterMapping parameterMapping : parameterMappings) {
- if (parameterMapping.getMode() != ParameterMode.OUT) {
- Object value;
- String propertyName = parameterMapping.getProperty();
- if (boundSql.hasAdditionalParameter(propertyName)) {
- value = boundSql.getAdditionalParameter(propertyName);
- } else if (parameterObject == null) {
- value = null;
- } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
- value = parameterObject;
- } else {
- MetaObject metaObject = configuration.newMetaObject(parameterObject);
- value = metaObject.getValue(propertyName);
- }
- cacheKey.update(value);
- }
- }
- if (configuration.getEnvironment() != null) {
- // issue #176
- cacheKey.update(configuration.getEnvironment().getId());
- }
- return cacheKey;
- }
从查询的源码可以看出, mybatis 在查询前, 先看是否需要清除缓存, 再通过 CacheKey 从缓存中查找是否有对应的 key, 有则返回对象, 无则从数据库中查找, 再看一级缓存的作用域范围, 是 STATEMENT 则清除缓存, 否则将结果集放到缓存中.
- 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;
- }
一级缓存演示 & & 失效情况
同一次会话期间只要查询过的数据都会保存在当前 SqlSession 的一个 Map 中
一级缓存失效的四种情况
- 1, 不同的 SqlSession 对应不同的一级缓存
- 2, 同一个 SqlSession 但是查询条件不同
- 3, 同一个 SqlSession 两次查询期间执行了任何一次增删改操作
- 4, 同一个 SqlSession 两次查询期间手动清空了缓存
注意:
- sql 标签的 flushCache 属性查询默认 flushCache=false; 增删改 insert|update|delete 会修改数据, 默认 flushCache=true, 会同时清空一级和二级缓存.
- sqlSession.clearCache(): 只是用来清除一级缓存, 不会清除二级缓存.
- 当在某一个作用域 (一级缓存 Session / 二级缓存 Namespaces) 进行了 C/U/D 操作后, 默认该作用域下所有有 select 中的缓存将被 clear.
二级缓存
来源: http://www.bubuko.com/infodetail-2909023.html