【MyBatis】MyBatis的一级缓存和二级缓存简介

目录

1、一级缓存

1.1 我们在一个 sqlSession 中,对 User 表根据id进行两次查询,查看他们发出sql语句的情况。 

1.2 同样是对user表进行两次查询,只不过两次查询之间进行了一次update操作。

1.3 一级缓存查询过程

1.4 Mybatis与Spring整合导致一级缓存失效

1.5 总结

2、二级缓存

2.1 二级缓存的使用

2.1.1 创建一个POJO Bean并序列化

2.1.2 在映射文件中开启二级缓存

2.1.3 在 mybatis-config.xml中开启二级缓存

2.1.4 测试

2.1.5 使用spring整合Mybatis时使用二级缓存

2.2 二级缓存的缺陷


MyBatis为我们提供了一级缓存和二级缓存,可以通过下图来理解:

  • 一级缓存SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
  • 二级缓存mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。二级缓存的底层也是HashMap。(同一个Mapper本质就是同一个命名空间)

1、一级缓存

我们在一个 sqlSession 中,对 User 表根据id进行两次查询,查看他们发出sql语句的情况。

1.1 我们在一个 sqlSession 中,对 User 表根据id进行两次查询,查看他们发出sql语句的情况。

@Test
public void testSelectOrderAndUserByOrderId(){//根据 sqlSessionFactory 产生 sqlsessionSqlSession sqlSession = sessionFactory.openSession();String statement = "one.to.one.mapper.OrdersMapper.selectOrderAndUserByOrderID";UserMapper userMapper = sqlSession.getMapper(UserMapper.class);//第一次查询,发出sql语句,并将查询的结果放入缓存中User u1 = userMapper.selectUserByUserId(1);System.out.println(u1);//第二次查询,由于是同一个sqlSession,会在缓存中查找查询结果//如果有,则直接从缓存中取出来,不和数据库进行交互User u2 = userMapper.selectUserByUserId(1);System.out.println(u2);sqlSession.close();
}

查看控制台打印情况:

 

1.2 同样是对user表进行两次查询,只不过两次查询之间进行了一次update操作。

@Test
public void testSelectOrderAndUserByOrderId(){//根据 sqlSessionFactory 产生 sqlsessionSqlSession sqlSession = sessionFactory.openSession();String statement = "one.to.one.mapper.OrdersMapper.selectOrderAndUserByOrderID";UserMapper userMapper = sqlSession.getMapper(UserMapper.class);//第一次查询,发出sql语句,并将查询的结果放入缓存中User u1 = userMapper.selectUserByUserId(1);System.out.println(u1);//第二步进行了一次更新操作,sqlSession.commit()u1.setSex("女");userMapper.updateUserByUserId(u1);sqlSession.commit();//第二次查询,由于是同一个sqlSession.commit(),会清空缓存信息//则此次查询也会发出 sql 语句User u2 = userMapper.selectUserByUserId(1);System.out.println(u2);sqlSession.close();
}

控制台打印情况:

1.3 一级缓存查询过程

1、第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。得到用户信息,将用户信息存储到一级缓存中。

2、如果中间sqlSession去执行commit操作(执行插入、更新、删除),则会清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。

3、第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息。

1.4 MybatisSpring整合导致一级缓存失效

我们使用Spring整合MyBatis的时候,就会发现我们的一级缓存失效,即如果1.1的代码是运行在spring项目下的,那么两次查询都会执行sql语句。

这是因为spring整合Mybatis之后,sqlSession是要交给spring容器去管理的,在每一次执行sql之后,spring会将sqlSession关闭,关闭sqlSession会清空一级缓存,所以一级缓存就会失效。

1.5 总结

一级缓存与数据库同一次会话(sqlSession)期间查询到的数据会放在本地缓存中,如果以后要获取相同的数据直接从缓存中获取,不会再次向数据库查询数据。

 

一个SqlSession拥有一个一级缓存,这个一级缓存是一个本地缓存。

 

MyBatis一直开启一级缓存,不同SqlSession的缓存,数据不可以共享

 

一级缓存还有失效的情况,一级缓存失效后,第二次查询相同的语句就还需向数据库发送sql。

