转载

原 荐 mybatis二级缓存了解下

背景

某小伙伴问了我一个问题

原 荐 mybatis二级缓存了解下

原 荐 mybatis二级缓存了解下

为啥查不到数据了呢???

分析

看到这种问题 常规思路就是是否数据隔离造成

但是看起来是正常的【毕竟只是查询】

小伙伴写的代码如下

@Override
public PUserVo getUserInfo(PuserQuery puserQuery) {
    PUserRelationQuery pUserRelationQuery = new PUserRelationQuery();
    pUserRelationQuery.setWxOpenId(puserQuery.getWxOpenId());
    pUserRelationQuery.setDeleteFlag(puserQuery.getDelleteFlag());
    pUserRelationQuery.setUserId(puserQuery.getUserId());
    pUserRelationQuery.setUserType(puserQuery.getUserType());
    pUserRelationQuery.setId(puserQuery.getId());
    PUserRelation pUserRelation = pUserRelationService.getUserRelation(pUserRelationQuery);
    logger.info("getUserInfo pUserRelation:" + JSON.toJSONString(pUserRelation));
    if (pUserRelation != null) {
        PUserVo pUserVo = orikaMapper.convert(pUserRelation, PUserVo.class);
        pUserVo.setImgSrc(pUserRelation.getImgSource());
        if (pUserRelation.getUserId() != null) {
            Puser puser = puserService.getById(pUserRelation.getUserId());
            pUserVo.setUserTel(puser.getUserTel());
            pUserVo.setStationId(puser.getStationId());
            pUserVo.setUserName(pUserRelation.getNickName());
        }
        return pUserVo;
    } else {
        return null;
    }
}

从上面代码来看确实查询到了数据才会返回!!!那么为何在db中却查询不到了呢???

为了了解该数据从何处获得的~【或者对应id的数据什么时间删除的 我们来翻一下binlog】

原 荐 mybatis二级缓存了解下

早在早上8:30该数据就被人删除了,但是为何在9:06该数据还能查询到呢???

到了这时 缓存两个字就到到了我的思绪中~

众所周知 mybatis分为一级缓存和二级缓存

其中一级缓存通过BaseExecutor完成并且无法取消

二级缓存通过CachingExecutor完成 通过全局配置cacheEnabled完成配置

常规博客都说明使用二级缓存需要设置cacheEnabled为true

<setting name="cacheEnabled" value="true"/>

实质上真的如此么???

来看到对应的默认值

configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));

哎呀~原来默认的配置中二级缓存就是生效的~看来真是尽信书不如无书……

那么为什么我们平时没有感觉到二级缓存生效呢???

二级缓存代码如下

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);
  }
  if (cacheEnabled) {
    executor = new CachingExecutor(executor);
  }
  executor = (Executor) interceptorChain.pluginAll(executor);
  return executor;
}

来看一下CachingExecutor

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

一定要cache不为空并且useCache为true并且resultHandler为空才会生效

官方文档如是说

缓存

MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。MyBatis 3 中的缓存实现的很多改进都已经实现了,使得它更加强大而且易于配置。

默认情况下是没有开启缓存的,除了局部的 session 缓存,可以增强变现而且处理循环 依赖也是必须的。要开启二级缓存,你需要在你的 SQL 映射文件中添加一行:

<cache/>

字面上看就是这样。这个简单语句的效果如下:

  • 映射语句文件中的所有 select 语句将会被缓存。
  • 映射语句文件中的所有 insert,update 和 delete 语句会刷新缓存。
  • 缓存会使用 Least Recently Used(LRU,最近最少使用的)算法来收回。
  • 根据时间表(比如 no Flush Interval,没有刷新间隔), 缓存不会以任何时间顺序 来刷新。
  • 缓存会存储列表集合或对象(无论查询方法返回什么)的 1024 个引用。
  • 缓存会被视为是 read/write(可读/可写)的缓存,意味着对象检索不是共享的,而 且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

NOTE The cache will only apply to statements declared in the mapping file where the cache tag is located. If you are using the Java API in conjunction with the XML mapping files, then statements declared in the companion interface will not be cached by default. You will need to refer to the cache region using the @CacheNamespaceRef annotation.

也就是说当使用cache【当然cache-ref也可以】的时候可以创建cache 当使用cache-ref用来引用其他定义的缓存

boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
 
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
    .resource(resource)
    .fetchSize(fetchSize)
    .timeout(timeout)
    .statementType(statementType)
    .keyGenerator(keyGenerator)
    .keyProperty(keyProperty)
    .keyColumn(keyColumn)
    .databaseId(databaseId)
    .lang(lang)
    .resultOrdered(resultOrdered)
    .resulSets(resultSets)
    .resultMaps(getStatementResultMaps(resultMap, resultType, id))
    .resultSetType(resultSetType)
    .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
    .useCache(valueOrDefault(useCache, isSelect))
    .cache(currentCache);

而当命令类型为select将会默认加上useCache为true 这样就完成了默认cache的二级缓存。

我们来看一下这位同学的代码

原 荐 mybatis二级缓存了解下

破案

上述明显了 该同学加上了cache导致对应namespace下的二级缓存生效呢 二级缓存不会在每个sqlSession查询完成或者事务完成后清除 因此在db中操作的删除语句并不能影响到内存中的数据 导致某个请求一直可以查到“不存在”的数据

由此引发了一些问题~

原文  https://my.oschina.net/qixiaobo025/blog/1941402
正文到此结束
Loading...