• MyBatis---缓存-提高检索效率的利器


    目录

    让我们来看看官方文档

    缓存

    一.一级缓存

    1.基本介绍  

    2.一级缓存

    3.一级缓存失效分析

    二.二级缓存

    1.基本介绍

    2.二级缓存快速入门 

    2.1快速入门

    3.注意事项和使用陷阱

    三.Mybatis 的一级缓存和二级缓存执行顺序

    四.EhCache 缓存

    1.基本介绍

    2.配置和使用 EhCache

    2.1. 加入相关依赖pom.xml

    2.2mybatis-config.xml 仍然打开二级缓存

    2.3加入配置文件ehcache.xml

    2.4在 XxxMapper.xml 中启用 EhCache , 当然原来 MyBatis 自带的缓存配置就注销了

    2.5测试

    3.EhCache 缓存-细节说明


    让我们来看看官方文档

    缓存

    MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。 为了使它更加强大而且易于配置,我们对 MyBatis 3 中的缓存实现进行了许多改进。

    默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:

    基本上就是这样。这个简单语句的效果如下:

    • 映射语句文件中的所有 select 语句的结果将会被缓存。
    • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
    • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
    • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
    • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
    • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

    提示 缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。

    这些属性可以通过 cache 元素的属性来修改。比如:

    这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。

    可用的清除策略有:

    • LRU – 最近最少使用:移除最长时间不被使用的对象。
    • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
    • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
    • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。

    默认的清除策略是 LRU。

    flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。

    size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。

    readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。

    提示 二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。

    一.一级缓存

    1.基本介绍  

    基本说明
    1. 默认情况下, mybatis 是启用一级缓存的 / 本地缓存 /local Cache ,它是 SqlSession 级别的。
    2. 同一个 SqlSession 接口对象调用了相同的 select 语句 , 会直接从缓存里面获取,而不是再
    去查询数据库
    一级缓存原理图【简单追一下源码 , 后面还会详细的 Debug

    图的解析:

            客户端/浏览器发出一个请求的时候,控制器会先去Executor中去查询Local Cache去查有没有,如果有就会直接返回给客户端/浏览器,没有就会去数据库中去拿,同时也会放到 Local Cache中,下一次在查询这个数据的时候就会直接从Local Cache中获取。

    2.一级缓存

    当我们第 1 次查询 id=1 Monster 后,再次查询 id=1 monster 对象 , 就会直接
    从一级缓存获取 , 不会再次发出 sql

    3.一级缓存失效分析

    1. 关闭 sqlSession 会话后 , 再次查询,会到数据库查询 , 修改 MonsterMapperTest.java,
    试一级缓存失效情况
    1. //测试一级缓存,失效
    2. //关闭sqlSession会话后 , 一级缓存失效
    3. @Test
    4. public void level1CacheTest2() {
    5. //查询id=3的monster
    6. Monster monster = monsterMapper.getMonsterById(3);
    7. System.out.println("monster=" + monster);
    8. //关闭sqlSession, 一级缓存失效
    9. if (sqlSession != null) {
    10. sqlSession.close();
    11. }
    12. //因为关闭了sqlSession,所以需要重新初始化sqlSession和 monsterMapper
    13. sqlSession = MyBatisUtils.getSqlSession();
    14. monsterMapper = sqlSession.getMapper(MonsterMapper.class);
    15. //再次查询id=3的monster
    16. System.out.println("--如果你关闭了sqlSession,当你再次查询相同的id时, 仍然会发出sql----");
    17. Monster monster2 = monsterMapper.getMonsterById(3);
    18. System.out.println("monster2=" + monster2);
    19. if (sqlSession != null) {
    20. sqlSession.close();
    21. }
    22. }
    2. 当执行 sqlSession.clearCache() 会使一级缓存失效,修改 MonsterMapperTest.java,
    试一级缓存失效情况(清除localcache中的缓存)
    1. //测试一级缓存,失效
    2. //如果执行sqlSession.clearCache() , 会导致一级缓存失效
    3. @Test
    4. public void level1CacheTest3() {
    5. //查询id=3的monster
    6. Monster monster = monsterMapper.getMonsterById(3);
    7. System.out.println("monster=" + monster);
    8. //执行clearCache
    9. /**
    10. * @Override
    11. * public void clearCache() {
    12. * executor.clearLocalCache();
    13. * }
    14. */
    15. sqlSession.clearCache();
    16. //再次查询id=3的monster
    17. System.out.println("--如果你执行sqlSession.clearCache(),当你再次查询相同的id时, 仍然会发出sql----");
    18. Monster monster2 = monsterMapper.getMonsterById(3);
    19. System.out.println("monster2=" + monster2);
    20. if (sqlSession != null) {
    21. sqlSession.close();
    22. }
    23. }
    3. 当对同一个 monster 修改,该对象在一级缓存会失效 , 修改 MonsterMapperTest.java,
    试一把
    1. //测试一级缓存,失效
    2. //如果修改了同一个对象 , 会导致一级缓存[对象数据]失效
    3. @Test
    4. public void level1CacheTest4() {
    5. //查询id=3的monster
    6. Monster monster = monsterMapper.getMonsterById(3);
    7. System.out.println("monster=" + monster);
    8. //如果修改了同一个对象 , 会导致一级缓存[对象数据]失效
    9. monster.setName("蚂蚱精");
    10. monsterMapper.updateMonster(monster);
    11. //再次查询id=3的monster
    12. System.out.println("--如果你修改了同一个对象,当你再次查询相同的id时, 仍然会发出sql----");
    13. Monster monster2 = monsterMapper.getMonsterById(3);
    14. System.out.println("monster2=" + monster2);
    15. if (sqlSession != null) {
    16. sqlSession.commit();//这里需要commit
    17. sqlSession.close();
    18. }
    19. }

    二.二级缓存

    1.基本介绍

    基本介绍
    1. 二级缓存和一级缓存都是为了提高检索效率的技术
    2. 最大的区别就是作用域的范围不一样,一级缓存的作用域是 sqlSession 会话级别 , 在一次
    会话有效,而二级缓存作用域是全局范围,针对不同的会话都有效(不管是哪一个sqlsession都有效)
    二级缓存原理图

            客户端发出了一次请求或者说一次会话,这个时候假如你配置了二级缓存,会在CachingExecytor中会先有一个获取二级缓存的动作,在这个二级缓存中看有没有数据(有自带二级缓存和第三方缓存库),查到了直接返回,没有查到就会继续往下走,到我们的一级缓存中去查找,在一次里面找到了也可以直接返回给客户端,没有找到继续去数据库查找。 

    2.二级缓存快速入门 

    2.1快速入门

    1. mybatis-config.xml 配置中开启二级缓存

    1. <configuration>
    2. <properties resource="jdbc.properties"/>
    3. <settings>
    4. <setting name="cacheEnabled" value="true"/>
    5. settings>

    2.使用二级缓存时 entity 类实现序列化接口 (serializable),因为二级缓存可能使用到序

    列化技术

    3. 在对应的 XxxMapper.xml 中设置二级缓存的策略

    1. <mapper namespace="com.hong.mapper.MonsterMapper">
    2. <cache eviction="FIFO" flushInterval="60000"
    3. size="512" readOnly="true"/>

    4.测试

    1. //测试二级缓存的使用
    2. @Test
    3. public void level2CacheTest() {
    4. //查询id=3的monster
    5. Monster monster = monsterMapper.getMonsterById(3);
    6. System.out.println("monster=" + monster);
    7. //这里老师关闭sqlSession
    8. if (sqlSession != null) {
    9. sqlSession.close();
    10. }
    11. //重新获取sqlSession
    12. sqlSession = MyBatisUtils.getSqlSession();
    13. //重新获取了monsterMapper
    14. monsterMapper = sqlSession.getMapper(MonsterMapper.class);
    15. //再次查询id=3的monster
    16. System.out.println("--虽然前面关闭了sqlSession,因为配置二级缓存, " +
    17. "当你再次查询相同的id时, 依然不会再发出sql, 而是从二级缓存获取数据----");
    18. Monster monster2 = monsterMapper.getMonsterById(3);
    19. System.out.println("monster2=" + monster2);
    20. Monster monster3 = monsterMapper.getMonsterById(3);
    21. System.out.println("monster3=" + monster3);
    22. if (sqlSession != null) {
    23. sqlSession.close();
    24. }
    25. }

    3.注意事项和使用陷阱

    1. 理解二级缓存策略的参数
    上面的配置意思如下:
    创建了 FIFO 的策略,每隔 30 秒刷新一次,最多存放 360 个对象而且返回的对象被认为是只读的。
    eviction :缓存的回收策略
    flushInterval: 时间间隔,单位是毫秒,
    size :引用数目,内存大就多配置点,要记住你缓存的对象数目和你运行环境的可用内存
    资源数目。默认值是 1024
    readOnly:true, 只读
    2. 四大策略
    √ LRU – 最近最少使用的 : 移除最长时间不被使用的对象,它是默认
    √ FIFO – 先进先出 : 按对象进入缓存的顺序来移除它们。
    √ SOFT – 软引用 : 移除基于垃圾回收器状态和软引用规则的对象。
    √ WEAK – 弱引用 : 更积极地移除基于垃圾收集器状态和弱引用规则的对象。
    3. 如何禁用二级缓存
    3.1mybatis-config.xml
    1. <settings>
    2. <setting name="logImpl" value="STDOUT_LOGGING"/>
    3. <setting name="cacheEnabled" value="false"/>
    4. settings>

    3.2MonsterMapper.xml

    3.3或者更加细粒度的, 在配置方法上指定

    设置 useCache=false 可以禁用当前 select 语句的二级缓存,即每次查询都会发出 sql 去查询,
    默认情况是 true ,即该 sql 使用二级缓存。

     注意一般我们不需要去修改,使用默认的即可

    4. mybatis 刷新二级缓存的设置

    1. <update id="updateMonster" parameterType="Monster" flushCache="true">
    2. UPDATE mybatis_monster SET NAME=#{name},age=#{age} WHERE id=#{id}
    3. update>
    insert update delete 操作数据后需要刷新缓存,如果不执行刷新缓存会出现脏读
    默认为 true ,默认情况下为 true 即刷新缓存,一般不用修改。

    三.Mybatis 的一级缓存和二级缓存执行顺序

    一句话:缓存执行顺序是:二级缓存-->一级缓存-->数据库

    当我们关闭一级缓存的时候,如果你配置二级缓存,那么一级缓存的数据,会放入到二级缓存
    细节说明
    1. 不会出现一级缓存和二级缓存中有同一个数据。因为二级缓存 ( 数据 ) 是在一级缓存关闭
    之后才有的

    2.运行效果 , 可以看到,在一级缓存存在的情况下,依然是先查询二级缓存,但是因为 二级缓存,没有数据, 所以命中率都是 0.0 ,

    四.EhCache 缓存

    1.基本介绍

    1. EhCache 是一个纯 Java 的缓存框架,具有快速、精干等特点
    2. MyBatis 有自己默认的二级缓存 ( 前面我们已经使用过了 ) ,但是在实际项目中,往往使用
    的是更加专业的第三方缓存产品 作为 MyBatis 的二级缓存 ,EhCache 就是非常优秀的缓存
    产品

    可以通过一个接口找到第三方缓存库。 

    2.配置和使用 EhCache

    2.1. 加入相关依赖pom.xml

    1. <dependencies>
    2. <dependency>
    3. <groupId>net.sf.ehcachegroupId>
    4. <artifactId>ehcache-coreartifactId>
    5. <version>2.6.11version>
    6. dependency>
    7. <dependency>
    8. <groupId>org.slf4jgroupId>
    9. <artifactId>slf4j-apiartifactId>
    10. <version>1.7.25version>
    11. dependency>
    12. <dependency>
    13. <groupId>org.mybatis.cachesgroupId>
    14. <artifactId>mybatis-ehcacheartifactId>
    15. <version>1.2.1version>
    16. dependency>
    17. dependencies>

    2.2mybatis-config.xml 仍然打开二级缓存

    1. <settings>
    2. <setting name="cacheEnabled" value="true"/>
    3. settings>

    2.3加入配置文件ehcache.xml

    1. "1.0" encoding="UTF-8"?>
    2. <ehcache>
    3. <diskStore path="java.io.tmpdir/Tmp_EhCache"/>
    4. <defaultCache
    5. eternal="false"
    6. maxElementsInMemory="10000"
    7. overflowToDisk="false"
    8. diskPersistent="false"
    9. timeToIdleSeconds="1800"
    10. timeToLiveSeconds="259200"
    11. memoryStoreEvictionPolicy="LRU"/>
    12. ehcache>

    2.4XxxMapper.xml 中启用 EhCache , 当然原来 MyBatis 自带的缓存配置就注销了

    1. <mapper namespace="com.hong.mapper.MonsterMapper">
    2. <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

    2.5测试

    1. //测试ehCache级缓存
    2. @Test
    3. public void ehCacheTest() {
    4. //查询id=3的monster
    5. Monster monster = monsterMapper.getMonsterById(3);
    6. //会发出SQL, 到db查询
    7. System.out.println("monster=" + monster);
    8. //这里老师关闭sqlSession, 一级缓存[数据]失效.=> 将数据放入到二级缓存 (ehcache)
    9. if (sqlSession != null) {
    10. sqlSession.close();
    11. }
    12. //重新获取sqlSession
    13. sqlSession = MyBatisUtils.getSqlSession();
    14. //重新获取了monsterMapper
    15. monsterMapper = sqlSession.getMapper(MonsterMapper.class);
    16. //再次查询id=3的monster
    17. System.out.println("--虽然前面关闭了sqlSession,因为配置二级缓存(ehcache), " +
    18. "当你再次查询相同的id时, 不会再发出sql, 而是从二级缓存(ehcache)获取数据----");
    19. Monster monster2 = monsterMapper.getMonsterById(3);
    20. System.out.println("monster2=" + monster2);
    21. //再次查询id=3的monster, 仍然到二级缓存(ehcache), 获取数据, 不会发出sql
    22. Monster monster3 = monsterMapper.getMonsterById(3);
    23. System.out.println("monster3=" + monster3);
    24. if (sqlSession != null) {
    25. sqlSession.close();
    26. }

    3.EhCache 缓存-细节说明

    如何理解 EhCache MyBatis 缓存的关系
    1. MyBatis 提供了一个接口 Cache 【如右图,找到 org.apache.ibatis.cache.Cache ,关联源
    码包就可以看到 Cache 接口】
    2. 只要实现了该 Cache 接口,就可以作为二级缓存产品和 MyBatis 整合使用 ,Ehcache
    是实现了该接口
    3. MyBatis 默认情况 ( 即一级缓存 ) 是使用的 PerpetualCache 类实现 Cache 接口的 , 是核心类

    4. 当我们使用了 Ehcahce 后,就是 EhcacheCache 类实现 Cache 接口的,是核心类. 

    5. 我们看一下源码,发现缓存的本质就是 Map 

  • 相关阅读:
    二十一、CANdelaStudio深入-SPRMIB的配置
    win11更新后任务栏空白怎么办? win11更新后任务栏空白卡死的解决方法
    拼多多回应六万人砍价不成功:不实;苹果回应iOS 15.4正式版续航翻车;AMD FSR 2.0 即将面世|极客头条
    电脑图片损坏打不开怎么办?能修复吗?
    无线网络性能该如何检测?
    WEB渗透Bypass篇-常规操作
    OpenCV 07(图像滤波器)
    Python爬虫——scrapy-4
    2212. 射箭比赛中的最大得分-动态规划算法-多背包问题转化
    jdbc&数据库连接池&jdbcTemplate教程
  • 原文地址:https://blog.csdn.net/weixin_54107527/article/details/127849578