一级缓存失效情况:

  1. sqlSession不同
  2. 当sqlSession对象相同的时候,查询的条件不同,原因是第一次查询时候一级缓存中没有第二次查询所需要的数据
  3. 当sqlSession对象相同,两次查询之间进行了插入的操作
  4. 当sqlSession对象相同,手动清除了一级缓存中的数据

一级缓存的生命周期有多长?

aMyBatis在开启一个数据库会话时,会创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象。Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。

b、如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,使PerpetualCache对象失效,以致一级缓存将不可用。

c、如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用。

dSqlSession中执行了任何一个update操作(update()delete()insert()) ,都会清空PerpetualCache对象的数据,但是该对象可以继续使用。

 

2、二级缓存

二级缓存的原理和一级缓存原理一样,第一次查询,会将数据放入缓存中,然后第二次查询则会直接去缓存中取。但是一级缓存是基于 sqlSession 的,而二级缓存是基于 mapper文件的namespace的,也就是说多个sqlSession可以共享一个mapper中的二级缓存区域,并且如果两个mapper的namespace相同,即使是两个mapper,那么这两个mapper中执行sql查询到的数据也将存在相同的二级缓存区域中

namespace默认就是Mapper类的全限定名。所以同一个Mapper也就保证了是同一个namespace

2.1 二级缓存的使用

2.1.1 创建一个POJO Bean并序列化

由于二级缓存的数据不一定都是存储到内存中,它的存储介质多种多样,所以需要给缓存的对象执行序列化。(如果存储在内存中的话,实测不序列化也可以的。)

public class Student implements Serializable{private static final long serialVersionUID = 735655488285535299L;private String id;private String name;private int age;private Gender gender;private List<Teacher> teachers;setters&getters()....;toString();        
}

2.1.2 在映射文件中开启二级缓存

 

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yihaomen.mybatis.dao.StudentMapper"><!--开启本mapper的namespace下的二级缓存--><!--eviction:代表的是缓存回收策略,目前MyBatis提供以下策略。(1) LRU,最近最少使用的,一处最长时间不用的对象(2) FIFO,先进先出,按对象进入缓存的顺序来移除他们(3) SOFT,软引用,移除基于垃圾回收器状态和软引用规则的对象(4) WEAK,弱引用,更积极的移除基于垃圾收集器状态和弱引用规则的对象。这里采用的是LRU,移除最长时间不用的对形象flushInterval:刷新间隔时间,单位为毫秒,这里配置的是100秒刷新,如果你不配置它,那么当SQL被执行的时候才会去刷新缓存。size:引用数目,一个正整数,代表缓存最多可以存储多少个对象,不宜设置过大。设置过大会导致内存溢出。这里配置的是1024个对象readOnly:只读,意味着缓存数据只能读取而不能修改,这样设置的好处是我们可以快速读取缓存,缺点是我们没有办法修改缓存,他的默认值是false,不允许我们修改--><cache eviction="LRU" flushInterval="100000" readOnly="true" size="1024"/><resultMap id="studentMap" type="Student"><id property="id" column="id" /><result property="name" column="name" /><result property="age" column="age" /><result property="gender" column="gender" typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler" /></resultMap><resultMap id="collectionMap" type="Student" extends="studentMap"><collection property="teachers" ofType="Teacher"><id property="id" column="teach_id" /><result property="name" column="tname"/><result property="gender" column="tgender" typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler"/><result property="subject" column="tsubject" typeHandler="org.apache.ibatis.type.EnumTypeHandler"/><result property="degree" column="tdegree" javaType="string" jdbcType="VARCHAR"/></collection></resultMap><select id="selectStudents" resultMap="collectionMap">SELECTs.id, s.name, s.gender, t.id teach_id, t.name tname, t.gender tgender, t.subject tsubject, t.degree tdegreeFROMstudent sLEFT JOINstu_teach_rel strONs.id = str.stu_idLEFT JOINteacher tONt.id = str.teach_id</select><!--可以通过设置useCache来规定这个sql是否开启缓存,ture是开启,false是关闭--><select id="selectAllStudents" resultMap="studentMap" useCache="true">SELECT id, name, age FROM student</select><!--刷新二级缓存<select id="selectAllStudents" resultMap="studentMap" flushCache="true">SELECT id, name, age FROM student</select>-->
</mapper>

