MyBatis】MyBatis一级缓存和二级缓存

转载自  MyBatis】MyBatis一级缓存和二级缓存

MyBatis自带的缓存有一级缓存和二级缓存

一级缓存

Mybatis的一级缓存是指Session缓存。一级缓存的作用域默认是一个SqlSession。Mybatis默认开启一级缓存。 
也就是在同一个SqlSession中,执行相同的查询SQL,第一次会去数据库进行查询,并写到缓存中; 
第二次以后是直接去缓存中取。 
当执行SQL查询中间发生了增删改的操作,MyBatis会把SqlSession的缓存清空。

一级缓存的范围有SESSION和STATEMENT两种,默认是SESSION,如果不想使用一级缓存,可以把一级缓存的范围指定为STATEMENT,这样每次执行完一个Mapper中的语句后都会将一级缓存清除。 
如果需要更改一级缓存的范围,可以在Mybatis的配置文件中,在下通过localCacheScope指定。

 <setting name="localCacheScope" value="STATEMENT"/>

建议不需要修改

需要注意的是 
当Mybatis整合Spring后,直接通过Spring注入Mapper的形式,如果不是在同一个事务中每个Mapper的每次查询操作都对应一个全新的SqlSession实例,这个时候就不会有一级缓存的命中,但是在同一个事务中时共用的是同一个SqlSession。 
如有需要可以启用二级缓存。

二级缓存

Mybatis的二级缓存是指mapper映射文件。二级缓存的作用域是同一个namespace下的mapper映射文件内容,多个SqlSession共享。Mybatis需要手动设置启动二级缓存。

二级缓存是默认启用的(要生效需要对每个Mapper进行配置),如想取消,则可以通过Mybatis配置文件中的元素下的子元素来指定cacheEnabled为false。

<settings><setting name="cacheEnabled" value="false" />
</settings>

cacheEnabled默认是启用的,只有在该值为true的时候,底层使用的Executor才是支持二级缓存的CachingExecutor。具体可参考Mybatis的核心配置类org.apache.ibatis.session.Configuration的newExecutor方法实现。 
可以通过源码看看

