MyBatis 的一级、二级缓存机制

news2024/11/16 18:07:26

目录标题

  • 缓存
    • 什么是缓存
    • 为什么使用缓存
    • 什么样的数据能使用缓存,什么样的数据不能使用
      • 适用于缓存
      • 不适用于缓存
    • MyBatis 一级缓存、二级缓存关系
  • 1. 一级缓存
    • 1.1 什么是一级缓存mybatis
    • 1.2 一级缓存配置
    • 1.3 什么情况下会命中一级缓存
      • mybatis清除一级缓存的几种方法
    • 1.4 内部结构
      • PerpetualCache
    • 1.5 clear() == map.clear()
      • insert/delete/update 方法, 清空 localCache
    • 1.6 Mybatis的一级缓存时序图
    • 1.7 一级缓存实验
      • 一级缓存同一个会话共享数据
      • 同一个会话如果有更新操作则缓存清除
      • 一级缓存在多会话中会导致脏数据
      • 解决方式:在配置一级缓存作用范围的时候将其设置为 STATEMENT,那么缓存仅对当前执行的语句有效,当语句执行完毕后,缓存就会被清空。
  • 2. 二级缓存
    • 2.2 二级缓存工作流程
    • 2.2 二级缓存配置
      • 二级缓存何时存入
      • 二级缓存如何清空
    • 2.3 二级缓存实验
      • 测试二级缓存与 SqlSession 无关
      • 测试执行 commit(),二级缓存数据清空
      • 多表操作一定不能使用缓存
    • useCache 和 flushCache

缓存

什么是缓存

缓存是存在于内存中的临时数据。

为什么使用缓存

使用缓存减少和数据库的交互次数,提高执行效率。(因为查询数据库是一件很费时很费效率的事,还涉及一些硬盘等io操作,而缓存是存在内存中的,读取都很快,而且效率高)

什么样的数据能使用缓存,什么样的数据不能使用

适用于缓存

经常查询并且不经常改变的;
数据的正确与否对最终结果影响不大的;

不适用于缓存

经常改变的数据;
数据的正确与否对最终结果影响很大的;
例如:商品的库存,银行的汇率,股市的牌价;

MyBatis 一级缓存、二级缓存关系

在这里插入图片描述
一级缓存是 SqlSession 级别的缓存。在操作数据库时需要构造 SqlSession 对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的是 SqlSession 之间的缓存数据区(HashMap)是互相不影响。
二级缓存是 Mapper 级别的缓存,多个 SqlSession 去操作同一个 Mapper 的 sql 语句,多个 SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。

1. 一级缓存

1.1 什么是一级缓存mybatis

默认情况下只会开启一级缓存,也就是局部的 session 会话缓存。
每一个 session 会话都会有各自的缓存,是局部的。

1.2 一级缓存配置

<setting name="localCacheScope" value="SESSION"/>

在 MyBatis 的配置文件中添加上面语句,就可以使用一级缓存。共有两个选项,SESSION 或者 STATEMENT。
默认是 SESSION 级别,即在一个 MyBatis 会话中执行的所有语句,都会共享这一个缓存。
一种是 STATEMENT 级别,可以理解为缓存只对当前执行的这一个 Statement 有效;STATEMENT 级别粒度更细。

1.3 什么情况下会命中一级缓存

必须是在一个会话 Session当中,相同的 namespace(同一个命名空间 -> 同一个mapper文件) , sql 和 参数
不能够在查询之前执行 clearCache
中间不能执行 任何 update ,delete , insert (会将SqlSession中的数据全部清空)

mybatis清除一级缓存的几种方法

  1. 主动调用清理缓存的方法
sqlSession.clearCache()
  1. 提交事务,或者关闭session。
sqlSession.commit();
sqlSession.close();
  1. 执行增删改操作回清理缓存

1.4 内部结构

SqlSession 是一个接口,提供了一些 CRUD 的方法,而 SqlSession 的默认实现类是 DefaultSqlSession,DefaultSqlSession 类持有 Executor 接口对象,而 Executor 的默认实现是 BaseExecutor 对象,每个 BaseExecutor 对象都有一个 PerpetualCache 缓存,也就是上图的 Local Cache。
在这里插入图片描述

PerpetualCache

内部持有 HashMap,对一级缓存的操作实则是对 HashMap 的操作。