2.1.3 mybatis-config.xml中开启二级缓存

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><settings><!--这个配置使全局的映射器(二级缓存)启用或禁用缓存--><setting name="cacheEnabled" value="true" />.....</settings>....
</configuration>

 

2.1.4 测试

测试二级缓存和sqlSession 无关

@Test
public void testTwoCache(){//根据 sqlSessionFactory 产生 sessionSqlSession sqlSession1 = sessionFactory.openSession();SqlSession sqlSession2 = sessionFactory.openSession();String statement = "com.ys.twocache.UserMapper.selectUserByUserId";UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);//第一次查询,发出sql语句,并将查询的结果放入缓存中User u1 = userMapper1.selectUserByUserId(1);System.out.println(u1);sqlSession1.close();//第一次查询完后关闭sqlSession//第二次查询,即使sqlSession1已经关闭了,这次查询依然不发出sql语句User u2 = userMapper2.selectUserByUserId(1);System.out.println(u2);sqlSession2.close();
}

可以看出上面两个不同的sqlSession,第一个关闭了,第二次查询依然不发出sql查询语句。

测试执行 commit() 操作,二级缓存数据清空

@Test
public void testTwoCache(){//根据 sqlSessionFactory 产生 sessionSqlSession sqlSession1 = sessionFactory.openSession();SqlSession sqlSession2 = sessionFactory.openSession();SqlSession sqlSession3 = sessionFactory.openSession();String statement = "com.ys.twocache.UserMapper.selectUserByUserId";UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);UserMapper userMapper3 = sqlSession2.getMapper(UserMapper.class);//第一次查询,发出sql语句,并将查询的结果放入缓存中User u1 = userMapper1.selectUserByUserId(1);System.out.println(u1);sqlSession1.close();//第一次查询完后关闭sqlSession//执行更新操作,commit()u1.setUsername("aaa");userMapper3.updateUserByUserId(u1);sqlSession3.commit();//第二次查询,由于上次更新操作,缓存数据已经清空(防止数据脏读),这里必须再次发出sql语句User u2 = userMapper2.selectUserByUserId(1);System.out.println(u2);sqlSession2.close();
}

查看控制台情况:

2.1.5 使用spring整合Mybatis时使用二级缓存

只需要在Mapper接口类上加一个@CacheNamespace注解就开启二级缓存了。

@CacheNamespace
public interface demoMapper {@Select("select * from demo")public List<Map<String, Object>> list();
}

2.2 二级缓存的缺陷

MyBatis二级缓存使用的在某些场景下会出问题,来看一下为什么这么说。

假设我有一条select语句(开启了二级缓存):

select a.col1, a.col2, a.col3, b.col1, b.col2, b.col3 from tableA a, tableB b where a.id = b.id;

对于tableA与tableB的操作定义在两个Mapper中,分别叫做MapperA与MapperB,即它们属于两个命名空间,如果此时启用缓存:

  1. MapperA中执行上述sql语句查询这6个字段
  2. tableB更新了col1与col2两个字段
  3. MapperA再次执行上述sql语句查询这6个字段(前提是没有执行过任何insert、delete、update操作)

此时问题就来了,即使第(2)步tableB更新了col1与col2两个字段,第(3)步MapperA走二级缓存查询到的这6个字段依然是原来的这6个字段的值,因为我们从CacheKey的3组条件来看:

  1. <select>标签所在的Mapper的Namespace+<select>标签的id属性
  2. RowBounds的offset和limit属性,RowBounds是MyBatis用于处理分页的一个类,offset默认为0,limit默认为Integer.MAX_VALUE
  3. <select>标签中定义的sql语句

对于MapperA来说,其中的任何一个条件都没有变化,自然会将缓存中的原结果返回。

这个问题对于MyBatis的二级缓存来说是一个无解的问题,因此使用MyBatis二级缓存有一个前提:必须保证所有的增删改查都在同一个命名空间下才行

