0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

聊聊MyBatis自定义缓存的原理和使用

冬至子 来源:悟空聊架构 作者:悟空聊架构 2022-11-16 14:30 次阅读

一、MyBatis 缓存中的常用概念

MyBatis 缓存:它用来优化 SQL 数据库查询的,但是可能会产生脏数据。

SqlSession:代表和数据库的一次会话,向用户提供了操作数据库的方法。

MappedStatement:代表要发往数据库执行的指令,可以理解为是 SQL 的抽象表示。

Executor:代表用来和数据库交互的执行器,接受 MappedStatment 作为参数

namespace:每个 Mapper 文件只能配置一个 namespace,用来做 Mapper 文件级别的缓存共享。

映射接口:定义了一个接口,然后里面的接口方法对应要执行 SQL 的操作,具体要执行的 SQL 语句是写在映射文件中。

映射文件:MyBatis 编写的 XML 文件,里面有一个或多个 SQL 语句,不同的语句用来映射不同的接口方法。通常来说,每一张单表都对应着一个映射文件。

二、MyBatis 一级缓存

2.1 一级缓存原理

在一次 SqlSession 中(数据库会话),程序执行多次查询,且查询条件完全相同,多次查询之间程序没有其他增删改操作,则第二次及后面的查询可以从缓存中获取数据,避免走数据库。

图片

每个SqlSession中持有了Executor,每个Executor中有一个LocalCache。当用户发起查询时,MyBatis根据当前执行的语句生成MappedStatement,在Local Cache进行查询,如果缓存命中的话,直接返回结果给用户,如果缓存没有命中的话,查询数据库,结果写入Local Cache,最后返回结果给用户。

Local Cache 其实是一个 hashmap 的结构:

private Map<Object, Object> cache = new HashMap<Object, Object>();

如下图所示,有两个 SqlSession,分别为 SqlSession1 和 SqlSession2,每个 SqlSession 中都有自己的缓存,缓存是 hashmap 结构,存放的键值对。

键是 SQL 语句组成的 Key :

Statement Id + Offset + Limmit + Sql + Params

值是 SQL 查询的结果:

图片

2.2 一级缓存配置

在 mybatis-config.xml 文件配置,name=localCacheScope,value有两种值:SESSIONSTATEMENT

<configuration>
    <settings>
        <setting name="localCacheScope" value="SESSION"/>
    <span class="hljs-name"settings>
<configuration>

SESSION:开启一级缓存功能

STATEMENT:缓存只对当前执行的这一个 SQL 语句有效,也就是没有用到一级缓存功能。

首先我们通过几个考题来体验下 MyBatis 一级缓存。

2.3 一级缓存考题

考题(1)只开启了一级缓存,下面的代码调用了三次查询操作 getStudentById,请判断,下列说法正确的是?

// 打开一个 SqlSession
SqlSession sqlSession = factory.openSession(true);
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); 
// 根据 id=1 查询学生信息
System.out.println(studentMapper.getStudentById(1)); 
// 根据 id=1 查询学生信息
System.out.println(studentMapper.getStudentById(1)); 
// 根据 id=1 查询学生信息
System.out.println(studentMapper.getStudentById(1));

答案:第一次从数据库查询到的数据,第二次和第二次从 MyBatis 一级缓存查询的数据。

解答:第一次从数据库查询后,后续查询走 MyBatis 一级缓存

考题(2)只开启了一级缓存,下面代码示例中,开启了一个 SqlSession 会话,调用了一次查询,然后对数据进行了更改,又调用了一次查询,下列关于两次查询的说法,正确的是?

// 打开一个 SqlSession
SqlSession sqlSession = factory.openSession(true);
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); 
// 根据 id=1 查询学生信息
System.out.println(studentMapper.getStudentById(1)); 
// 插入了一条学生数据,改变了数据库
System.out.println("增加了" + studentMapper.addStudent(buildStudent()) + "个学生"); 
// 根据 id=1 查询学生信息
System.out.println(studentMapper.getStudentById(1)); 
sqlSession.close();

答案:第一次从数据库查询到的数据,第二次从数据库查询的数据

解答:第一次从数据库查询后,后续更新(包括增删改)数据库中的数据后,这条 SQL 语句的缓存失效了,后续查询需要重新从数据库获取数据。

考题(3)当开启了一级缓存,下面的代码中,开启了两个 SqlSession,第一个 SqlSession 查询了两次学生 A 的姓名,第二次 SqlSession 更新了一次学生 A 的姓名,请判断哪个选项符合最后的查询结果。

SqlSession sqlSession1 = factory.openSession(true); 
SqlSession sqlSession2 = factory.openSession(true); 
StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class); 
StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class); studentMapper2.updateStudentName("B",1); 
System.out.println(studentMapper.getStudentById(1)); 
System.out.println(studentMapper2.getStudentById(1));