public class PerpetualCache implements Cache {
    private final String id;
    private Map<Object, Object> cache = new HashMap();
    ...
}

1.5 clear() == map.clear()

在这里插入图片描述
也就是说一级缓存的底层数据结构就是 HashMap。所以说 cache.clear() 其实就是 map.clear(),也就是说,缓存其实是本地存放的一个 map 对象,每一个 SqlSession 都会存放一个 map 对象的引用。

public class PerpetualCache implements Cache {
    ...
    private Map<Object, Object> cache = new HashMap();

    public void clear() {
        this.cache.clear();
    }
    ...
}

insert/delete/update 方法, 清空 localCache

而为了保证缓存里面的数据肯定是准确数据避免脏读,每次我们进行数据修改后(增、删、改操作)就会执行commit操作,清空缓存区域。

1.6 Mybatis的一级缓存时序图

在这里插入图片描述

1.7 一级缓存实验

一级缓存同一个会话共享数据

@Test
public void firstLevelCacheFindUserById() {
    // 第一次查询id为1的用户
    User user1 = userMapper.findUserById(1);
    // 第二次查询id为1的用户
    User user2 = userMapper.findUserById(1);

    System.out.println(user1);
    System.out.println(user2);

    System.out.println(user1 == user2);
}

在这里插入图片描述
我们可以看到,只有第一次真正查询了数据库,后续的查询使用了一级缓存。

同一个会话如果有更新操作则缓存清除

增加了对数据库的修改操作,验证在一次数据库会话中,如果对数据库发生了修改操作,一级缓存是否会失效。

@Test
public void firstLevelCacheOfUpdate() {
    // 第一次查询id为1的用户
    User user1 = userMapper.findUserById(1);
    System.out.println(user1);

    // 更新用户
    User user = new User();
    user.setId(2);
    user.setUsername("tom");

    System.out.println("更新了" + userMapper.updateUser(user) + "个用户");

    // 第二次查询id为1的用户
    User user2 = userMapper.findUserById(1);
    System.out.println(user2);

    System.out.println(user1 == user2);
}

在这里插入图片描述
我们可以看到,在修改操作后执行的相同查询,查询了数据库,一级缓存失效。

一级缓存在多会话中会导致脏数据

开启两个 SqlSession,在 sqlSession1 中查询数据,使一级缓存生效,在 sqlSession2 中更新数据库,验证一级缓存只在数据库会话内部共享。

@Test
public void firstLevelCacheOfScope() {
    SqlSession sqlSession2 = sqlSessionFactory.openSession(true);
    UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);

    System.out.println("userMapper读取数据: " + userMapper.findUserById(1));
    System.out.println("userMapper读取数据: " + userMapper.findUserById(1));

    // 更新用户
    User user = new User();
    user.setId(1);
    user.setUsername("andy");
    System.out.println("userMapper2更新了" + userMapper2.updateUser(user) + "个用户");

    System.out.println("userMapper读取数据: " + userMapper.findUserById(1));
    System.out.println("userMapper2读取数据: " + userMapper2.findUserById(1));
}

在这里插入图片描述
sqlSession2 更新了 id 为 1 的用户的姓名,从 riemann 改为了 andy,但 session1 之后的查询中,id 为 1 的学生的名字还是 riemann,出现了脏数据,也证明了之前的设想,一级缓存只在数据库会话内部共享。

解决方式:在配置一级缓存作用范围的时候将其设置为 STATEMENT,那么缓存仅对当前执行的语句有效,当语句执行完毕后,缓存就会被清空。

<settings>
  <setting name="localCacheScope" value="STATEMENT"/>
</settings>

2. 二级缓存

2.2 二级缓存工作流程

在上文中提到的一级缓存中,其最大的共享范围就是一个 SqlSession 内部,如果多个 SqlSession 之间需要共享缓存,则需要使用到二级缓存。开启二级缓存后,会使用 CachingExecutor 装饰 Executor,进入一级缓存的查询流程前,先在 CachingExecutor 进行二级缓存的查询,具体的工作流程如下所示。
在这里插入图片描述
二级缓存开启后,同一个 namespace 下的所有操作语句,都影响着同一个 Cache,即二级缓存被多个 SqlSession 共享,是一个全局的变量。
当开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库。

MyBatis 是默认关闭二级缓存的,因为对于增删改操作频繁的话,那么二级缓存形同虚设,每次都会被清空缓存。

