缓存的重要性是不言而喻的。使用缓存, 我们可以避免频繁的与数据库进行交互, 尤其是在查询越多、缓存命中率越高的情况下, 使用缓存对性能的提高更明显。

同样地,mybatis作为ORM框架,也必然会支持缓存

它分别支持一级缓存和二级缓存。其中一级缓存是sqlSession级的缓存,而二级缓存则可以实现多个sqlSession间的缓存

什么意思?往下看喽~

一 一级缓存

01. 什么是一级缓存

之所以说mybatis的一级缓存是sqlSession级的,是因为它只支持同一个sqlSession下的缓存,也就是说缓存只在同一个sqlSession之间共享

02.如何实现一级缓存

一级缓存分为两个范围:statementsession

statement:说白了就是一个sql语句

session:有数据库产生的一个连接,即一个sqlsession

而mybatis默认支持一级缓存,不需要专门进行配置,并且它支持session范围的一级缓存。

针对缓存属性,mybatis通过类org.apache.ibatis.sessionConfiuration进行了配置,我们可以看到localCacheScope的默认级别为SESSION(并且二级缓存的也是默认开启的)

注意:Configuration类中的cacheEnabled属性是针对二级缓存的开关控制,而不是针对一级缓存的。一级缓存完全不需要进行配置,它并没有开关,是Mybatis默认支持的

那么,也就是说,我直接运行服务,一级缓存就生效了?

试试看,进入一级缓存测试环节~

03. 一级缓存测试

1)在同一个方法中调用两次相同的方法

public void testCache(){    User user = userMapperWithAnnotation.findById(33L);    log.info("Find User: {}", user);    User user2 = userMapperWithAnnotation.findById(33L);    log.info("Find User2: {}", user2);}

2)直接在run方法中进行调用

@SpringBootApplication@Slf4j@MapperScan("com.shumile.springbootmybatis.mapper")public class SpringBootMybatisApplicationimplements ApplicationRunner {    @Autowired    private UserMapperWithAnnotation userMapperWithAnnotation;        public static void main(String[] args) {        SpringApplication.run(SpringBootMybatisApplication.class, args);    }    @Override    public void run(ApplicationArguments args) throws Exception {          testCache();    }

我们预期的效果是,既然mybatis默认已经支持一级缓存,那么我执行两个一模一样的方法,肯定只需要查询一次数据库了,第二次就应该直接从缓存中取结果了3)运行代码,如下图所示

哈?什么情况?骗人呢?打印了两条sql语句,这不还是查询了两次吗?

说实话,这个问题曾经困扰了我好几个小时,我在想,难道网上说的都有问题吗?是不是对于一级缓存,还专门有什么特殊配置呢?

最后呢,通过源码的跟踪,终于豁然开朗~

04. 源码分析

我们跟踪findById()方法的执行,步骤如下

1)首先会进入DefaultSqlSession的selectOne()方法

2)接着,会进入selectList()方法

我们知道在Confifuration类中已经默认配置了一级缓存的支持范围,在执行查询语句的时候,我们拿到的对应配置对象 就是默认支持session范围内的缓存(依旧先不用管cacheEnabled属性)。

那么,这块是没有问题的

3)往下走,进入CachingExecutor的query()方法

根据参数,获取到对应的可执行sql语句之后,进入创建缓存key的方法createCacheKey(),并将获取到的key作为参数传给下面的query()方法

在这里,先暂停,我们进入createCacheKey()方法内部,看看这个Key是如何确定的

4)createCacheKey()实现探究

它通过分别执行cacheKey.update()方法,将Statement.id、Offset、Limmit、Sql以及Params,这五个属性分别,放入cacheKey中的updateList中。其中update()方法如下

再来看看,cacheKey.equals()方法

很显然,除去hashcode,checksum和count的比较外,只要updatelist中的元素对应相等了,就可以认为是CacheKey相等。也就是说,只要两条Sql的Statement.id、Offset、Limmit、Sql以及Params这五个值相同,即可以认为是相同的Sql

