Mybatis引出的一系列问题-Mybatis缓存机制的探究

Mybatis 使用到了两种缓存:本地缓存(local cache)和二级缓存(second level cache)。

一级缓存默认是开启的,而且不能关闭,MyBatis的一些关键特性(例如通过<association><collection>建立级联映射、避免循环引用(circular references)、加速重复嵌套查询等)都是基于MyBatis一级缓存实现的,而且MyBatis结果集映射相关代码重度依赖CacheKey,所以目前MyBatis不支持关闭一级缓存。

虽然我们不能关闭一级缓存,但是我们可以更改他的作用范围: MyBatis提供了一个配置参数localCacheScope,用于控制一级缓存的级别,该参数的取值为SESSIONSTATEMENT当指定localCacheScope参数值为SESSION时,缓存对整个SqlSession有效,只有执行DML语句(更新语句)时,缓存才会被清除当localCacheScope值为STATEMENT时,缓存仅对当前执行的语句有效,当语句执行完毕后,缓存就会被清空

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

能更改一级缓存的作用范围这一点很重要后面我们讲解中会用到这一特性。

一级缓存默认是SqlSession级别的: 在操作数据库时需要构造 sqlSession 对象,在对象中有一个(内存区域)数据结构(HashMap)用于存储缓存数据。不同的 sqlSession 之间的缓存数据区域(HashMap)是互相不影响的。用一张图来表示一下一级缓存,其中每一个 SqlSession 的内部都会有一个一级缓存对象。
在这里插入图片描述
1、一级缓存同一个会话共享数据 模拟思路:打开一个会话,进行两次查询通过日志查看第二次是否走数据库。

  @Testpublic void testSession() throws IOException {try (SqlSession sqlSession = sqlSessionFactory.openSession()) {OrderMapper orderMapper = sqlSession.getMapper(OrderMapper.class);List<Order> orders = orderMapper.queryById(620898339119480832L);System.out.println("第一次查询:" + JSON.toJSONString(orders));OrderMapper orderMapper2 = sqlSession.getMapper(OrderMapper.class);List<Order> orders2 = orderMapper2.queryById(620898339119480832L);System.out.println("第二次查询:" + JSON.toJSONString(orders2));}}

在这里插入图片描述
分析:第一次查询打印了 sql 日志信息,说明是通过数据库获取到数据,第二次也查询到数据但是没有打印日志信息,说明走了缓存。
结论:一级缓存同一个会话共享数据。

2、同一个会话如果有更新操作则缓存清除 模拟思路:打开一个会话,先进行查询,然后进行更新操作,最后再次查询刚才的语句,看是否打印查询数据库的 sql 日志(这些操作都是对一条数据的操作)。