2.2 二级缓存配置

和一级缓存默认开启不一样,二级缓存需要我们手动开启。

  1. 全局配置文件 SqlMapConfig.xml
<!--开启二级缓存-->
<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>
  1. 在 UserMapper.xml 文件中开启二级缓存

mapper 代理模式

<!--开启二级缓存-->
<cache />

注解开发模式

@CacheNamespace(implementation = PerpetualCache.class) // 开启二级缓存
public interface UserMapper {
}

开启二级缓存后,还需要将要缓存的实体类去实现 Serializable 序列化接口,为了将缓存数据取出执行反序列化操作,因为二级缓存数据存储介质多种多样,不一定只存在内存中,有可能存在硬盘中,如果我们再取出这个缓存的话,就需要反序列化。所以 MyBatis 的所有 pojo 类都要去实现 Serializable 序列化接口。

二级缓存何时存入

在关闭sqlsession后(close),才会把该sqlsession一级缓存中的数据添加到namespace的二级缓存中。

二级缓存如何清空

当对SqlSession执行更新操作(update、delete、insert)后并执行commit时,不仅清空其自身的一级缓存(执行更新操作的效果),也清空二级缓存(执行commit()的效果)。

2.3 二级缓存实验

测试二级缓存与 SqlSession 无关

@Test
public void secondLevelCache() {
    SqlSession sqlSession1 = sqlSessionFactory.openSession();
    SqlSession sqlSession2 = sqlSessionFactory.openSession();

    UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
    UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);

    // 第一次查询id为1的用户
    User user1 = userMapper1.findUserById(1);
    sqlSession1.close(); // 清空一级缓存
    System.out.println(user1);

    // 第二次查询id为1的用户
    User user2 = userMapper2.findUserById(1);
    System.out.println(user2);

    System.out.println(user1 == user2);
}

在这里插入图片描述
第一次查询时,将查询结果放入缓存中,第二次查询,即使 sqlSession1.close(); 清空了一级缓存,第二次查询依然不发出 sql 语句。
这里的你可能有个疑问,这里不是二级缓存了吗?怎么 user1 与 user2 不相等?

这是因为二级缓存的是数据,并不是对象。而 user1 与 user2 是两个对象,所以地址值当然也不想等。

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

@Test
public void secondLevelCacheOfUpdate() {
    SqlSession sqlSession1 = sqlSessionFactory.openSession();
    SqlSession sqlSession2 = sqlSessionFactory.openSession();
    SqlSession sqlSession3 = sqlSessionFactory.openSession();

    UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
    UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
    UserMapper userMapper3 = sqlSession3.getMapper(UserMapper.class);

    // 第一次查询id为1的用户
    User user1 = userMapper1.findUserById(1);
    sqlSession1.close(); // 清空一级缓存

    User user = new User();
    user.setId(3);
    user.setUsername("edgar");
    userMapper3.updateUser(user);
    sqlSession3.commit(); //清空二级缓存

    // 第二次查询id为1的用户
    User user2 = userMapper2.findUserById(1);
    sqlSession2.close();

    System.out.println(user1 == user2);
}

在这里插入图片描述
在 sqlSession3 更新数据库,并提交事务后,sqlsession2 的 UserMapper namespace 下的查询走了数据库,没有走 Cache。

多表操作一定不能使用缓存

首先不管多表操作写到那个namespace下,都会存在某个表不在这个namespace下的情况。
例如两个表:role和user_role,如果我想查询出某个用户的全部角色role,就一定会涉及到多表的操作。

<select id="selectUserRoles" resultType="UserRoleVO">
    select * from user_role a,role b where a.roleid = b.roleid and a.userid = #{userid}
</select>

不管是写到RoleMapper.xml还是UserRoleMapper.xml,或者是一个独立的XxxMapper.xml中。如果使用了二级缓存,都会导致上面这个查询结果可能不正确。
如果你正好修改了这个用户的角色,上面这个查询使用缓存的时候结果就是错的。
这点应该很容易理解。

useCache 和 flushCache

<select id="findAll" resultMap="userMap" useCache="false" flushCache="true">
    select * from user u left join orders o on u.id = o.uid
</select>