...    public Executor newExecutor(Transaction transaction) {        return this.newExecutor(transaction, this.defaultExecutorType);}    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {executorType = executorType == null ? this.defaultExecutorType : executorType;executorType = executorType == null ? ExecutorType.SIMPLE : executorType;Object 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 (this.cacheEnabled) {//设置为true才执行的executor = new CachingExecutor((Executor)executor);}Executor executor = (Executor)this.interceptorChain.pluginAll(executor);        return executor;}
...

要使用二级缓存除了上面一个配置外,我们还需要在我们每个DAO对应的Mapper.xml文件中定义需要使用的cache

...
<mapper namespace="...UserMapper"><cache/><!-- 加上该句即可,使用默认配置、还有另外一种方式,在后面写出 -->...
</mapper>

具体可以看org.apache.ibatis.executor.CachingExecutor类的以下实现 
其中使用的cache就是我们在对应的Mapper.xml中定义的cache。

    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {BoundSql boundSql = ms.getBoundSql(parameterObject);CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);        return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}    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) {//第一个条件 定义需要使用的cache  this.flushCacheIfRequired(ms);            if (ms.isUseCache() && resultHandler == null) {//第二个条件 需要当前的查询语句是配置了使用cache的,即下面源码的useCache()是返回true的  默认是truethis.ensureNoOutParams(ms, parameterObject, boundSql);List<E> list = (List)this.tcm.getObject(cache, key);                if (list == null) {list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);                    this.tcm.putObject(cache, key, list);}                return list;}}        return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}

还有一个条件就是需要当前的查询语句是配置了使用cache的,即上面源码的useCache()是返回true的,默认情况下所有select语句的useCache都是true,如果我们在启用了二级缓存后,有某个查询语句是我们不想缓存的,则可以通过指定其useCache为false来达到对应的效果。 
如果我们不想该语句缓存,可使用useCache=”false

<select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.String" useCache="false">select<include refid="Base_Column_List"/>from tuserwhere id = #{id,jdbcType=VARCHAR}</select>

cache定义的两种使用方式

上面说了要想使用二级缓存,需要在每个DAO对应的Mapper.xml文件中定义其中的查询语句需要使用cache来缓存数据的。 
这有两种方式可以定义,一种是通过cache元素定义,一种是通过cache-ref元素来定义。 
需要注意的是 
对于同一个Mapper来讲,只能使用一个Cache,当同时使用了和时,定义的优先级更高(后面的代码会给出原因)。 
Mapper使用的Cache是与我们的Mapper对应的namespace绑定的,一个namespace最多只会有一个Cache与其绑定。

cache元素定义

使用cache元素来定义使用的Cache时,最简单的做法是直接在对应的Mapper.xml文件中指定一个空的元素(看前面的代码),这个时候Mybatis会按照默认配置创建一个Cache对象,准备的说是PerpetualCache对象,更准确的说是LruCache对象(底层用了装饰器模式)。 
具体的可看org.apache.ibatis.builder.xml.XMLMapperBuilder中的cacheElement()方法解析cache元素的逻辑。

...    private void configurationElement(XNode context) {        try {String namespace = context.getStringAttribute("namespace");            if (namespace.equals("")) {                throw new BuilderException("Mapper's namespace cannot be empty");} else {                this.builderAssistant.setCurrentNamespace(namespace);                this.cacheRefElement(context.evalNode("cache-ref"));                this.cacheElement(context.evalNode("cache"));//执行在后面this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));                this.resultMapElements(context.evalNodes("/mapper/resultMap"));                this.sqlElement(context.evalNodes("/mapper/sql"));                this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));}} catch (Exception var3) {            throw new BuilderException("Error parsing Mapper XML. Cause: " + var3, var3);}}
...    private void cacheRefElement(XNode context) {        if (context != null) {            this.configuration.addCacheRef(this.builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));CacheRefResolver cacheRefResolver = new CacheRefResolver(this.builderAssistant, context.getStringAttribute("namespace"));            try {cacheRefResolver.resolveCacheRef();} catch (IncompleteElementException var4) {                this.configuration.addIncompleteCacheRef(cacheRefResolver);}}}    private void cacheElement(XNode context) throws Exception {        if (context != null) {String type = context.getStringAttribute("type", "PERPETUAL");Class<? extends Cache> typeClass = this.typeAliasRegistry.resolveAlias(type);String eviction = context.getStringAttribute("eviction", "LRU");Class<? extends Cache> evictionClass = this.typeAliasRegistry.resolveAlias(eviction);Long flushInterval = context.getLongAttribute("flushInterval");Integer size = context.getIntAttribute("size");            boolean readWrite = !context.getBooleanAttribute("readOnly", false).booleanValue();Properties props = context.getChildrenAsProperties();            this.builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, props);//如果同时存在<cache>和<cache-ref>,这里的设置会覆盖前面的cache-ref的缓存}}

空cache元素定义会生成一个采用最近最少使用算法最多只能存储1024个元素的缓存,而且是可读写的缓存,即该缓存是全局共享的,任何一个线程在拿到缓存结果后对数据的修改都将影响其它线程获取的缓存结果,因为它们是共享的,同一个对象。

cache元素可指定如下属性,每种属性的指定都是针对都是针对底层Cache的一种装饰,采用的是装饰器的模式。

  1. blocking:默认为false,当指定为true时将采用BlockingCache进行封装,blocking,阻塞的意思,使用BlockingCache会在查询缓存时锁住对应的Key,如果缓存命中了则会释放对应的锁,否则会在查询数据库以后再释放锁,这样可以阻止并发情况下多个线程同时查询数据,详情可参考BlockingCache的源码。 
    简单理解,也就是设置true时,在进行增删改之后的并发查询,只会有一条去数据库查询,而不会并发

  2. eviction:eviction,驱逐的意思。也就是元素驱逐算法,默认是LRU,对应的就是LruCache,其默认只保存1024个Key,超出时按照最近最少使用算法进行驱逐,详情请参考LruCache的源码。如果想使用自己的算法,则可以将该值指定为自己的驱逐算法实现类,只需要自己的类实现Mybatis的Cache接口即可。除了LRU以外,系统还提供了FIFO(先进先出,对应FifoCache)、SOFT(采用软引用存储Value,便于垃圾回收,对应SoftCache)和WEAK(采用弱引用存储Value,便于垃圾回收,对应WeakCache)这三种策略。 
    这里,根据个人需求选择了,没什么要求的话,默认的LRU即可

  3. flushInterval:清空缓存的时间间隔,单位是毫秒,默认是不会清空的。当指定了该值时会再用ScheduleCache包装一次,其会在每次对缓存进行操作时判断距离最近一次清空缓存的时间是否超过了flushInterval指定的时间,如果超出了,则清空当前的缓存,详情可参考ScheduleCache的实现。

  4. readOnly:是否只读 
    默认为false。当指定为false时,底层会用SerializedCache包装一次,其会在写缓存的时候将缓存对象进行序列化,然后在读缓存的时候进行反序列化,这样每次读到的都将是一个新的对象,即使你更改了读取到的结果,也不会影响原来缓存的对象,即非只读,你每次拿到这个缓存结果都可以进行修改,而不会影响原来的缓存结果; 
    当指定为true时那就是每次获取的都是同一个引用,对其修改会影响后续的缓存数据获取,这种情况下是不建议对获取到的缓存结果进行更改,意为只读(不建议设置为true)。 
    这是Mybatis二级缓存读写和只读的定义,可能与我们通常情况下的只读和读写意义有点不同。每次都进行序列化和反序列化无疑会影响性能,但是这样的缓存结果更安全,不会被随意更改,具体可根据实际情况进行选择。详情可参考SerializedCache的源码。

  5. size:用来指定缓存中最多保存的Key的数量。其是针对LruCache而言的,LruCache默认只存储最多1024个Key,可通过该属性来改变默认值,当然,如果你通过eviction指定了自己的驱逐算法,同时自己的实现里面也有setSize方法,那么也可以通过cache的size属性给自定义的驱逐算法里面的size赋值。

  6. type:type属性用来指定当前底层缓存实现类,默认是PerpetualCache,如果我们想使用自定义的Cache,则可以通过该属性来指定,对应的值是我们自定义的Cache的全路径名称。

cache-ref元素定义

cache-ref元素可以用来指定其它Mapper.xml中定义的Cache,有的时候可能我们多个不同的Mapper需要共享同一个缓存的 
是希望在MapperA中缓存的内容在MapperB中可以直接命中的,这个时候我们就可以考虑使用cache-ref,这种场景只需要保证它们的缓存的Key是一致的即可命中,二级缓存的Key是通过Executor接口的createCacheKey()方法生成的,其实现基本都是BaseExecutor,源码如下。

    public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {        if (this.closed) {            throw new ExecutorException("Executor was closed.");} else {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();            for(int i = 0; i < parameterMappings.size(); ++i) {ParameterMapping parameterMapping = (ParameterMapping)parameterMappings.get(i);                if (parameterMapping.getMode() != ParameterMode.OUT) {String propertyName = parameterMapping.getProperty();Object value;                    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 = this.configuration.newMetaObject(parameterObject);value = metaObject.getValue(propertyName);}cacheKey.update(value);}}            return cacheKey;}}

打个比方我想在MenuMapper.xml中的查询都使用在UserMapper.xml中定义的Cache,则可以通过cache-ref元素的namespace属性指定需要引用的Cache所在的namespace,即UserMapper.xml中的定义的namespace,假设在UserMapper.xml中定义的namespace是cn.chenhaoxiang.dao.UserMapper,则在MenuMapper.xml的cache-ref应该定义如下。

<cache-ref namespace="cn.chenhaoxiang.dao.UserMapper"/>
  • 1

这样这两个Mapper就共享同一个缓存了

自定义cache就不介绍了。

测试二级缓存

 

查询测试

/*** Created with IntelliJ IDEA.* User: 陈浩翔.* Date: 2018/1/10.* Time: 下午 10:15.* Explain:*/@RunWith(SpringJUnit4ClassRunner.class)//配置了@ContextConfiguration注解并使用该注解的locations属性指明spring和配置文件之后@ContextConfiguration(locations = {"classpath:spring.xml","classpath:spring-mybatis.xml"})public class MyBatisTestBySpringTestFramework {//注入userService@Autowiredprivate UserService userService;    @Testpublic void testGetUserId(){String userId = "4e07f3963337488e81716cfdd8a0fe04";User user = userService.getUserById(userId);System.out.println(user);        //前面说到spring和MyBatis整合User user2 = userService.getUserById(userId);System.out.println("user2:"+user2);}
}

 

接下来我们把Mapper中的cache元素删除,不使用二级缓存

 

再运行测试

 

对二级缓存进行了以下测试,获取两个不同的SqlSession(前面有说,Spring和MyBatis集成,每次都是不同的SqlSession)执行两条相同的SQL,在未指定Cache时Mybatis将查询两次数据库,在指定了Cache时Mybatis只查询了一次数据库,第二次是从缓存中拿的。

Cache Hit Ratio 表示缓存命中率。 
开启二级缓存后,每执行一次查询,系统都会计算一次二级缓存的命中率。 
第一次查询也是先从缓存中查询,只不过缓存中一定是没有的。 
所以会再从DB中查询。由于二级缓存中不存在该数据,所以命中率为0.但第二次查询是从二级缓存中读取的,所以这一次的命中率为1/2=0.5。 
当然,若有第三次查询,则命中率为1/3=0.66 
0.5这个值可以从上面开启cache的图看出来,0.0的值未截取到~漏掉了~

注意: 
增删改操作,无论是否进行提交sqlSession.commit(),均会清空一级、二级缓存,使查询再次从DB中select。 
说明: 
二级缓存的清空,实质上是对所查找key对应的value置为null,而非将

二级缓存的使用原则

  1. 只能在一个命名空间下使用二级缓存 
    由于二级缓存中的数据是基于namespace的,即不同namespace中的数据互不干扰。在多个namespace中若均存在对同一个表的操作,那么这多个namespace中的数据可能就会出现不一致现象。

  2. 在单表上使用二级缓存 
    如果一个表与其它表有关联关系,那么久非常有可能存在多个namespace对同一数据的操作。而不同namespace中的数据互补干扰,所以就有可能出现多个namespace中的数据不一致现象。

  3. 查询多于修改时使用二级缓存 
    在查询操作远远多于增删改操作的情况下可以使用二级缓存。因为任何增删改操作都将刷新二级缓存,对二级缓存的频繁刷新将降低系统性能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/324079.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

.net core 2.0学习笔记(一):开发运行环境搭建

期待已久的.net core 2.0终于发布了&#xff01;大家等的花儿都谢了。 不过比预期提前了一个多月&#xff0c;这在微软历史上还真的不多见。按照历史经验看&#xff0c;2.0版本应该比较靠谱&#xff0c;我猜这也是社区非常火爆的原因吧。下面就简单分享一下.net core2.0开发运行…

不好意思,你这个加分理由不行……

大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号&#xff1a;雄雄的小课堂。周五了&#xff0c;又该周测了&#xff0c;今天和以往一样&#xff0c;上午前两节课都在上课&#xff0c;第三节课进行测试&#xff0c;这周的填空题有点儿多&#xff0c;所以考试的时间较…

SQL索引一步到位

转载自 SQL索引一步到位 SQL索引在数据库优化中占有一个非常大的比例&#xff0c; 一个好的索引的设计&#xff0c;可以让你的效率提高几十甚至几百倍&#xff0c;在这里将带你一步步揭开他的神秘面纱。 1.1 什么是索引&#xff1f; SQL索引有两种&#xff0c;聚集索引和非聚…

jzoj3794,P1383-高级打字机【欧拉序,离线O(n)】

正题 题目链接&#xff1a;https://www.luogu.org/problemnew/show/P1383 大意 三个操作 T c&#xff1a;加入一个字符c U x&#xff1a;撤销前x次操作&#xff08;只包括T和U&#xff09; Q x&#xff1a;询问当前第x个字符 解题思路 对于50%的数据U不会撤销到U 所以我们可…

你也可以做一个简易抽奖程序!

大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号&#xff1a;雄雄的小课堂。今天给大家分享一个使用winform制作的小案例——随机点名&#xff08;抽奖&#xff09;程序&#xff0c;下面我们来看看运行结果&#xff1a;在班内点名为了公平起见&#xff0c;一直使用的…

ASP.NET Core 2.0 特性介绍和使用指南

ASP.NET Core 2.0 发布日期&#xff1a;2017年8月14日 ASP.NET团队宣布ASP.NET Core 2.0正式发布&#xff0c;发布Visual Studio 2017 15.3支持ASP.NET Core 2.0&#xff0c;提供新的Razor Pages项目模板。 详细发布信息查看.NET Core 2.0.0发布说明文档 最新版SDK下载&…

blog项目中遇到的问题及解决

1、dependencesmangement 只做资源定位 2、多模块开发中mapper扫描 3、lambda表达式简写时&#xff08;使用lombox建造者模式&#xff09; 4、新增评论时 使用mp的自动填充时userid未赋值

你在学校我安排了你没有做到最多凶你一顿,在公司不一样,直接得让走人!...

今天放一部分聊天记录吧~毛帅龙同学穆老师我今天跟着公司做项目了雄雄的小课堂可以呀雄雄的小课堂厉害了雄雄的小课堂好好干哈雄雄的小课堂等有空了出一套若依的文档雄雄的小课堂给大家分享分享毛帅龙同学那个ruoyi视频买了雄雄的小课堂199&#xff1f;毛帅龙同学现在每天看两个…

2018/7/10-纪中某C组题【jzoj3792,jzoj3793,jzoj3794】

前言 由于B组题目太残酷&#xff0c;忒容易爆零&#xff0c;于是我就回到了C组温暖的怀抱 今日说法分数 正题 T1&#xff1a;jzoj3792,P2062-分队问题【贪心】 博客链接&#xff1a;https://blog.csdn.net/mr_wuyongcong/article/details/80988719 T2&#xff1a;jzoj3793,…

ASP.NET Core 源码学习之 Logging[2]:Configure

在上一章中&#xff0c;我们对 ASP.NET Logging 系统做了一个整体的介绍&#xff0c;而在本章中则开始从最基本的配置开始&#xff0c;逐步深入到源码当中去。 默认配置 在 ASP.NET Core 2.0 中&#xff0c;对默认配置做了很大的简化&#xff0c;并把一些基本配置移动到了程序…

两个月拿到N个offer,看看我是如何做到的

转载自 两个月拿到N个offer&#xff0c;看看我是如何做到的 前言&#xff1a; 北京-三年经验-Java&#xff0c;在金三银四这两个月期间&#xff08;在五月初还去面试了几家&#xff0c;主要是三四月份期面试剧居多&#xff09;&#xff0c;我跳槽面试&#xff0c;前前后后我…

“小朋友”们节日快乐呀~

大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号&#xff1a;雄雄的小课堂。今天是六月一日——儿童节&#xff0c;看了看朋友圈&#xff0c;不管是大孩子还是小孩子&#xff0c;都在过节&#xff0c;哈哈哈。最近四班一直在做项目&#xff0c;一共6个小组&#xff…

Entity Framework Core 2.0 全局查询过滤器

本博文翻译自&#xff1a;http://gunnarpeipman.com/2017/08/ef-core-global-query-filters/ Entity Framework Core 2.0 全局查询过滤器 Entity Framework Core 2.0引入了全局查询过滤器&#xff0c;可以在创建模型时应用到实体 。它使得构建多租户应用程序和支持对实体 的软…

SpringCloudGateway

文章目录SpringCloudGateway起步消费端整合SpringCloudGateway静态路由配置内置扩展网关过滤内置网关过滤自定义过滤全局过滤器内置全局过滤器自定义全局过滤器ForwardRoutingFilterNetty全局路由响应式负载均衡代理GatewayMetricsFilter网关度量过滤器&#xff08;服务监控&am…

来自一位家长的肺腑之言,句句在理!!!

大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号【雄雄的小课堂】。今天分享一段话&#xff0c;原创来自三班的一位同学家长&#xff0c;可所谓字字在理&#xff01;看完之后我自己也反思了好多&#xff0c;主要是思想观念的转变&#xff0c;就像佟老师给我说的一样…

jzoj1295,P1607-轻轨(庙会班车)【贪心,线段树】

前言 我考试时敲了一个不仅比正解编程复杂度高&#xff0c;而且时间更慢&#xff0c;还AC不了的费用流 垃圾代码 #include<cstdio> #include<cstring> #include<algorithm> #include<queue> #define MN 20011 using namespace std; struct node{int…

一篇文章了解RPC框架原理

转载自 一篇文章了解RPC框架原理 1.RPC框架的概念 RPC&#xff08;Remote Procedure Call&#xff09;–远程过程调用&#xff0c;通过网络通信调用不同的服务&#xff0c;共同支撑一个软件系统&#xff0c;微服务实现的基石技术。使用RPC可以解耦系统&#xff0c;方便维护…

winform分页案例简单实现方式~

大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号&#xff1a;雄雄的小课堂。最近&#xff0c;四班在做KTV点歌系统&#xff0c;正好需要用到分页的内容&#xff0c;所以今天我就整理整理&#xff0c;写了一个简易的winfrom分页案例&#xff0c;以下是案例截图&#…

.Net Core下通过Proxy 模式 使用 WCF

.NET Core下的WCF客户端也是开源的&#xff0c;这次发布.NET Core 2.0,同时也发布了 WCF for .NET Core 2.0.0, 本文介绍在.NET Core下如何通过Proxy 消费WCF服务。 我们现在直接可以在 standard 2.0下调用wcf服务了&#xff0c;不过 Microsoft WCF Web Service Reference Pro…

JWT 入门

文章目录使用JWT的原因JWT结构JWT入门案例Token拦截使用JWT的原因 为了保护项目之中的数据资源&#xff0c;那么一定就需要采用认证检测机制&#xff0c;于是SpringCloud进行认证处理&#xff0c;就可以使用SpringSecurity 来实现了&#xff0c;但是如果你真的去使用了SpringSe…