答案:

A
B

解答:只开启一级缓存的情况下,SqlSession 级别是不共享的。代码示例中,分别创建了两个 SqlSession,在第一个 SqlSession 中查询学生 A 的姓名,第二个 SqlSession 中修改了学生 A 的姓名为 B,SqlSession2 更新了数据后,不会影响 SqlSession1,所以 SqlSession1 查到的数据还是 A。

2.4 MyBatis 一级缓存失效的场景

  1. 不同的SqlSession对应不同的一级缓存
  2. 同一个SqlSession但是查询条件不同
  3. 同一个SqlSession两次查询期间执行了任何一次增删改操作
  4. 同一个SqlSession两次查询期间手动清空了缓存

2.5 MyBatis 一级缓存总结

  • MyBatis一级缓存内部设计简单,只是一个没有容量限定的 HashMap,在缓存的功能性上有所欠缺
  • MyBatis的一级缓存最大范围是SqlSession内部,有多个SqlSession或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为Statement
  • 一级缓存的配置中,默认是 SESSION 级别,即在一个MyBatis会话中执行的所有语句,都会共享这一个缓存。

三、MyBatis 二级缓存

3.1 MyBatis 二级缓存概述

  • MyBatis的二级缓存相对于一级缓存来说,实现了SqlSession之间缓存数据的共享,同时粒度更加的细,能够到namespace级别,通过Cache接口实现类不同的组合,对Cache的可控性也更强。
  • MyBatis在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻。
  • 在分布式环境下,由于默认的MyBatis Cache实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将 MyBatis的Cache 接口实现,有一定的开发成本,直接使用Redis、Memcached 等分布式缓存可能成本更低,安全性也更高。

3.2 MyBatis 二级缓存原理

一级缓存最大的共享范围就是一个 SqlSession 内部,如果多个 SqlSession 之间需要共享缓存,则需要使用到二级缓存。

开启二级缓存后,会使用 CachingExecutor 装饰 Executor,进入一级缓存的查询流程前,先在CachingExecutor 进行二级缓存的查询。

二级缓存开启后,同一个 namespace下的所有操作语句,都影响着同一个Cache。

图片

每个 Mapper 文件只能配置一个 namespace,用来做 Mapper 文件级别的缓存共享。

<mapper namespace="mapper.StudentMapper"><span class="hljs-name"mapper>

二级缓存被同一个 namespace 下的多个 SqlSession 共享,是一个全局的变量。MyBatis 的二级缓存不适应用于映射文件中存在多表查询的情况。

通常我们会为每个单表创建单独的映射文件,由于MyBatis的二级缓存是基于namespace的,多表查询语句所在的namspace无法感应到其他namespace中的语句对多表查询中涉及的表进行的修改,引发脏数据问题。

3.3 MyBatis缓存查询的顺序

图片

  • 先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用
  • 如果二级缓存没有命中,再查询一级缓存
  • 如果一级缓存也没有命中,则查询数据库
  • SqlSession关闭之后,一级缓存中的数据会写入二级缓存。

3.4 二级缓存配置

开启二级缓存需要在 mybatis-config.xml 中配置:


3.5 二级缓存考题

测试update操作是否会刷新该namespace下的二级缓存。

开启了一级和二级缓存,通过三个SqlSession 查询和更新 学生张三的姓名,判断最后的输出结果是什么?

SqlSession sqlSession1 = factory.openSession(true); 
SqlSession sqlSession2 = factory.openSession(true); 
SqlSession sqlSession3 = factory.openSession(true); 
StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class); 
StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class); 
StudentMapper studentMapper3 = sqlSession3.getMapper(StudentMapper.class); System.out.println("studentMapper读取数据: " + studentMapper.getStudentById(1)); 
sqlSession1.commit(); 
System.out.println("studentMapper2读取数据: " + studentMapper2.getStudentById(1)); studentMapper3.updateStudentName("李四",1); 
sqlSession3.commit(); 
System.out.println("studentMapper2读取数据: " + studentMapper2.getStudentById(1));

答案:

张三
张三
李四

解答:三个 SqlSession 是共享 MyBatis 缓存,SqlSession2 更新数据后,MyBatis 的 namespace 缓存(StudentMapper) 就失效了,SqlSession2 最后是从数据库查询到的数据。

四、MyBatis 自定义缓存

4.1 MyBatis 自定义缓存概述

当 MyBatis 二级缓存不能满足要求时,可以使用自定义缓存替换。(较少使用)

自定义缓存需要实现 MyBatis 规定的接口:org.apache.ibatis.cache.Cache。这个接口里面定义了 7 个方法,我们需要自己去实现对应的缓存逻辑。

图片

4.2 整合第三方缓存 EHCache

