多次查询缓存的问题 flushCache为啥失效

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多次查询缓存的问题 flushCache为啥失效相关的知识,希望对你有一定的参考价值。

参考技术A 最近在使用mybatis的过程中,发现一个问题。如果在同一个事物中,多次同一个查询sql在mybatis的执行过程中,只会查询一次数据库,后几次所返回的对象是mybatis在在内部做了缓存。 Property property = this.findByPropertyId("123"); property.setPropertyId(null);; property = this.findByPropertyId("123"); System.out.println(property.getPropertyId()); 以上的代码,打印的结果为 null , 但是我们所期望的可能是 123 , 我不知道这是mybatis的一个bug还是故意这样去设计的.mybatis在执行查询语句的时候,会在本地做一份缓存信息.在BaseExecutor类中: private List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException List 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; 可以看到在queryFromDatabase方法中,查询数据库返回结果之后,mybatis编制了一个cachekey的对象,作为key,返回结果作为value,放入了缓存当中 (这个地方没有使用拷贝的函数,所以只要外部修改了值,内部缓存中的值信息也会被修改) 之后再下次查询的时候,会依据一个判断,是否需要执行缓存信息,同样是在BaseExecutor类中. @SuppressWarnings("unchecked") public List 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 list; try queryStack++; list = resultHandler == null ? (List) 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(); deferredLoads.clear(); // issue #601 if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) clearLocalCache(); // issue #482 return list; 看到mybatis判断了 ms.isFlushCacheRequired() 的返回数据,如果为 true 会执行 clearLocalCache 方法,清空缓存信息。如果缓存中获取不到的话,才会继续去查询数据库。 可以从 list = resultHandler == null ? (List) localCache.getObject(key) : null; 代码中看出。 所以当第一次查询放入缓存之后,在外部修改了任何一个值之后,mybatis内部缓存的值也会被修改,而且下次查询不会查询数据库,直接返回缓存中被修改过的值 ms.isFlushCacheRequired() 这段代码的判断是基于了一个MappedStatement 类中的flushCacheRequired 的属性做判断的。flushCacheRequired 变量可以通过注解的方式和xml的方式来配置 1.注解:注解的方式是通过 @Options 注解中 flushCache 的配置 2.配置文件:xml中每一个select 都可以设置 flushCache 的属性 flushCache 设置成true之后,本sql的每次查询都会清空缓存后在执行

mybatis缓存问题导致无法查询到数据

今天查询记录时,发现重复查询结果时出现空记录的情况

查看控制台信息,只有红色框选部分有进行查询数据,而其他没有。然而上图可看出有两条数据是能展现出来的,故有可能是mybatis缓存命中的。

因此在mapper文件中加入flushCache="true" useCache="false"

<select id="querySuppliers"  flushCache="true" useCache="false"  resultType="cn.com.ebidding.web.yzPurchaseMain.dao.model.QueryYzResultModel">
		select t1.id id,t2.company_name name
		from lib_suppliers t1, sys_company t2
		where t1.is_deleted = \'0\'
		and t2.is_deleted = \'0\'
		and t1.company_id = t2.id
		and t1.agent_id = #{agentId}
		and t1.id in
		<foreach item="item" index="index" collection="ids" open="(" separator="," close=")">
		#{item}
		</foreach>
	</select>

问题解决

总结:

(1) 当为select语句时:

  • flushCache默认为false,表示任何时候语句被调用,都不会去清空本地缓存和二级缓存。

  • useCache默认为true,表示会将本条语句的结果进行二级缓存。

(2) 当为insert、update、delete语句时:

  • flushCache默认为true,表示任何时候语句被调用,都会导致本地缓存和二级缓存被清空。

  • useCache属性在该情况下没有。

以上是关于多次查询缓存的问题 flushCache为啥失效的主要内容,如果未能解决你的问题,请参考以下文章

为什么要用缓存?

Redis 缓存穿透和缓存失效的预防和解决

Redis分布式缓存知识拓展1 -- 处理缓存失效穿透和雪崩问题

为啥标准 C# 事件调用模式是线程安全的,没有内存屏障或缓存失效?类似的代码呢?

框架集成Redis两种方式

为啥 Magento 在产品保存时使整页缓存失效实际上使页面未缓存以及由于未缓存而刷新会做啥