Mybatis 中的一级缓存与二级缓存

2022-10-22 12:07:20

一,Mybatis中为什么要有缓存

  缓存的意义是将用户经常查询的数据放入缓存(内存)中去,用户去查询数据的时候就不需要从磁盘(关系型数据库)中查询,直接从缓存中查询,从而提高了查询效率,解决了高并发中系统的性能问题。Mybatis中提供一级缓存与二级缓存。
在这里插入图片描述
  Mybatis的一级缓存是一个SqlSession级别的缓存,只能访问自己的一级缓存数据,而二级缓存是Mapper级别的缓存,是跨SqlSession的,不同的SqlSession是可以共享缓存数据的。
在这里插入图片描述

二,一级缓存

  Mybatis 一级缓存原理:
在这里插入图片描述
  第一次发出查询请求,sql 查询的结果写入SqlSession的一级缓存当中,缓存使用的数据结构是一个map<key, value>

  1. key : hashcode + sql + sql输入参数 + 输出参数 (sql的唯一标识)
  2. value : 用户信息

  同一个SqlSession再次发出相同的sql,就会从缓存中读取而不走数据库,如果两次操作之间出现commit(修改、输出、添加)操作,那么本SqlSession中一级缓存区域全部清空,下次再去缓存中查不到所以要从数据库中查询,从数据库再写入一级缓存。

@TestpublicvoidcreateTable(){SqlSession sqlSession=DBUtil.getSqlSession();List<Map<String,Object>> list1= sqlSession.selectList("com.snow.xml.SnowOracle.getEmployeeByName","周康");System.out.println(list);List<Map<String,Object>> list2= sqlSession.selectList("com.snow.xml.SnowOracle.getEmployeeByName","周康");System.out.println(list2);}

  在数据库中有一张Employee表,里面有一条数据,通过selectList的方法查询,结果如下[{ID=70DD7D10-9FC6-4B79-ADAF-8B408DE1E048, EMPNAME=周康, AGE=36, DMGRP=男, BIRTHDATE=1987-07-31 00:00:00.0, SALARY=17000, ADDRESS=湖南长沙, GRADE=高级架构师}]
在这里插入图片描述
  此时,手动修改数据库该人的年龄,手动修改为35,然后保存
在这里插入图片描述
  此时,在运行代码,查看list2的值,两次结果一致,都是36 并非 35,说明第二次相同的查询走的是SqlSession中的一级缓存。
在这里插入图片描述
  Mybatis 中一级缓存需要注意的点 :

  1. Mybatis 中一级缓存是默认开启的,不需要手动配置。
  2. MybatisSpring 整合后进行mapper 代理开发后,不支持一级缓存。MybatisSpring 整合,Spring 按照mapper 的模板去生成mapper 代理对象,模板中在最后会统一关闭SqlSession

三、二级缓存

  Mybatis二级缓存原理:
在这里插入图片描述
二级缓存的范围是mapper级别(mapper同一个命名空间),mapper以命名空间为单位创建缓存数据结构,结构是map<key, value>。每次查询前看是否开启了二级缓存,如果开启则从二级缓存的数据结构中取缓存数据,如果二级缓存中没有取到,再从一级缓存中取,如果一级缓存也没有,那就从数据库中查询。

  1. 二级缓存配置

  需要在Mybatis的配置文件中<settings>标签中配置二级缓存:

<settings><settingname="cacheEnabled"value="true"/><!--Mybatis的二级缓存配置--></settings>

  Mybatis的二级缓存的范围是mapper级别的,因此我们mapper如果想要使用二级缓存,还需要在对应的映射文件中配置<cache>标签

<mappernamespace="com.snow.xml.SnowOracle"><cache></cache><!--Mybatis的二级缓存配置--></mapper>

测试:

@Testpublicvoidtest(){SqlSession sqlSession1=DBUtil.getSqlSession();List<Map<String,Object>> list1= sqlSession1.selectList("getEmployeeByName","周康");System.out.println("list1="+ list1);
    sqlSession1.commit();
    sqlSession1.close();DBUtil.closeSqlsession();SqlSession sqlSession2=DBUtil.getSqlSession();List<Map<String,Object>> list2= sqlSession2.selectList("getEmployeeByName","周康");System.out.println("list2="+ list2);
    sqlSession2.commit();
    sqlSession2.close();}