设置 statement 配置中的 flushCache=“true” 属性,默认情况下为 true,即刷新缓存,一般执行完 commit 操作都需要刷新缓存,flushCache=“true” 表示刷新缓存,这样可以避免增删改操作而导致的脏读问题。默认不要配置。

<select id="findAll" resultMap="userMap" useCache="false">
    select * from user u left join orders o on u.id = o.uid
</select>

useCache 是用来设置是否禁用二级缓存的,在 statement 中设置 useCache=“false”,可以禁用当前 select 语句的二级缓存,即每次都会去数据库查询。

参考文章:
深入浅出 MyBatis 的一级、二级缓存机制
Mybatis 一级缓存
mybatis的一级缓存和二级缓存

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

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

相关文章

Delphi 10.4.2使用传统代码提示方案(auto complete)(转)

Delphi 10.4重点是实现了LSP&#xff0c;但现在最新的10.4.2还是不成熟&#xff0c;无法满足日常需要&#xff0c;不过没关系&#xff0c;可以设置为原有的方案&#xff0c;如下图&#xff1a;具体操作&#xff1a;Tools->Options->Editor->language->Code Insight…

迷宫问题图解 : 基于骨架提取、四邻域

目录 1. 迷宫的连通域 2. How to remove branch &#xff1f; 3. 基于4邻域的 remove 分支 3.1 找到分支的端点 3.2 4邻域的 remove 分支 3.3 循环移除分支 3.4 code 4. 迷宫路线 4.1 预处理 4.2 提取骨架 4.3 分支的端点 4.4 去除分支的端点 4.5 循环去除分支 4…

Java-合并两个链表

每日一题 Java-合并两个链表 给你两个链表 list1 和 list2 &#xff0c;它们包含的元素分别为 n 个和 m 个。 请你将 list1 中下标从 a 到 b 的全部节点都删除&#xff0c;并将list2 接在被删除节点的位置。 下图中蓝色边和节点展示了操作后的结果&#xff1a; 请你返回结果…

linux下redis安装 及常用命令

安装及常用命令 redis的yum方式安装 先查看是否已经安装redis执行命令 rpm -qa | grep redis如果存在&#xff0c;将存在的卸载&#xff1a;(-y 代表自动选择) yum remove xxx -y在线安装redis yum install redis安装本地已经下载好的redis安装包 yum localinstall redis6.2…

基于Spring、Spring MVC、MyBatis的招聘管理系统

文章目录项目介绍主要功能截图&#xff1a;首页账户管理招聘建议部分代码展示设计总结项目获取方式&#x1f345; 作者主页&#xff1a;Java韩立 &#x1f345; 简介&#xff1a;Java领域优质创作者&#x1f3c6;、 简历模板、学习资料、面试题库【关注我&#xff0c;都给你】 …

流程引擎之Camunda简介

背景Camunda 是支持 BPMN&#xff08;工作流和流程自动化&#xff09;、CMMN&#xff08;案例管理&#xff09; 和 DMN&#xff08;业务决策管理&#xff09; java 框架。Camunda 基于Activiti5 保留了 PVM&#xff0c;其开发团队也是从 activiti 中分裂出来的。Camunda 来自拉…

KubeSphere实战

文章目录一、KubeSphere平台安装1、Kubernetes上安装KubeSphere1.1 安装docker1.2 安装Kubernetes1.3 前置环境之nfs存储1.4 前置环境之metrics-server1.5 安装KubeSphere2、Linux单节点部署KubeSphere3、Linux多节点部署KubeSphere(推荐)二、KubeSphere实战1、多租户实战2、中…

Spring中的数据校验--进阶

分组校验 场景描述 在实际开发中经常会遇到这种情况&#xff1a;添加用户时&#xff0c;id是由后端生成的&#xff0c;不需要校验id是否为空&#xff0c;但是修改用户时就需要校验id是否为空。如果在接收参数的User实体类的id属性上添加NotNull&#xff0c;显然无法实现。这时…

【飞桨AI-Python小白逆袭大神课程】作业3-《青春有你2》选手数据分析

目录 一、数据准备 1、文件数据以json文件格式保存&#xff1a; 二、数据分析 2、数据分析四剑客&#xff1a; &#xff08;1&#xff09;Numpy &#xff08;2&#xff09;pandas &#xff08;3&#xff09;Matplotlib &#xff08;4&#xff09;PIL &#xff08;5&#x…

操作系统题目收录(十一)

