MyBatis-二级缓存
MyBatis-二级缓存
学习核心
- 二级缓存概念核心
- 二级缓存失效条件
- 二级缓存原理分析
- 使用二级缓存的注意事项
学习资料
查询缓存概念
什么是查询缓存?
MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。MyBatis 3 中的缓存实现的很多改进都已经实现了,使得它更加强大而且易于配置。 MyBatis支持一级缓存(会话级别)、二级缓存(mapper级别)、三级缓存(分布式)
MyBatis提供查询缓存用于减轻数据压力,提供数据库性能。
查询缓存(本地缓存):基于PerpetualCache,本质上是一个HashMap ,分为一级缓存、二级缓存
MyBatis提供了两级缓存,一级缓存和二级缓存:
一级缓存:
- 基于PerpetualCache的HashMap本地缓存,默认是开启的
- 基于SqlSession(会话)级别的缓存,在操作数据库需要构建SqlSession对象。在这个对象中有一个数据结构HashMap,用于存储缓存数据,不同的sqlSession之间的缓存数据是互不影响的
二级缓存
- 默认基于PerpetualCache的HashMap存储,默认是关闭的
- 基于mapper级别的缓存,多个sqlSession之间可以操作通过一个Mapper的sql语句,多个sqlSession可以共用二级缓存,二级缓存是跨SqlSession的
三级缓存:基于分布式级别的缓存概念
如果缓存中有的数据直接从缓存中操作,不需要从数据库获取,大大提高系统的性能
MyBatis中缓存的执行顺序:二级缓存 =》一级缓存 =》 数据库查询
缓存数据的更新机制(缓存数据什么时候回清理)
当某一个作用域(一级缓存sqlSession/二级缓存Namespace)进行了新增、修改、删除操作之后,该作用域下的所有select中的缓存都会被clear
案例分析
1.一级缓存(sqlSession级别)
一级缓存时基于SqlSession(会话)级别的,在测试一级缓存的时候的场景是:针对同一个sqlSession多次调用同一个mapper方法(执行相同的sql语句)会出现什么样的情况
案例1:同一个sqlSession查找同一条数据
参考上述案例(此处暂不考虑二级缓存),针对同一个sqlSession操作,当第一次查找的时候,先查询缓存中的数据,一级缓存中没有相关的数据,需要发送sql从数据库中查找获取相应的用户信息,但完成查找之后会在一级缓存中存入已查找过的用户信息
在第二次查找的时候,从缓存中获取数据,由于查找的是相同的数据,此时一级缓存中已经存在相关的用户信息,因此可以直接获取,而不需要发送sql进行查找,从而显示上述测试结果
案例1:同一个sqlSession先查询后修改,然后再次查询
参考上述案例(此处暂不考虑二级缓存),针对同一个sqlSession操作:
- 第一次查找:先查询一级缓存中的数据,一级缓存没有命中,执行sql从数据库中检索,检索成功后将数据存入一级缓存
- 修改数据:修改数据并提交,此时会相应清空一级缓存相关信息
- 第二次查找:先查询一级缓存中的数据,由于此处缓存中的内容已经被前面的修改操作触发清理,因此此处无法命中缓存,需要访问数据库获取数据随后将数据存入一级缓存
2.二级缓存(namespace级别)
二级缓存是基于mapper级别(namespace)的缓存,即对同一个命名空间的所有操作语句都影响着一个共同的cache,二级缓存被多个sqlSession共享,当开启了二级缓存后MyBatis数据查询的执行流程:二级缓存 =》一级缓存 =》 数据库查询
二级缓存默认是不开启的,要开启二级缓存,需要进行配置
- 步骤1:核心配置文件中开启二级缓存开关
<settings> <setting name = "cacheEnabled" value = "true" /> </settings>
- 步骤2:指定的SQL 映射文件中开启二级缓存:
<cache/>
- 步骤3:二级缓存需要缓存的数据(相关的POJO类)要实现序列化(
implement Serializable
)
案例1:针对不同的sqlSession操作,请求查找同一条数据
开启了二级缓存,此时MyBatis查询操作的执行流程为:二级缓存 =》一级缓存 =》数据库查询
结合上述案例,针对多个不同的sqlSession操作请求查询同一条数据,此处分析如下:
第1次查找:sqlSession1
- 检索:先从二级缓存检索,二级缓存中没有数据则继续跟踪一级缓存,一级缓存中也没有数据,则访问数据库进行查找。查找成功后将数据分别放入二级缓存、一级缓存中
- 关闭sqlSession1:一级缓存会被清空
第2次查找:sqlSession2
- 检索:先从二级缓存检索,此时二级缓存中会有数据,则直接命中返回
- 关闭sqlSession2:一级缓存会被清空
第3次查找:sqlSession3
- 检索:先从二级缓存检索,此时二级缓存中会有数据,则直接命中返回
- 关闭sqlSession3:一级缓存会被清空
综上说书分析,后面的2次查找都是直接从二级缓存中获取数据返回(命中缓存),因此后面的2次查找不会请求数据库,而是直接返回数据(有相应的缓存命中率参考)
二级缓存失效的条件
失效场景1:第一次sqlSession未提交
例如对于同一条数据,操作多个sqlSession先后执行查询、更新(不提交)、查询操作
- 首次查询:从数据库中获取数据并更新到一级缓存、二级缓存(如果sqlSession关闭则清空一级缓存)
- 更新操作:更新操作执行但是不提交,此时不会触发缓存清空操作(如果sqlSession关闭则清空一级缓存)
- 再次查询:先从二级缓存检索,此时可以检索数据(检索的内容是首次查询的时候存入二级缓存中的数据)
因为更新操作没有提交,所以不会触发二级缓存的clear操作,因此在后面的查询操作中对这次更新操作是无感知的,故出现”二级缓存失效“的情况
失效场景2:更新操作对二级缓存的影响
例如对于同一条数据,操作多个sqlSession先后执行查询、更新(提交)、查询操作
- 首次查询:从数据库中获取数据并更新到一级缓存、二级缓存(如果sqlSession关闭则清空一级缓存)
- 第2次查询:命中缓存,直接获取数据
- 更新操作:执行更新操作并提交,此时一级缓存、二级缓存会触发clear
- 第3次查询:缓存中没有数据,会重新从数据库中获取并更新缓存
失效场景3:多表操作对二级缓存的影响
场景分析:有两个表,部门表dept(deptNo,dname,loc)和 部门数量表deptNum(id,name,num),其中部门表的名称和部门数量表的名称相同(dept.dname = deptNum.name),通过名称能够联查两个表可以知道其坐标(loc)和数量(num),现在要对部门数量表的 num 进行更新,然后再次关联dept 和 deptNum 进行查询,获取到的num是什么?
此处涉及到不同mapper操作:
- 首次查询:mapper1 执行联查操作
- 首次查询,从数据库中获取数据,并更新到缓存中(假设此时查询获取的num为1000)
- 更新操作:mapper2执行更新(更新部门数量表的num信息,在相应的DeptNumMapper中操作)
- 更新、提交,此时针对mapper2的二级缓存会clear(更新num为1500)
- 再次查询:再次使用mapper1执行联查操作
- 此时会从mapper1相关的二级缓存中获取数据(而此时二级缓存中存储的是1000)
结合上述案例分析,在同一组操作中,后面的查询结果显然不是所希望的,正常来说应该读取更新后的数据,但是此时读取到的却是以往的”脏数据“,并不如预期。
失效场景分析:可以理解为 缓存的作用域范围内(一级缓存sqlSession级别、二级缓存namespace级别)如果执行了新增、修改、删除操作,就会触发缓存clear操作。结合这一定义分析,就能理解这些所谓缓存失效的场景
使用二级缓存的注意事项
- 二级缓存是基于namespace级别的,不同namespace下的操作互不影响,不依赖于sqlSession
- 二级缓存刷新条件:insert,update,delete操作会清空所在
namespace
下的全部缓存 - 单表独立:一般使用MyBatis Generator生成的代码中,都是各个表独立的,每个表都有自己的
namespace
- 多表操作可能衍生的脏数据问题(参考上述失效场景分析):如果是多表操作,可能涉及到不同namespace的交互,可能会导致脏数据的产生。在多表联查的环境中不建议开启二级缓存