EHCache 和 MyBatis 已经帮我们整合好了一个自定义缓存,我们可以直接拿来用,不需要自己去实现 MyBatis 的 org.apache.ibatis.cache.Cache 接口。

添加 mybatis-ehcache 依赖包。

<dependency>
 <groupId>org.mybatis.caches<span class="hljs-name"groupId>
 <artifactId>mybatis-ehcache<span class="hljs-name"artifactId>
 <version>1.2.1<span class="hljs-name"version>
<span class="hljs-name"dependency>

创建EHCache的配置文件ehcache.xml。

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
    
    <diskStore path="D:\\passjava\\ehcache"/>
    <defaultCache
            maxElementsInMemory="1000"
            maxElementsOnDisk="10000000"
            eternal="false"
            overflowToDisk="true"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU">
    <span class="hljs-name"defaultCache>
<span class="hljs-name"ehcache>

设置二级缓存的类型,在xxxMapper.xml文件中设置二级缓存类型


4.3 EHCache配置文件说明

图片

五、总结

本篇分别介绍了 MyBatis 一级缓存、二级缓存、自定义缓存的原理和使用,其中还穿插了 4 道考题来验证 MyBatis 缓存的功能。不足之处是 MyBatis 缓存源码未分析。

审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • XML
    XML
    +关注

    关注

    0

    文章

    188

    浏览量

    33079
  • SQL
    SQL
    +关注

    关注

    1

    文章

    762

    浏览量

    44117
  • mybatis
    +关注

    关注

    0

    文章

    60

    浏览量

    6711
收藏 人收藏

    评论

    相关推荐

    HarmonyOS开发实例:【自定义Emitter】

    使用[Emitter]实现事件的订阅和发布,使用[自定义弹窗]设置广告信息。
    的头像 发表于 04-14 11:37 997次阅读
    HarmonyOS开发实例:【<b class='flag-5'>自定义</b>Emitter】

    自定义函数测试学习工程

    自定义函数测试学习工程
    发表于 07-01 16:37 5次下载

    SOPC中自定义外设和自定义指令性能分析

    SOPC中自定义外设和自定义指令性能分析 NiosII是一个建立在FPGA上的嵌入式软核处理器,灵活性很强。作为体现NiosII灵活性精髓的两个最主要方面,自
    发表于 03-29 15:12 1610次阅读
    SOPC中<b class='flag-5'>自定义</b>外设和<b class='flag-5'>自定义</b>指令性能分析

    1602自定义字符

    1602液晶能够显示自定义字符,能够根据读者的具体情况显示自定义字符。
    发表于 01-20 15:43 1次下载

    JAVA教程之自定义光标

    JAVA教程之自定义光标,很好的学习资料。
    发表于 03-31 11:13 7次下载

    C#教程之自定义屏保

    C#教程之自定义屏保,很好的C#资料,快来学习吧。
    发表于 04-20 09:59 7次下载

    RTWconfigurationguide基于模型设计—自定义

    基于模型设计—自定义目标系统配置指南,RTW自动代码生成相关资料。
    发表于 05-17 16:41 3次下载

    PDH网管盘 自定义字节

    PDH网管盘 自定义字节
    发表于 12-26 22:13 0次下载

    如何在android设备上安装自定义rom

    完成后,请执行相同的操作,但不要选择自定义rom,而是选择间隙。安装间隙之前需要使用一些自定义rom,您可以从自定义rom的开发人员网页上了解,如果他们没有提及任何相关内容,只需在自定义
    的头像 发表于 11-05 10:48 5148次阅读

    自定义视图组件教程案例

    自定义组件 1.自定义组件-particles(粒子效果) 2.自定义组件- pulse(脉冲button效果) 3.自定义组件-progress(progress效果) 4.
    发表于 04-08 10:48 14次下载

    ArkUI如何自定义弹窗(eTS)

    自定义弹窗其实也是比较简单的,通过CustomDialogController类就可以显示自定义弹窗。
    的头像 发表于 08-31 08:24 2191次阅读

    自定义特性能做什么?

    今天跟大家分享的主题是基于自定义特性实现DataGridView全自动生成。
    的头像 发表于 02-22 16:20 816次阅读
    <b class='flag-5'>自定义</b>特性能做什么?

    labview自定义控件

    labview自定义精美控件
    发表于 05-15 16:46 17次下载

    自定义算子开发

    一个完整的自定义算子应用过程包括注册算子、算子实现、含自定义算子模型转换和运行含自定义op模型四个阶段。在大多数情况下,您的模型应该可以通过使用hb_mapper工具完成转换并顺利部署到地平线芯片上……
    的头像 发表于 04-07 16:11 2799次阅读
    <b class='flag-5'>自定义</b>算子开发

    labview超快自定义控件制作和普通自定义控件制作

    labview超快自定义控件制作和普通自定义控件制作
    发表于 08-21 10:32 13次下载