看完了对于key的组装环节,接着继续往下走~

5)在缓存中找key

往下执行,在query()方法中,会首先通过key值去缓存中取,如果缓存中没有,即获取到的list为为空,则需要去数据库中查询

6)执行具体的数据库查询方法

7)将结果存入缓存

执行以后,会通过localCache.putObject(key,list)将当前执行的结果放在本地缓存中

8)提交结果

这一步结束以后,便进入到了commit()方法

我们看到,在执行commit()方法时,会清空本地缓存。那么以后再次查询时,缓存中总是找不到对应的key值,就会出现每次都重新执行sql语句,去数据库中查询的现象了

那么,我们便很容易就知道了,为什么会不支持一级缓存了。

原来,要想支持一级缓存,就得要保证在这些sql语句全部执行完以后,再去执行commit()方法,也就是说,我们的方法必须要在同一个事务内,才会支持

那就一起来验证一下,开启了注解的情况~

1)开启事务

接下来,我们对方法开启事务,在启动类添加@EnableTransactionManagement注解,并在run()方法上添加@Transactional注解

然后进入验证环节

2)验证环节

第一次查询的执行过程,跟上面的基本一样。同样是从数据库中查询得到结果,并将结果存放到缓存中第二次查询注意了,关键就在第二次查询
会继续判断从缓存中取对应key的值,这次我们可以取到key的value值,即它的查询结果,直接将这个结果集返回即可

直到执行完两条语句之后,进入commit()方法,进行事务提交操作
看下执行结果图

sql语句只执行了一次,那么说明验证成功~

小结

为什么会出现不开启事务时,一级缓存不生效;开启了事务,一级缓存生效?

很显然,那是因为它每条语句执行结束以后,都会执行提交方法,而提交方法在每次都会清空本地缓存。而开启了事务的话,方法是在所有操作结束以后才会提交,因此就会支持一级缓存啦

二 二级缓存

01. 什么是二级缓存

一级缓存中,是一个sqlSession使用一个缓存,而mybatis的二级缓存,则支持多个SqlSession之间共享缓存。mybatis默认开启二级缓存

02. 如何二级缓存

从第一节中对一级缓存的源码分析中,我们也提到了,在Configuration类中,已经默认开启了二级缓存(cacheEnabled=true)
除了这个参数,还需要在mapper.xml文件中,添加cache或者cache-ref标签,进行缓存配置
如下所示

cache标签用于声明namespace使用二级缓存,并且可以通过以下属性自定义配置

  • type:cache使用的类型,默认是PerpetualCache
  • eviction:定义回收的策略,常见的有FIFO,LRU
  • flushInterval:配置一定时间自动刷新缓存
  • size:最多缓存对象的个数
  • readOnly:是否只读,若配置可读写,则需要对应的实体类能够序列化
  • blocking:配置若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存

cache-ref代表引用别的命名空间的Cache配置,两个命名空间的操作使用的是同一个Cache

要想实现两个命名空间共享缓存,那么可以cache-ref标签的namespace属性引入另一个命名空间,如:

--标签说明参考自:

https://tech.meituan.com/2018/01/19/mybatis-cache.html03. 二级缓存测试

要想验证mybatis的二级缓存,就需要构造多个不同sqlsession的操作,验证在这些sqlsession之间能够实现缓存共享

1)创建mybatis-configuration.xml文件

<?xml version="1.0" encoding="UTF-8"?>

2)我们通过这个配置文件,分别定义两个sqlsession,进行同样的查询操作

调用方法执行结果如下

我们看到,并没有实现缓存的共享,即二级缓存失效

3)第二次测试

我们在执行第二个sqlsession的操作之前,先执行sqlSession1的提交

执行代码,如下所示

我们看到,二级缓存成功实现了

咦~这又是为什么呢?

这是因为呢,只有第一个sqlSession执行了提交操作,第二个sqlSession才能感应到,然后才能获取到这个缓存

