【零基础入门MyBatis系列】第十三篇——缓存机制

news/2024/7/24 3:11:28 标签: mybatis, 缓存, java

一、概述

🌔 1、如何理解缓存

  • 提前将数据放到缓存中(内存中),下一次用的时候,直接从缓存中取数据,效率更高。
  • 使用减少文件读写(IO)的方式提高执行效率
  • 什么时候会使用缓存机制?–执行完全相同的SQL语句,可能会用到我们的缓存数据【因为如果两个语句之间可能发生清除缓存的操作】

在这里插入图片描述

  • 缓存技术是我们用来优化程序的重要手段,还有哪些缓存技术呢?
    • 字符串常量池
    • 整数型常量池
    • 线程池
    • 连接池

🌔 2、MyBatis 中的缓存是怎样的呢?

  • 针对 select 语句的查询结果进行缓存
  • MyBatis 缓存分为三种:
    • 一级缓存:将查询到的数据存储到SqlSession中。
    • 二级缓存:将查询到的数据存储到SqlSessionFactory中。
    • 集成其他的缓存EhCacheJava语言开发的】、MemcacheC语言开发的】等。

🌔 3、准备工作:

  • 创建一个新模块:mybatis-011-catch
  • 确定打包方式、加载依赖、创建对应包结构
    在这里插入图片描述
  • 老样子,还是对三兄弟进行编写和测试

二、一级缓存

🌔 1、如何使用一级缓存

  • 一级缓存不需要任何配置,默认为开启状态
  • 一级缓存是针对同一个SqlSession,当我们执行相同的查询语句,会直接从一级缓存中取出数据

🌔 2、假如我们想根据 id 查询汽车信息:

(1)在CarMapper接口中写一个方法

java">/**
* 根据 id 查询汽车信息
* @param id
* @return
*/
Car selectById(Long id);

(2)在 CarMapper,xml 映射文件中编写对应的SQL

java"><select id="selectById" resultType="Car">
	select * from t_car where id = #{id}
</select>

(3)想要看出用没用一级缓存,重点在测试方法的编写

$A: 我们直接查询相同 id 的汽车信息

java">@Test
public void testSelectById(){
	SqlSession sqlSession = SqlSessionUtil.openSession();
	CarMapper mapper = sqlSession.getMapper(CarMapper.class);

	Car car = mapper.selectById(31L);
	System.out.println(car);

	Car car1 = mapper.selectById(31L);
	System.out.println(car1);

	sqlSession.close();
}

在这里插入图片描述
此处我们可以看到就执行了一条 select * from t_car where id = 31L【这所以这里就出现了 Cache Hit Ratio 是因为我开启了二级缓存

$B:因为我们这里用的是同一个接口实现类,如果不是一个,那么一级缓存还会有效吗?

java">@Test
public void testSelectById(){
	SqlSession sqlSession = SqlSessionUtil.openSession();
	CarMapper mapper1 = sqlSession.getMapper(CarMapper.class);
	CarMapper mapper2 = sqlSession.getMapper(CarMapper.class);
	Car car = mapper1.selectById(31L);
	System.out.println(car);

	Car car1 = mapper2.selectById(31L);
	System.out.println(car1);

	sqlSession.close();
}

运行结果是一样的,说明只要满足在一个SqlSession下,相同的查询语句就会使用一级缓存

$C: 我们最后再验证一下,如果不同的SqlSession,一级缓存是否还有效?

java">@Test
public void testSelectById() throws Exception{
	// 利用原始的创建会话的方法
	SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
	// 创建两个会话
	SqlSession sqlSession1 = sqlSessionFactory.openSession();
	SqlSession sqlSession2 = sqlSessionFactory.openSession();
	// 创建对应的接口实现类
	CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);
	CarMapper mapper2 = sqlSession2.getMapper(CarMapper.class);
	// 调用查询方法
	Car car1 = mapper1.selectById(32L);
	Car car2 = mapper2.selectById(32L);

	System.out.println(car1);
	System.out.println(car2);

	sqlSession1.close();
	sqlSession2.close();
}

此处我们使用了SqlSessionFactory的openSession方法,这个方法每次调用都会创建一个新的连接对象
在这里插入图片描述
可以看出,不同的SqlSession对象之间是无法使用彼此的一级缓存的。

🌔 3、有哪些情况不会走一级缓存

  • 经过上面的测试,不同的SqlSession对象
  • 不是完全相同的查询语句
  • 一级缓存失效了
    • 在两次查询之间,通过sqlSession.clearCache();手动清理了缓存
    • 在两次查询之间,调用了其他 insert / update / delete 操作 【无论是对哪个表的操作,都会清空一级缓存

三、二级缓存

🌔 1、一级缓存与二级缓存的区别?

  • 一级缓存的有效区域为当前的 SqlSession,二级缓存的有效区域是整个 SqlSessionFactory
  • 二级缓存中的内容,是由一级缓存中的内容生成的