@Test
public void testUpdate() throws IOException {try (SqlSession sqlSession = sqlSessionFactory.openSession()) {// 同一个会话 第一次查询System.out.println("第一次会会话的 第一次查询");OrderMapper orderMapper = sqlSession.getMapper(OrderMapper.class);List<Order> orders = orderMapper.queryById(620898339119480832L);Order order = orders.get(0);order.setAmount(12L);// 进行更新orderMapper.updateByPrimaryKey(order);// 同一个会话 第二次查询System.out.println("第一次会会话的 第二次查询");List<Order> orders2 = orderMapper.queryById(620898339119480832L);System.out.println(JSON.toJSONString(orders2));}
}

在这里插入图片描述
分析:第一次查询打印了 sql 日志,然后进行数据更新,最后进行第二次查询发现仍旧查询数据库,说明缓存已经失效。
结论:同一个会话如果有更新操作则缓存清除。

3、导致脏数据 模拟思路:打开两个会话,第一个会话查询数据库获取数据后 ,接着第二个会话修改数据,最后第一个会话再查询数据,那最后这次查询如果和第一次查询相同,那说明一级缓存会导致脏数据问题。

@Testpublic void testDirtyData() throws IOException {SqlSession sqlSession1 = sqlSessionFactory.openSession(true);SqlSession sqlSession2 = sqlSessionFactory.openSession(true);try {// 同一个会话 第一次查询OrderMapper orderMapper1 = sqlSession1.getMapper(OrderMapper.class);OrderMapper orderMapper2 = sqlSession2.getMapper(OrderMapper.class);List<Order> orders = orderMapper1.queryById(620898339119480832L);System.out.println("第一次会会话第一次查询的结果" + orders);Order order1 = getOrder(orders);System.out.println("=========更新数据======");orderMapper2.updateByPrimaryKey(order1);sqlSession2.commit();List<Order> orders1 =  orderMapper1.queryById(620898339119480832L);System.out.println("第二次查询:"+JSON.toJSONString(orders1));}catch (Exception e){sqlSession1.close();sqlSession2.close();}}

在这里插入图片描述
分析:第一个会话第一次查询 amount 值是1212,第二会话将 amount 更改为666,第一个会话再次查询数据获取的 amount 值为1212,这说明第一个会话的第二次查询命中缓存导致了脏数据问题。
结论:一级缓存在多会话中会导致脏数据。
解决方式:在配置一级缓存作用范围的时候将其设置为 STATEMENT,那么缓存仅对当前执行的语句有效,当语句执行完毕后,缓存就会被清空。

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

你可以随时调用以下方法来清空本地缓存:

void clearCache()

二级缓存: 二级缓存的原理和一级缓存原理一样,第一次查询,会将数据放入缓存中,然后第二次查询则会直接去缓存中取。但是一级缓存是基于sqlSession的,而二级缓存是基于mapper文件的namespace的,也就是说多个sqlSession可以共享一个mapper中的二级缓存区域,并且如果两个mapper的namespace 相同,即使是两个mapper,那么这两个mapper中执行sql查询到的数据也将存在相同的二级缓存区域中。
在这里插入图片描述
和一级缓存默认开启不一样,二级缓存需要我们手动开启首先在全局配置文件sqlMapConfig.xml文件中加入如下代码:

<!--开启二级缓存--><settings><setting name="cacheEnabled" value="true"/>
</settings>

其次在UserMapper.xml文件中开启缓存(如果我们是基于注解形式进行查询,可以在mapper查询接口上添加@CacheNamespace注解开启二级缓存)

方式一:在xml文件中配置
<!--开启二级缓存-->
<cache></cache>方式二:在mapper接口上添加上
@CacheNamespace
后面可以带参数指定加载自定义的缓存实现类
@CacheNamespace(implementation = RedisCache.class)//开启二级缓存

我们可以看到mapper.xml文件中就这么一个空标签,其实这里可以配置,PerpetualCache这个类是mybatis默认实现缓存功能的类。我们不写type就使用mybatis默认的缓存,也可以去实现Cache接口来自定义缓存。
在这里插入图片描述

public class PerpetualCache implements Cache {private final String id;private MapcObject, Object> cache = new HashMapC);public PerpetualCache(St ring id) { this.id = id;
}

我们可以看到二级缓存底层还是HashMap结构。

public class User implements Serializable(//⽤户IDprivate int id;//⽤户姓名private String username;//⽤户性别private String sex;
}

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

测试⼆级缓存和sqlSession无关,可以看出下面两个不同的sqlSession,第一个关闭了,第二次查询依然不发出sql查询语句。

@Test
public void testTwoCache(){//根据 sqlSessionFactory 产⽣ sessionSqlSession sqlSession1 = sessionFactory.openSession();SqlSession sqlSession2 = sessionFactory.openSession();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 ,sqlsession关闭后就会清除一级缓存//第⼆次查询,即使sqlSession1已经关闭了,这次查询依然不发出sql语句User u2 = userMapper2.selectUserByUserId(1);System.out.println(u2);sqlSession2.close();
}

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

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

在这里插入图片描述
mybatis中还可以配置userCacheflushCache等配置项,userCache是用来设置是否禁用二级缓存的,在statement中设置useCache=false可以禁⽤当前select语句的二级缓存,即每次查询都会发出sql去查询,默认情况是true,即该sql使用二级缓存,还可以使用注解方式禁用@Options(useCache=false),flushCache的注解配置方法同上 。

方法一:
<select id="selectUserByUserId" useCache="false" resultType="com.lagou.pojo.User" parameterType="int">select * from user where id=#{id}
</select> 方法二:
@Options(useCache = false)
@Select({"select * from user where id = #{id}"})
public User findUserById(Integer id);

这种情况是针对每次查询都需要最新的数据sql,要设置成useCache=false,禁用二级缓存,直接从数据库中获取。

在mapper的同一个namespace中,如果有其它insert、update, delete操作数据后需要刷新缓存,如果不执行刷新缓存会出现脏读。

设置statement配置中的flushCache="true”属性,默认情况下为true,即刷新缓存,如果改成false则 不会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。

一般下执行完commit操作都需要刷新缓存,flushCache=true表示刷新缓存,这样可以避免数据库脏读。所以我们不用设置,默认即可。

一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中,如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中,新的会话查询信息,就可以从二级缓存中获取内容,同的mapper查出的数据会放在自己对应的缓存(map)中。

  1. 只要开启了二级缓存,我们在同一个Mapper【namespace】中的查询,可以在二级缓存中拿到数据
  2. 查出的数据都会被默认先放在一级缓存中
  3. 只有会话提交或者关闭以后,一级缓存中的数据才会转到二级缓存中

缓存原理:

  1. 用户先查二级缓存
  2. 再查询一级缓存
  3. 最后查询数据库

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

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

相关文章

Nginx学习教程(基础篇)

目录 一、Nginx安装 二、Nginx基本使用 2.1、目录结构 conf html logs sbin 2.2、基本运行原理 2.3、nginx.conf最小配置解析 worker_processes worker_connections include mime.types default_type application/octet-stream sendfile on keepalive_timeout…

eBay逆变器UL458检测报告

逆变器是把直流电能&#xff08;电池、蓄电瓶&#xff09;转变成交流电&#xff08;一般为220V,50Hz正弦波&#xff09;。它由逆变桥、控制逻辑和滤波电路组成。逆变器是一种DC to AC的变压器&#xff0c;它其实与转化器是一种电压逆变的过程。广泛适用于办公设备&#xff0c;生…

【MYSQL】DataGrip连接linux本地mysql失败:Connection refused

防火墙需要开放3306端口 sudo ufw allow 3306 要么就把防火墙关了&#xff1a; sudo ufw disablemysql开放连接 记住你的密码 ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password by 123456;修改配置文件 sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf这个…

Vue的 hash 模式与 history 模式

为了能够在改变视图的同时&#xff0c;不向后端发出网络请求。浏览器提供了 hash 模式与 history 模式。 而 vue 中的路由器就是利用了这两种模式&#xff0c;来实现前端路由的。 路由器的 hash 模式&#xff1a; 一、在 router 目录下的 index.js 文件中&#xff0c;通过 m…

什么是搜索引擎?2023 年搜索引擎如何运作?

目录 什么是搜索引擎&#xff1f;搜索引擎的原理什么是搜索引擎爬取&#xff1f;什么是搜索引擎索引&#xff1f;什么是搜索引擎检索?什么是搜索引擎排序&#xff1f; 搜索引擎的目的是什么&#xff1f;搜索引擎如何赚钱&#xff1f;搜索引擎如何建立索引?网页抓取文本处理建…

[Docker实现测试部署CI/CD----Jenkins集成相关服务器(3)]

目录 7、 Jenkins 集成 SonarQubeJenkins 中安装 SonarScanner下载移动修改配置文件 8、Jenkins配置SonarQube安装插件添加SonarQube添加 SonarScanner 9、Jenkins集成目标服务器 7、 Jenkins 集成 SonarQube Jenkins 中安装 SonarScanner SonarScanner 是一种代码扫描工具&am…

express学习笔记6 - 用户模块

新建router/user.js const express require(express) const routerexpress.Router() router.get(/login, function(req, res, next) {console.log(/user/login, req.body)res.json({code: 0,msg: 登录成功})})module.exportsrouter 在router/user.js引入并使用 const us…

Mybatis ,Mybatis-plus列表多字段排序,包含sql以及warpper

根据 mybatis 根据多字段排序已经wrapper 根据多字段排序 首先根据咱们返回前端的数据列来规划好排序字段 如下&#xff1a; 这里的字段为返回VO的字段,要转换成数据库字段然后加入到排序中 示例&#xff0c;穿了 surname,cerRank 多字段,然后是倒序 false 首先创建好映射&am…

从零搭建一个react + electron项目

最近打算搭建一个react electron的项目&#xff0c;发现并不是那么傻瓜式 于是记录一下自己的实践步骤 通过create-react-app 创建react项目 npx create-react-app my-app 安装electron依赖 npm i electron -D暴露react项目的配置文件&#xff08;这一步看自己需求&#xff0c…

【LeetCode】5. 最长回文串

题目链接 文章目录 1. 思路讲解2. 代码实现 1. 思路讲解 与求回文子串思路差别不大 在做这道题目之前&#xff0c;可以先做一下另一道回文子串的题目&#xff0c;如果会了那道求回文子串的题目&#xff0c;这道题基本上也就会了。 回文子串的题解在这里 它也就是求出每一个回…

Eureka 学习笔记2:EurekaClient

版本 awsVersion ‘1.11.277’ EurekaClient 接口实现了 LookupService 接口&#xff0c;拥有唯一的实现类 DiscoveryClient 类。 LookupService 接口提供以下功能&#xff1a; 获取注册表根据应用名称获取应用根据实例 id 获取实例信息 public interface LookupService<…

Linux中的file命令:查看文件类型

2023年8月1日&#xff0c;周二上午 目录 简要说明使用方法MIME类型举例说明 简要说明 在Linux中&#xff0c;file命令用于识别文件类型。 file命令可以识别各种类型的文件&#xff0c;包括普通文件、目录、符号链接、设备文件、压缩文件、二进制可执行文件等。 它是一个非常…

防火墙监控工具

防火墙监控是跟踪在高效防火墙性能中起着关键作用的重要防火墙指标&#xff0c;防火墙监控通常应包括&#xff1a; 防火墙日志监控防火墙规则监控防火墙配置监控防火墙警报监控 防火墙监控服务的一个重要方面是它应该是主动的。主动识别内部和外部安全威胁有助于在早期阶段识…

arm 函数栈回溯

大概意思就是arm每个函数开始都会将PC、LR、SP以及FP四个寄存器入栈。 下面我们看一下这四个寄存器里面保存的是什么内存 arm-linux-gnueabi-gcc unwind.c -mapcs -w -g -o unwind&#xff08;需要加上-mapcs才会严格按照上面说的入栈&#xff09; #include <stdio.h> …

Rpc异步日志模块

Rpc异步日志模块作用 在一个大型分布式系统中&#xff0c;任何部署的分布式节点都可能发生崩溃&#xff0c;试想如果用普通的办法&#xff0c;即先排查哪个节点down掉了&#xff0c;找到down掉的节点后采取调试工具gdb调试该节点&#xff0c;进而排查宕机的原因。这中排查方法…

文心一言 VS 讯飞星火 VS chatgpt (70)-- 算法导论6.5 9题

九、请设计一个时间复杂度为 (n lgk)的算法&#xff0c;它能够将 k 个有序链表合并为一个有序链表&#xff0c;这里 n 是所有输入链表包含的总的元素个数。(提示:使用最小堆来完成 k 路归并。 文心一言&#xff1a; 要设计一个时间复杂度为 O(n log k) 的算法&#xff0c;将 …

leetcode----JavaScript 详情题解(1)

目录 2618. 检查是否是类的对象实例 2619. 数组原型对象的最后一个元素 2620. 计数器 2621. 睡眠函数 2622. 有时间限制的缓存 2623. 记忆函数 2625. 扁平化嵌套数组 2626. 数组归约运算 2627. 函数防抖 2618. 检查是否是类的对象实例 请你编写一个函数&#xff0c;…

Python 程序设计入门(001)—— 安装 Python(Windows 操作系统)

Python 程序设计入门&#xff08;001&#xff09;—— 安装 Python&#xff08;Windows 操作系统&#xff09; 目录 Python 程序设计入门&#xff08;001&#xff09;—— 安装 Python&#xff08;Windows 操作系统&#xff09;一、下载 Python 安装包二、安装 Python三、测试&…

Android四大组件之服务

为什么要使用服务呢&#xff1f; 从上面的文字说&#xff0c;我们知道这个服务是用于执行长期后台运行的操作。有些时候&#xff0c;我们没有界面&#xff0c;但是程序仍然需要工作。比如说&#xff0c;我们播放音乐&#xff0c;在后台播放音乐。比如说&#xff0c;我们下载任…

SpringBoot内嵌的Tomcat:

SpringBoot内嵌Tomcat源码&#xff1a; 1、调用启动类SpringbootdemoApplication中的SpringApplication.run()方法。 SpringBootApplication public class SpringbootdemoApplication {public static void main(String[] args) {SpringApplication.run(SpringbootdemoApplicat…