在SqlSession2 创建处打断点,观看此时输出:list1=[{ID=70DD7D10-9FC6-4B79-ADAF-8B408DE1E048, EMPNAME=周康, AGE=35, DMGRP=男, BIRTHDATE=1987-07-31 00:00:00.0, SALARY=17000, ADDRESS=湖南长沙, GRADE=高级架构师}]
在这里插入图片描述
此时去修改数据库中周康此人的年龄,改为37提交,代码继续往下执行,会看到SqlSession2 与SqlSession1 是两个不同的SqlSession,观看此时输出:list2=[{ID=70DD7D10-9FC6-4B79-ADAF-8B408DE1E048, EMPNAME=周康, AGE=35, DMGRP=男, BIRTHDATE=1987-07-31 00:00:00.0, SALARY=17000, ADDRESS=湖南长沙, GRADE=高级架构师}]
在这里插入图片描述
在这里插入图片描述
两次结果一致,均为35岁,说明SqlSession2 的查询没有走数据库,而是用了Mybatis的二级缓存,从里面拿到的数据,虽然是两个不同的SqlSession,但是二级缓存是mapper级别的,SqlSession1 只执行了查询操作没有增改删,所以不会清空二级缓存中的数据。

此处如果关闭了二级缓存的配置,查询出来的结果会是实时的,因为一级缓存默认开启,一级缓存的作用是SqlSession级别的,不同的SqlSession缓存数据不共享。这里就不演示一级缓存效果了。

  1. 禁用二级缓存

  有些情况下,我们需要打开二级缓存的配置,但是某个sql语句的查询变化频率较高,则需要针对该sql禁用二级缓存。在xml中statement中设置useCache=false 则可以禁用当前select语句的二级缓存,即每次查询都会发出sql去查询,默认是true(使用二级缓存)

<selectid="getEmployeeByName"parameterType="string"resultType="java.util.LinkedHashMap"useCache="false">
    SELECT E.ID, E.EMPNAME, E.AGE, GB01.DMGRP, E.BIRTHDATE, E.SALARY, E.ADDRESS, GB02.DMGRP AS GRADE
        FROM EMPLOYEE E LEFT JOIN GB01 ON E.SEX = GB01.ID LEFT JOIN GB02 ON E.GRADE = GB02.ID
    WHERE E.EMPNAME = #{name}</select>

测试:
操作与上面一样,SqlSession2处打断点,更改数据库中该人的年龄,查看两次输出结果。
在这里插入图片描述
在这里插入图片描述
list1=[{ID=70DD7D10-9FC6-4B79-ADAF-8B408DE1E048, EMPNAME=周康, AGE=37, DMGRP=男, BIRTHDATE=1987-07-31 00:00:00.0, SALARY=17000, ADDRESS=湖南长沙, GRADE=高级架构师}]
list2=[{ID=70DD7D10-9FC6-4B79-ADAF-8B408DE1E048, EMPNAME=周康, AGE=35, DMGRP=男, BIRTHDATE=1987-07-31 00:00:00.0, SALARY=17000, ADDRESS=湖南长沙, GRADE=高级架构师}]

可以看到操作虽然与第一次一样,可结果却变了,虽然二级缓存中有周康该人的信息,但是SqlSession2 还是从数据库中查询到了此人最新的数据,因为我们禁用了二级缓存。useCache=false

  1. 增删改的二级缓存

  二级缓存其实大部分都是为查询服务的,对于它们而言,如果我们缓存的数据不是最新的那么就会读到脏数据了。增删改之后之所以二级缓存会被清空是因为它们有一个默认的flushCache=true,默认在sql结束后刷新二级缓存,可以通过修改配置值达到不刷新缓存的目的(不建议使用)。

<updateid="updateAgeByName"parameterType="string"flushCache="false">
    UPDATE EMPLOYEE SET AGE = '40' WHERE EMPNAME = #{EMPNAME}</update>

四、了解Mybatis缓存的一些参数

  mybatis 的cache 参数只适用于mybatis 维护缓存。

flushInterval : 刷新间隔,可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式时间段,默认情况是不设置,也就是没有刷新间隔,缓存仅仅在调用语句时刷新。

size : 引用数目,可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的可用内存资源数目默认值为1024。

readOnly : 只读属性,可被设置为true or false,只读的缓存会给所有调用者返回缓存对象的相同实例,因此这些对象不能被修改,这提供了很重要的性能优势。可读写的缓存会返回缓存对象的拷贝(通过序列化)。这样会导致效率慢一些,但是安全,默认值为false。

<cacheeviction="FIFO"flushInterval="6000"size="512"readOnly="true"/>

  这样的二级缓存配置,创建了一个FIFO的缓存,并且每隔60秒刷新,存数结果对象或列表的512个引用,而且返回的对象被认为是只读的,因此在不同线程中的调用者之间修改它们会导致冲突。

默认的回收策略有(默认为LRU):

  1. LRU:最近最少使用的,移除最长时间不被使用的对象。
  2. FIFO:先进先出,俺对象进入缓存的顺序来移除它们。
  3. SOFT:软引用,移除基于垃圾回收器状态和软引用规则的对象。
  4. WEAK:弱引用,更积极地移除给予垃圾回收器状态和弱引用规则的对象。
  • 作者:头真的好重好重Y
  • 原文链接:https://blog.csdn.net/zy_zhangruichen/article/details/122592504
    更新时间:2022-10-22 12:07:20