那么,如果我们的数据发生了变化,它肯定就不能再去取缓存中的数据了,否则我们在页面中看到的数据就不是最新的了

这一点,mybatis也为我们考虑到了,我们来看看如果我们在操作中间,执行了更新表的操作,情况是怎样的

4)第三次测试

如下所示,在两个查询操作之间,我们执行了另一个sqlSession的update()方法

执行结果,如下所示

在最后一次查询操作时,同样执行了sql语句,那么,这种情况下,二级缓存就失效
此外,如果我们在执行过程中,执行了多表操作,即如果A表和B表相关联,若对A表执行了更新操作,B表并不能够感知到,从而会拿到脏数据,影响正常的业务逻辑
二级缓存的实现原理,大家可以参照一级缓存中跟踪源码的方式,自己跟着代码再进行更进一步的探究,有任何问题也可以随时与我留言沟通哦~

小结

  1. mybatis通过cacheEnabled来进行二级缓存的开启与关闭配置,默认是开启状态
  2. 使用时,还需要在mapper.xml中通过cache标签开启命名空间内部的缓存。当然也可以通过cache-ref加入其它命名空间,进行二级缓存的共享
  3. Mybatis对多表查询有局限性,容易出现读到脏数据。建议使用第三方缓存实现

三 总结 总而言之

今天主要聊了聊Mybatis的一级缓存和二级缓存

mybatis默认支持一级缓存,不需要开关设置,但必须是在事务开启的情况下才会生效,具体原因,我们也通过跟踪查询语句的执行过程理解了

而二级缓存,必须在前面的sqlSession提交事务之后,才能够支持,并且使用具有局限性,每一个sqlSession执行完之后,必须进行提交操作,其他sqlSession擦能感应到变化,对于多表操作容易拿到脏数据等缺陷

个人更建议在项目使用集中式缓存,比如使用redis进行数据的存储,对于分布式的场景,我们也能够保证缓存不失效,并且不会读到脏数据,从而保证业务一致性

技能总结

1、mybatis一级缓存的实现方式与实现原理2、mybatis二级缓存的实现方式与失效场景


3、两级缓存使用情况总结与建议
温馨提示
项目中如果出现一些不符合认知的代码逻辑问题,除了四处搜索之外,也不妨试试跟踪源码一步步分析解决。这样不仅了解了其中的原理,同时还会极大锻炼你解决问题的能力哦~建议试试,相信我,时间久了,你会觉得代码其实也是香的呢


嗯,就这样。每天学习一点,时间会见证你的强大。

下期预告:Spring Boot(八):Spring Boot的监控特性
本期项目代码已上传到github~有需要的可以参考

https://github.com/wangjie0919/Spring-Boot-Notes

往期精彩回顾

Spring Boot(六):那些好用的数据库连接池们

Spring Boot(五):春眠不觉晓,Mybatis知多少

Spring Boot(四):让人又爱又恨的JPA

Spring Boot(三):操作数据库-Spring JDBC

SpringBoot(二):第一个Spring Boot项目

SpringBoot(一):特性概览