🌔 2、我们如何才能使用二级缓存

  • 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存【默认已经配置好了<setting name="cacheEnabled" value="true">
  • 在我们需要使用二级缓存的映射文件中添加 <catch />
  • 使用二级缓存的实体类对象必须是可序列化的 【让我们的pojo类去实现java.io.Serializable接口】
  • SqlSession对象关闭或提交之后,一级缓存中的数据才会被写入到二级缓存当中。此时二级缓存才可用。

🌔 3、演示我们是如何使用二级缓存的?

  • 还是以根据 id 查询汽车信息为例
  • 此处我们重写一下接口方法和SQL语句,与之前的区别其实仅仅差一个 <catch />

(1)接口方法

java">/**
* 测试二级缓存
* @param id
* @return
*/
Car selectById2(Long id);

(2)Mapper.xml 中添加catch标签和对应SQL

java"><cache />
<select id="selectById2" resultType="Car">
	select * from t_car where id = #{id}
</select>

(3)接下来就是测试部分了:$A – 没关闭连接,没使用二级缓存$B – 关闭连接了,使用了二级缓存

$A: 为了验证第二问第四点,我们第一个会话查询结束后先不关闭,第二个直接查询

java">@Test
public void testSelectById2() throws Exception{
	// 一个会话工厂
	SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
	// 创建两个会话
	SqlSession sqlSession1 = sqlSessionFactory.openSession();
	SqlSession sqlSession2 = sqlSessionFactory.openSession();
	// 创建对应的接口实现类
	CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);
	CarMapper mapper2 = sqlSession2.getMapper(CarMapper.class);
	// 调用查询方法
	Car car1 = mapper1.selectById(32L);
	System.out.println(car1);
	// 我们没有关闭会话,所以不会将一级缓存写入到二级缓存
	Car car2 = mapper2.selectById(32L);
	System.out.println(car2);

	sqlSession1.close();
	sqlSession2.close();
}

通过结果我们可以看到,当我们不关闭前一个查询的连接,确实没有将一级缓存的内容写入二级缓存,底层还是执行了两次查询

在这里插入图片描述

$B: 这回我们执行完第一条查询就将这个会话关闭,看这次二级缓存中是否有数据

java">@Test
public void testSelectById2() throws Exception{
	// 一个会话工厂
	SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
	// 创建两个会话
	SqlSession sqlSession1 = sqlSessionFactory.openSession();
	SqlSession sqlSession2 = sqlSessionFactory.openSession();
	// 创建对应的接口实现类
	CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);
	CarMapper mapper2 = sqlSession2.getMapper(CarMapper.class);
	// 调用查询方法
	Car car1 = mapper1.selectById(32L);
	System.out.println(car1);
	sqlSession1.close();
	// 我们没有关闭会话,所以不会将一级缓存写入到二级缓存
	Car car2 = mapper2.selectById(32L);
	System.out.println(car2);
	sqlSession2.close();
}

很明显可以看到,本次只执行了一次SQL语句
在这里插入图片描述
我们看到的 Cache Hit Ratio 第一条为 0,第二条为 0,5 是怎么回事儿呢?

首先这条语句代表的是二级缓存的命中率,第一次执行这条SQL,缓存中没有,所以就为零,然而第二次执行缓存中已经有了,所以命中率为两次执行命中一次,以此类推 2/3 …

🌔 4、二级缓存还可以进行一些自定义配置:

  • 就是我们添加的 catch 标签有一些属性【具体可参考 官方文档】
  • eviction:指定从缓存中移除某个对象的淘汰算法。默认采用LRU策略。
    • LRU:优先淘汰在间隔时间内使用频率最低的对象。【最近最少使用】
    • FIFO:先进入二级缓存的对象最先被淘汰。
    • SOFT:软引用。淘汰软引用指向的对象。具体算法和JVM的垃圾回收算法有关。
    • WEAK:弱引用。淘汰弱引用指向的对象。具体算法和JVM的垃圾回收算法有关。
  • flushInterval
    • 二级缓存的刷新时间间隔。单位毫秒。如果没有设置。就代表不刷新缓存
    • 发生增删改也会刷新缓存
  • readOnly
    • true:多条相同的sql语句执行之后返回的对象是共享的同一个。性能好。但是多线程并发可能会存在安全问题。
    • false:多条相同的sql语句执行之后返回的对象是副本,调用了clone方法。性能一般。但安全。
  • size
    • 设置二级缓存中最多可存储的java对象数量。【默认值1024】

四、使用集成的EhCache

  • mybatis 对外提供了接口,也集成第三方的缓存组件。比如EhCache、Memcache等。【此处我们以EhCache为例】
  • 使用步骤如下:

(1)引入依赖

java"><!--mybatis集成ehcache的组件-->
<dependency>
  <groupId>org.mybatis.caches</groupId>
  <artifactId>mybatis-ehcache</artifactId>
  <version>1.2.2</version>
</dependency>

(2)在类的根路径下创建 ehcache.xmlidea中就是resources目录下】