最后说一下,MyBatis支持三种类型的二级缓存:

  • MyBatis默认的缓存,type为空,Cache为PerpetualCache
  • 自定义缓存(实现Cache接口)
  • 第三方缓存(redis、caffeine等

 相关文章:
【MyBatis】MyBatis解析动态SQL原理分析-CSDN博客【MyBatis】一篇文章带你彻底搞懂MyBatis解析SQL映射文件底层原理_mybatis 如何解析sql-CSDN博客
【MyBatis】MyBatis内置数据源_mybatis 指定数据源-CSDN博客
【MyBatis】MyBatis解析全局配置文件源码详解_mybatis解析配置文件源码-CSDN博客
【MyBatis】MyBatis的日志实现_mybatis 日志-CSDN博客
【MyBatis】MyBatis的介绍和基本使用_dbutil mybaits-CSDN博客
【MyBatis】史上最全的MyBatis执行SQL原理分析_mybatis 执行sql-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/52960.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

switch语句和while循环

switch语句和while循环 switch语句break的用法default的用法switch语句中的case和default的顺序问题 while语句while语句的执行流程while语句的具体例子 switch语句 switch 语句是⼀种特殊形式的 if…else 结构&#xff0c;用于判断条件有多个结果的情况。它把多重 的 else if…

滚动视图ScrollView

activity_scroll_view.xml <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"match_pare…

【Python系列】 Python 中的枚举使用

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

合宙LuatOS开发板使用手册——Air700EAQ

EVB-Air700EL&700EY 开发板是合宙通信推出的基于 Air700EL&700EY 模组所开发的&#xff0c; 包含电源&#xff0c;SIM 卡&#xff0c;USB&#xff0c;PCB 天线等必要功能的最小硬件系统。 以方便用户在设计前期对 模块进 行性能评估&#xff0c;功能调试&#xff0c;软…

如何让“相信相信的力量”帮你多赚100万

公门洞开纳百川 众心逐梦越千山 号召引领潜力绽 心觉潜意识无间 我们经常听到这句话&#xff1a;相信相信的力量 为什么要相信相信的力量 相信是什么意思 相信的力量又是什么意思 我估计99%的人不知道这句话的底层逻辑是什么 如果你悟透了&#xff0c;你的并且践行了&…

PE文件结构详解(非常详细)

最近在参考OpenShell为任务栏设置图片背景时&#xff0c;发现里面使用了IAT Hook&#xff0c;这一块没有接触过&#xff0c;去查资料的时候发现IAT Hook需要对PE文件结构有一定的了解&#xff0c;索性将PE文件结构的资料找出来&#xff0c;系统学习一下。 PE文件结构 Portable…

【Qt】 QDateTimeEdit | QDial

文章目录 QDateTimeEdit —— 时间日期 微调框QDateTimeEdit 属性核心信号QDateTimeEdit 的使用 QDial —— 按钮QDial 属性核心信号QDial 使用 QDateTimeEdit —— 时间日期 微调框 QDateTimeEdit 属性 QDateTimeEdit 作为 时间日期 的 微调框 dateTime —— 时间⽇期的值…

minio文件存储+ckplayer视频播放(minio分片上传合并视频播放)

文章目录 参考简述效果启动minio代码配置类RedisConfigWebConfigMinioClientAutoConfigurationOSSPropertiesapplication.yml 实体类MinioObjectResultStatusCodeOssFileOssPolicy 工具类FileTypeUtilMd5UtilMediaTypeMinioTemplate 文件分片上传与合并MinioFileControllerMini…

Webpack打包常见问题及优化策略

聚沙成塔每天进步一点点 本文回顾 ⭐ 专栏简介Webpack打包常见问题及优化策略1. 引言2. Webpack打包常见问题2.1 打包时间过长问题描述主要原因 2.2 打包体积过大问题描述主要原因 2.3 依赖包版本冲突问题描述主要原因 2.4 动态导入和代码拆分问题问题描述主要原因 2.5 文件路径…

Python+VScode 两个不同文件夹里的py文件相互调用|python的模块调用|绝对导入

第一次用VScode写python遇到了模块无法识别的问题&#xff0c;搞了一整天&#xff0c; 上网查&#xff0c;chatGPT都不行&#xff0c;现在时解决了。 首先项目结构如下&#xff0c;四个文件夹&#xff0c;四个py文件 代码&#xff1a; def f1fun():print("f1") de…

Code Practice Journal | Day59-60_Graph09 最短路径(待更)

1. Dijkstra 1.1 原理与步骤 步骤&#xff1a; 选取距离源点最近且未被访问过的节点标记该节点为已访问更新未访问节点到源点的距离 1.2 代码实现 以KamaCoder47题为例 题目&#xff1a;47. 参加科学大会&#xff08;第六期模拟笔试&#xff09; (kamacoder.com) class Progra…

Apache SeaTunnel Zeta 引擎源码解析(一)Server端的初始化

引入 本系列文章是基于 Apache SeaTunnel 2.3.6版本&#xff0c;围绕Zeta引擎给大家介绍其任务是如何从提交到运行的全流程&#xff0c;希望通过这篇文档&#xff0c;对刚刚上手SeaTunnel的朋友提供一些帮助。 我们整体的文章将会分成三篇&#xff0c;从以下方向给大家介绍&am…

指针5.回调函数与qsort

今天来学习回调函数与qsort 目录 1.回调函数实现模拟计算器代码的简化原代码运行结果简化代码运行结果 qsort函数排序整型数据代码运行结果 qsort排序结构数据代码 qsort函数的模拟实现代码运行结果 总结 1.回调函数 回调函数就是⼀个通过函数指针调用的函数。 如果你把函数的…

C++语法基础(一)

第一个C程序 1. <iostream>&#xff08;C&#xff09; <iostream> 是 C 标准库中的头文件&#xff0c;用于处理输入输出操作。它提供了基于流&#xff08;stream&#xff09;的输入输出机制。 特点&#xff1a; 面向对象&#xff1a;C 中的输入输出操作是基于流…

hyperf json-rpc

安装 安装docker hyperf 安装 hyperf-rpc-server-v8 &#xff08;服务端&#xff09; docker run --name hyperf-rpc-server-v8 \ -v /www/docker/hyperf-rpc-server:/data/project \ -w /data/project \ -p 9508:9501 -it \ --privileged -u root \ --entrypoint /bin/sh \…

Upload-LABS通关攻略【1-20关】

Pass-01 第一关是前端JS绕过 上传一个php文件显示只能上传特定后缀名的文件 这里将1.php改为1.jpg直接进行抓包&#xff0c;在数据包中将jpg改为php放行 文件上传成功&#xff0c;邮件图片新建页面打开 可以访问到1.php文件&#xff0c;则一句话密码上传成功 使用蚁剑 进行连接…

Redux的中间件原理分析

Redux的中间件原理分析 redux的中间件对于使用过redux的各位都不会感到陌生&#xff0c;通过应用上我们需要的所有要应用在redux流程上的中间件&#xff0c;我们可以加强dispatch的功能。最近抽了点时间把之前整理分析过的中间件有关的东西放在这里分享分享。本文只对中间件涉…

音视频开发之旅(90)-Vision Transformer论文解读与源码分析

目录 1.背景和问题 2.Vision Transformer(VIT)模型结构 3.Patch Embedding 4.实现效果 5.代码解析 6.资料 一、背景和问题 上一篇我们学习了Transformer的原理&#xff0c;主要介绍了在NLP领域上的应用&#xff0c;那么在CV(图像视频)领域该如何使用&#xff1f; 最直观…

算法复盘——LeetCode hot100:哈希

文章目录 哈希表哈希表的基本概念哈希表的使用1. 插入操作2. 查找操作3. 删除操作 哈希表的优点和缺点1.两数之和复盘 242.有效的字母异位词复盘 49.字母异位词分组复盘 128. 最长连续序列复盘HashSet 哈希表 先来搞清楚什么是哈希表吧~ 概念不清楚方法不清楚怎么做题捏 哈希表…

使用mysql保存密码

登录MySQL 这行命令告诉MySQL客户端程序用户root准备登录&#xff0c;-p表示告诉 MySQL 客户端程序提示输入密码。 mysql -u root -p创建数据库 create database wifi; use wifi;create table password(user_password CHAR(8),primary key(user_password));源码 代码编译 …