mybatis 不生效 参数_Spring Boot(七):你不能不知道的Mybatis缓存机制相关推荐

  1. 高手才知道!七个你所不知道的 D3.js 秘技

    D3.js ,当前最火红的视觉化套件,你用过了吗?越来越多人使用 D3.js 来开发视觉化专题,但- 你对 D3.js 的了解又到哪里呢?这次我们就带大家一起来看看一些 D3.js 很重要.大家却又普 ...

  2. mybatis 不生效 参数_MyBatis参数使用@Param注解获取不到自增id问题

    一.背景 群里有个哥们分享了一个mybatis的小"坑". "分享一个菜鸡点:mybatis中使用@param注解后,要keyProperty="注解名.id& ...

  3. mybatis 不生效 参数_Mybatis-日志配置

    日志 Mybatis 的内置日志工厂提供日志功能,内置日志工厂将日志交给以下其中一种工具作代理: SLF4J Apache Commons Logging Log4j 2 Log4j JDK logg ...

  4. springboot 单测加入参数_Spring Boot集成Elasticsearch实战分享

    作者|java梦想口服液|简书 最近有读者问我能不能写下如何使用 Spring Boot 开发 Elasticsearch(以下简称 ES) 相关应用,今天就讲解下如何使用 Spring Boot 结 ...

  5. i18n调用自己参数_Spring Boot :I18N

    什么是 I18N I18N 是 "国际化" 的简称,对于程序来说,在不修改内部代码的情况下,根据不同语言及地区显示相应的界面 Spring Boot 结合 I18N 1. Spri ...

  6. bean validation校验方法参数_Spring Boot 之使用 validation 验证参数

    前言 文本已收录至我的GitHub仓库,欢迎Star:https://github.com/bin392328206/six-finger种一棵树最好的时间是十年前,其次是现在 我知道很多人不玩qq了 ...

  7. springboot上传文件同时传参数_Spring Boot 系列:使用 Spring Boot 上传文件

    上传文件是互联网中常常应用的场景之一,最典型的情况就是上传头像等,今天就带着带着大家做一个 Spring Boot 上传文件的小案例. 1.pom 包配置 我们使用 Spring Boot 版本 2. ...

  8. Spring Boot 属性配置你所不知道的细节

    今天我们要聊的这个问题,可能工作5年的资深程序员也不一定搞得很清楚,但是我敢保证在开发 Web 应用过程中大家都遇到过. 这个问题就是: Spring Boot 应用程序读取配置属性时,不同配置源的优 ...

  9. 你所不知道的mybatis居然也有拦截器

    对于mybatis的拦截器这个想法我来自于三个地方 也就是下面这个三个地方是可以使用的,其他的情况需要开发人员根据实际情况来使用. 1.对于分页的查询,我们可以对于分页的方法采用比较规范的命名,然后根 ...

最新文章

  1. # 2021华为软件精英挑战赛C/C++——build.sh/build_and_run.sh/CodeCraft_zip.sh注释
  2. 第一记: JS变量类型判断(VUE源码解读)
  3. 【渝粤教育】国家开放大学2018年春季 0688-21T老年精神障碍护理 参考试题
  4. 一个很好的自学网站~推荐一下
  5. jeecgboot前端开发_Jeecg-Boot 技术文档
  6. linux sudo提权
  7. 循环执行次数 n(n+1)/2
  8. Python开发的一个IDE推荐,Sublime Text 3
  9. 基于STM32的简易交通灯设计
  10. IDC中国大型企业SaaS云服务市场:金蝶位居第一
  11. 10.2项目干系人管理+信息系统项目管理+野马合集
  12. appleid注册服务器错误,连接apple id 服务器时出错(Apple ID 验证连接失败,试试这招)...
  13. 【经验贴】小汽车科目二科目三 经验
  14. jee6 学习笔记 5 - Struggling with JSF2 binding GET params
  15. cf-1327F. AND Segments
  16. 单词长度和GPS数据处理——程序设计入门C语言
  17. IDEA如何配置Tomcat?
  18. Centos7 本地IOS配置本地yum源
  19. WiFi路由器存在226个漏洞
  20. 字符串转换成整数-----字符串‘123456’转换成数值123456(指针)

热门文章

  1. java 例子一对小兔子,Java解决标题:有一对兔子,从出生第三个月起每个月都生一对兔子,小兔子长到第三个月后,每个月又生一对兔子。...
  2. CentOs下部署Core环境
  3. CAS在Java类中的应用
  4. linux下使用./configure报-bash: ./configure: No such file or directory
  5. MyBatis学习--高级映射
  6. 转:什么是Node.js?
  7. 火星地形地貌图,摄影:“祝融号”火星车
  8. 高通平台Bring-up
  9. HTML配置CSDN自定义栏目
  10. Mac自带嗅探器和Wireshake抓包(三)