java"><?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="false">
    <!--磁盘存储:缓存中暂时不使用的对象,转移到硬盘,类似于Windows系统的虚拟内存-->
    <diskStore path="e:/ehcache"/>
  
    <!--defaultCache:默认的管理策略-->
    <!--eternal:设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断-->
    <!--maxElementsInMemory:在内存中缓存的element的最大数目-->
    <!--overflowToDisk:如果内存中数据超过内存限制,是否要缓存到磁盘上-->
    <!--diskPersistent:是否在磁盘上持久化。指重启jvm后,数据是否有效。默认为false-->
    <!--timeToIdleSeconds:对象空闲时间(单位:秒),指对象在多长时间没有被访问就会失效。只对eternal为false的有效。默认值0,表示一直可以访问-->
    <!--timeToLiveSeconds:对象存活时间(单位:秒),指对象从创建到失效所需要的时间。只对eternal为false的有效。默认值0,表示一直可以访问-->
    <!--memoryStoreEvictionPolicy:缓存3 种清空策略-->
    <!--FIFO:first in first out (先进先出)-->
    <!--LFU:Less Frequently Used (最少使用).意思是一直以来最少被使用的。缓存的元素有一个hit 属性,hit 值最小的将会被清出缓存-->
    <!--LRU:Least Recently Used(最近最少使用). (ehcache 默认值).缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存-->
    <defaultCache eternal="false" maxElementsInMemory="1000" overflowToDisk="false" diskPersistent="false"
                  timeToIdleSeconds="0" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LRU"/>

</ehcache>

(3)在我们的映射文件中添加 catch 标签

java"><cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

(4)至此,我们指定的映射文件的二级缓存就替换成了 EhCache缓存【对一级缓存没有影响】


http://www.niftyadmin.cn/n/4559.html

相关文章

刷题笔记之七(统计每个月兔子的总数+汽水瓶+查找两个字符串a,b中的最长公共子串+公共子串计算)

目录 1. 数据库中&#xff0c;count不会返回null值&#xff0c;max和concat可能会返回null值 2. 数据库特点&#xff1a; 共享性高&#xff0c;冗余度小&#xff0c;安全性强&#xff0c;独立性强 3. top是sql server中的关键字&#xff0c;用于求前n条数据 4. 数据库使用…

idea启动项目很久很慢的一种解决方案

一、问题描述 IntelliJ idea 在启动项目时&#xff0c;很久很慢。 二、解决 在不买个更强更贵的前提下&#xff0c;有以下一种解决方案(ಥ_ಥ) ​​​​​​​ 1、方案依据 一般地&#xff0c;JVM实例默认最大堆内存是机器的1/64&#xff0c;在启动时会不断地fullGC&#xf…

HTTP协议详解

1.HTTP协议介绍 先来给大家介绍以下HTTP&#xff1a; HTTP&#xff08;Hyper Text Transfer Protocol&#xff09;&#xff1a; 全称超文本传输协议&#xff0c;是用于从万维网&#xff08;WWW:World Wide Web &#xff09;服务器传输超文本到本地浏览器的传送协议。HTTP 是一种…

信息学奥赛一本通:1170:计算2的N次方

1170&#xff1a;计算2的N次方 时间限制: 1000 ms 内存限制: 65536 KB 提交数: 27334 通过数: 14606 【题目描述】 任意给定一个正整数N(N<100)&#xff0c;计算2的n次方的值。 【输入】 输入一个正整数N。 【输出】 输出2的N次方的值。 【输入样例】 5 【输…

Qt编写ffmpeg本地摄像头显示(16路本地摄像头占用3.2%CPU)

一、前言 内核ffmpeg除了支持本地文件、网络文件、各种视频流播放以外&#xff0c;还支持打开本地摄像头&#xff0c;和正常的解析流程一致&#xff0c;唯一的区别就是在avformat_open_input第三个参数传入个AVInputFormat参数&#xff0c;这个参数用于指定输入设备的格式&…

SpringBoot+Vue实现前后端分离的企业人事管理系统

文末获取源码 开发语言&#xff1a;Java 使用框架&#xff1a;spring boot 前端技术&#xff1a;JavaScript、Vue.js 、css3 开发工具&#xff1a;IDEA/MyEclipse/Eclipse、Visual Studio Code 数据库&#xff1a;MySQL 5.7/8.0 数据库管理工具&#xff1a;phpstudy/Navicat JD…

Linux基础IO(上)

这里写目录标题基础IO预备小知识文件封装的特性系统接口与封装文件封装的跨平台性文件与进程的关系什么叫做文件&#xff1f;回顾C语言相关文件IO什么叫做当前路径&#xff1f;C语言输出函数与文件打开方式以"w"的方式打开文件以"a"的方式打开文件以"…

异常的分类、产生、传递和处理(JAVA基础十)

目录一、异常1.1 概念1.2 异常的必要性二、异常分类2.1 错误2.2 异常三、异常产生和传递3.1 异常产生3.2 异常传递(这个其实是在打印信息中显示)四、异常处理【重点】4.1 try...catch...4.2 try...catch...finally...4.3 多重catch4.4 try…finally...4.5 小结五、声明、抛出异…