1、操作系统采用分页存储管理方式&#xff0c;要求&#xff08;&#xff09;。 A&#xff1a;每个进程拥有一张页表&#xff0c;且进程的页表驻留在内存中B&#xff1a;每个进程拥有一张页表&#xff0c;但只有执行进程的页表驻留在内存中C&#xff1a;所有进程共享一张页表&a…

django项目实战(django+bootstrap实现增删改查)

目录 一、创建django项目 二、修改默认配置 三、配置数据库连接 四、创建表结构 五、在app当中创建静态文件 六、页面实战-部门管理 1、实现一个部门列表页面 2、实现新增部门页面 3、实现删除部门 4、实现部门编辑功能 七、模版的继承 1、创建模板layout.html 1&…

Django框架之模型视图--Session

Session 1 启用Session Django项目默认启用Session。 可以在settings.py文件中查看&#xff0c;如图所示 如需禁用session&#xff0c;将上图中的session中间件注释掉即可。 2 存储方式 在settings.py文件中&#xff0c;可以设置session数据的存储方式&#xff0c;可以保存…

基于springboot的网上图书商城的设计与实现(程序+详细设计文档)

大家好✌&#xff01;我是CZ淡陌。在这里为大家分享优质的实战项目&#xff0c;本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目&#xff0c;希望你能有所收获&#xff0c;少走一些弯路&#xff01; &#x1f345;更多优质项目&#x1f447;&…

Rust学习入门--【17】Rust Slice(切片)类型

系列文章目录 Rust 语言是一种高效、可靠的通用高级语言&#xff0c;效率可以媲美 C / C 。本系列文件记录博主自学Rust的过程。欢迎大家一同学习。 Rust学习入门–【1】引言 Rust学习入门–【2】Rust 开发环境配置 Rust学习入门–【3】Cargo介绍 Rust学习入门–【4】Rust 输…

RocketMQ云服务器和本地基础安装搭建及可视化控制台安装使用

一起学编程&#xff0c;让生活更随和&#xff01; 如果你觉得是个同道中人&#xff0c;欢迎关注博主gzh&#xff1a;【随和的皮蛋桑】。 专注于Java基础、进阶、面试以及计算机基础知识分享&#x1f433;。偶尔认知思考、日常水文&#x1f40c;。 目录一、RocketMQ 介绍1、Ro…

分布式事务--理论基础

1、事务基础 1.1、什么是事务 事务可以看做是一次大的活动&#xff0c;它由不同的小活动组成&#xff0c;这些活动要么全部成功&#xff0c;要么全部失败。 1.2、本地事务 在同一个进程内&#xff0c;控制同一数据源的事务&#xff0c;称为本地事务。例如数据库事务。 在计…

PyTorch 并行训练 DistributedDataParallel完整代码示例

使用大型数据集训练大型深度神经网络 (DNN) 的问题是深度学习领域的主要挑战。 随着 DNN 和数据集规模的增加&#xff0c;训练这些模型的计算和内存需求也会增加。 这使得在计算资源有限的单台机器上训练这些模型变得困难甚至不可能。 使用大型数据集训练大型 DNN 的一些主要挑…

SpringBoot监控

文章目录一、PrometheusGrafana监控Springboot1、简介2、SpringBoot应用镜像搭建2.1 springboot应用创建2.2 镜像创建3、Prometheus3.1 概述3.2 Prometheus创建4、Grafana可视化监控4.1 可视化4.2 告警设置二、轻量级日志系统Loki1、简介1.1 介绍1.2 与ELK差异2、grafana loki日…

linux宝塔安装和部署node全栈项目

使用服务器:阿里云ECS系列 服务器操作系统: Alibaba Cloud Linux 2.1903 LTS 64位 连接服务器方式: Workbench远程连接 使用公网IP登录 Workbench远程桌面&#xff0c;使用命令安装linux宝塔面板操作服务器: 1.登录linux宝塔面板&#xff0c;使用终端命令安装linux宝塔 yum i…

【操作系统】计算机系统概述

文章目录操作系统的概念、功能和目标熟悉的操作系统计算机系统的层次结构操作系统的概念操作系统的功能和目标作为系统资源的管理者作为用户和计算机之间的接口作为最接近硬件的层次操作系统的四个特征并发共享并发和共享的关系虚拟异步操作系统的发展和分类手工操作阶段单道批…