一文让你彻底搞懂Mybatis之缓存机制

简介: 一文让你彻底搞懂Mybatis之缓存机制

编译软件:IntelliJ IDEA 2019.2.4 x64

操作系统:win10 x64 位 家庭版

Maven版本:apache-maven-3.6.3

Mybatis版本:3.5.6

一. 缓存是什么?

一说到缓存,我们可能都会想到Cashe,这里摘自百度百科对它的解释:它原本是指访问速度比一般随机存取存储器(RAM)快的一种高速存储器,通常它不像系统主存那样使用DRAM技术,而使用昂贵但较快速的SRAM技术。缓存的设置是所有现代计算机系统发挥高性能的重要因素之一。它的工作原理是当CPU要读取一个数据时,首先从CPU缓存中查找,找到就立即读取并送给CPU处理;没有找到,就从速率相对较慢的内存中读取并送给CPU处理,同时把这个数据所在的数据块调入缓存中,可以使得以后对整块数据的读取都从缓存中进行,不必再调用内存。

以上部分内容可能看不懂,没关系,你只需要知道正是由于这样的读取机制,使得CPU读取缓存的命中率非常高(大多数CPU可达90%左右),也就是说CPU下一次要读取的数据90%都在CPU缓存中,只有大约10%需要从内存读取。这样大大节省了CPU直接读取内存的时间,非常快捷!!!

简而言之,以cpu中的缓存举例,缓存其实就是数据交换的缓冲区【又称Cashe】,是存贮数据(使用频繁的数据)的临时地方。当用户查询数据,首先在缓存中寻找,如果找到了则直接执行。如果找不到,则去数据库中查找


二. 为什么要使用缓存?

举个生活中的例子,当我们在线观看视频时,以哔哩哔哩网站为例,你会发现,底下的进度条会实时显示蓝色,白色等两种颜色。不难发现,蓝色代表的是视频实际播放的进度,而白色代表的是视频实时预先缓存的进度。如下所示。

这种让用户一边下载一边观看、收听,而不要等整个文件下载到自己的计算机上才可以观看的网络传输技术,就是鼎鼎大名的流媒体技术。该技术的原理是先在使用者端的计算机上创建一个缓冲区,在播放前预先下一段数据作为缓冲,在网路实际连线速度小于播放所耗的速度时,播放程序就会取用一小段缓冲区内的数据,这样可以避免播放的中断,也使得播放品质得以保证。该技术在很多音频影视网站上被大量使用,旨在丰富用户的使用体验并提高音频的播放性能。

而程序中的缓存【Mybatis缓存】,亦是如此,Mybatis使用缓存优势可以提高查询效率,并降低服务器的压力。它的本质就是用利用空间换时间,牺牲数据的实时性,以服务器内存中的数据暂时代替从数据库读取最新的数据,减少数据库IO,减轻服务器压力,减少网络延迟,加快页面打开速度。


三. Mybatis中的缓存分哪几种?

👉分类

  1. 一级缓存
  2. 二级缓存
  3. 第三方缓存

3.1 Mybatis缓存机制之一级缓存

👉概述

一级缓存【本地缓存(Local Cache)或SqlSessiona级别缓存】

🤔什么是SqlSessiona?

SqlSession是一个会话,相当于JDBC中的一个Connection对象,是整个Mybatis运行的核心,它是MyBatis的关键对象,是执行持久化操作的独享,类似于JDBC中的Connection。它是应用程序与持久层之间执行交互操作的一个单线程对象,也是MyBatis的核心接口之一

👉特点

  • 一级缓存默认开启
  • 不能关闭
  • 可以清空

💡 :有点类似于使用腾讯视频网站去看电影,电影观看进度条前的那一小段灰色的进度条

👉缓存原理

  1. 第一次获取数据时,先从数据库中加载数据,将数据缓存至Mybatis一级缓存中【缓存底层实现原理Map,key:hashCode+查询的Sqlld+编写的sal查询语句+参数】
  2. 以后再次获取数据时,先从一级缓存中获取,如未获取到数据,再从数据库中获取数据

不信?请看如下测试代码的体现

代码示例如下:

@Test
public void test04(){
    try {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //通过SqlSessionFactory对象调用openSession();
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //获取EmployeeMapper的代理对象
        EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
        //第一次调用selectEmpByOneOpr(1);
        List<Employee> employees = employeeMapper.selectEmpByOneOpr(1);
        System.out.println(employees);
        System.out.println("---------------------------------------------");
        //第二次调用selectEmpByOneOpr(1);
        List<Employee> employees1 = employeeMapper.selectEmpByOneOpr(1);
        System.out.println(employees1);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

👉一级缓存的五种失效情况

  1. 不同的SqlSession对应不同的一级缓存
  2. 同一个SqlSession但是查间条件不同
  3. 同一个SqlSession两次查询期间执行了任何一次增删改操作
    执行任何一次的增删改操作会默认清空一级缓存
  4. 同一个SqlSession两次查询期间手动清空了缓存
    如何手动清空一级缓存?
sqlSession.clearCache()
  1. 同一个SqlSessioni两次查询期间提交了事务
sqlSession.commit()

3.2 Mybatis缓存机制之二级缓存

👉概述

二级缓存【全局作用域缓存】是SqlSessionFactory级别的缓存

👉特点

  • 二级缓存默认关闭,需要开启才能使用
  • 二级缓存需要提交sqlSession或关闭sqlSessionl时,才会缓存。

👉二级缓存使用的步骤

①全局配置文件中开启二级缓存“<setting name="cacheEnab1ed"value=“true”/>”

②需要使用二级缓存的映射文件处使用cache配置缓存

③注意:POJO(Java Bean【java的实体类】)需要实现Serializable接口

④关闭sqlSession或提交sqlSessionl时,将数据缓存到二级缓存

👉用法案例

演示二级缓存的效果

代码示例如下:

①全局配置文件中开启二级缓存

②这里假设映射文件EmployeeMapper.xml使用cache配置缓存

③在映射文件EmployeeMapper.xml对应的pojo类实现Serializable接口

④测试

@Test
public void test04(){
    try {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //通过SqlSessionFactory对象调用openSession()获取sqlSession对象;
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //通过sqlSession对象调用getMapper(EmployeeMapper.class)以获取EmployeeMapper的代理对象
        EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
        //第一次调用selectEmpByOneOpr(1);
        List<Employee> employees = employeeMapper.selectEmpByOneOpr(1);
        System.out.println(employees);
        //关闭sqlsession,目的是为了将数据加载到二级缓存中
        sqlSession.close();
        System.out.println("---------------------------------------------");
        //重新获取sqlsession对象
        SqlSession sqlSession1 =sqlSessionFactory.openSession();
        //使用sqlsession对象调用etMapper(EmployeeMapper.class)获取EmployeeMapper接口的代理对象mapper
        EmployeeMapper mapper = sqlSession1.getMapper(EmployeeMapper.class);
        //调用selectEmpByOneOpr(1);
        List<Employee> employees1 = mapper.selectEmpByOneOpr(1);
        System.out.println(employees1);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

👉底层原理

  1. 第一次获取数据时,先从数据库中获取数据,将数据缓存至一级缓存;当提交或关闭SqlSessionl时,将数据缓存至二级缓存
  2. 以后再次获取数据时,先从一级缓存中获取数据,如果一级缓存没有指定数据,再去二级缓存中获取数据。如果二级缓存也没有指定数据时,需要去数据库中获取数据

👉二级缓存相关属性(在设置了cash的映射文件中设置以下属性)

  • eviction=“FIFO"缓存清除【回收】策略
  • LU-最近最少使用的移除最长时间不被使用的对象
  • FFO-先进先出按对象进入缓存的顺序来移除它们
  • flushlnterval刷新间隔,单位毫秒,默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新
  • size引用数目,正整数,代表缓存最多可以存储多少个对象,太大容易导致内存益出
  • readOnly只读,true/false
  • true只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势
  • false读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false

👉二级缓存的失效情况

在两次查询之间,执行增删改操作,会同时清空一级缓存和二级缓存

sqlSession.clearCache()只是用来清除一级缓存

🤔思考

执行两次查询操作(查询条件相同且查询语句相同),中间使用sqlsession.clearCache(),然后关闭第一个sqlsession对象,又新建一个sqlsession对象,执行第二次查询,是否会导致二级缓存失效?

我的推测: 会失效

测试代码如下:

@Test
public void test05() {
    try {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //通过SqlSessionFactory对象调用openSession()获取sqlSession对象;
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //通过sqlSession对象调用getMapper(EmployeeMapper.class)以获取EmployeeMapper的代理对象
        EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
        //第一次调用selectEmpByOneOpr(1);
        List<Employee> employees = employeeMapper.selectEmpByOneOpr(1);
        System.out.println(employees);
        //清空一级缓存
        sqlSession.clearCache();
        //需要先关闭sqlsession对象,以缓存数据到二级缓存
        sqlSession.close();
        System.out.println("---------------------------------------------");
        //重新获取sqlsession对象
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        //使用sqlsession对象调用etMapper(EmployeeMapper.class)获取EmployeeMapper接口的代理对象mapper
        EmployeeMapper mapper = sqlSession1.getMapper(EmployeeMapper.class);
       /* //在两个查询之间执行一次更新操作,目的是为了清空二级缓存
        Employee e = new Employee();
        e.setId(1);
        e.setSalary(25000.0);
        mapper.updateEmp(e);*/
        //调用selectEmpByOneOpr(1);
        List<Employee> employees1 = mapper.selectEmpByOneOpr(1);
        System.out.println(employees1);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

推测错了,二级缓存仍然可以命中该查询结果

⭐原因分析

当第一次执行某个 SQL 语句时,该 SQL 语句的缓存条目会被添加到二级缓存中;而在清空当前 SqlSession 对象的一级缓存时,并没有清空对应的 SQL 语句在二级缓存中的缓存条目,因此该 SQL 语句的缓存条目仍然存在于二级缓存中,即使这个二级缓存还没有和数据库同步。

当新建另一个 SqlSession 对象,执行相同的查询操作且查询条件和前一次查询操作相同时,MyBatis 将会先从一级缓存中尝试获取数据。由于已经执行了 sqlsession.clearCache() 清空了当前 SqlSession 的一级缓存,因此一级缓存会命中失败,但是 MyBatis 可以从二级缓存中获取到之前查询过的结果集,返回给我当前的查询操作结果。

3.3 Mybatis中缓存机制之第三方缓存【以EhCache为例】

👉概述

EhCache【第三方缓存】是一个纯ava的进程内缓存框架

👉使用步骤

①导入jar包

代码示例如下:

<!-- 导入ehcache的jar包 -->
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.0.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<!-- 导入一个日志的jar包 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.6.2</version>
</dependency>

②编写ehcache的配置文件(resources目录下)【ehcache.xml】

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
    <!-- 磁盘保存路径 -->
    <diskStore path="E:\mybatis\ehcache" />
    <defaultCache
            maxElementsInMemory="512"
            maxElementsOnDisk="10000000"
            eternal="false"
            overflowToDisk="true"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU">
    </defaultCache>
</ehcache>

③加载第三方缓存【映射文件】

<!--  此处加载第三方缓存ehcache  -->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>

④开始使用

@Test
public void test04(){
    try {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //通过SqlSessionFactory对象调用openSession()获取sqlSession对象;
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //通过sqlSession对象调用getMapper(EmployeeMapper.class)以获取EmployeeMapper的代理对象
        EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
        //第一次调用selectEmpByOneOpr(1);
        List<Employee> employees = employeeMapper.selectEmpByOneOpr(1);
        System.out.println(employees);
        //关闭sqlsession,目的是为了将数据加载到二级缓存中
        sqlSession.close();
        System.out.println("---------------------------------------------");
        //重新获取sqlsession对象
        SqlSession sqlSession1 =sqlSessionFactory.openSession();
        //使用sqlsession对象调用etMapper(EmployeeMapper.class)获取EmployeeMapper接口的代理对象mapper
        EmployeeMapper mapper = sqlSession1.getMapper(EmployeeMapper.class);
        //调用selectEmpByOneOpr(1);
        List<Employee> employees1 = mapper.selectEmpByOneOpr(1);
        System.out.println(employees1);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

❗注意

①第三方缓存,需要建立在二级缓存基础上【需要开启二级缓存,第三方缓存才能生效】

②如何让第三方缓存失效?

将二级缓存设置失效即可【在两次查询之间,进行一次增删改操作以清除二级缓存】

代码示例如下:

@Test
public void test04(){
    try {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //通过SqlSessionFactory对象调用openSession()获取sqlSession对象;
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //通过sqlSession对象调用getMapper(EmployeeMapper.class)以获取EmployeeMapper的代理对象
        EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
        //第一次调用selectEmpByOneOpr(1);
        List<Employee> employees = employeeMapper.selectEmpByOneOpr(1);
        System.out.println(employees);
        //关闭sqlsession,目的是为了将数据加载到二级缓存中
        sqlSession.close();
        System.out.println("---------------------------------------------");
        //重新获取sqlsession对象
        SqlSession sqlSession1 =sqlSessionFactory.openSession();
        //使用sqlsession对象调用etMapper(EmployeeMapper.class)获取EmployeeMapper接口的代理对象mapper
        EmployeeMapper mapper = sqlSession1.getMapper(EmployeeMapper.class);
        //在两个查询操作之间执行一次更新操作,目的是为了清空二级缓存
        Employee e=new Employee();
        e.setId(1);
        e.setSalary(25000.0);
        mapper.updateEmp(e);
        //调用selectEmpByOneOpr(1);
        List<Employee> employees1 = mapper.selectEmpByOneOpr(1);
        System.out.println(employees1);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

相关文章
|
2月前
|
存储 缓存 NoSQL
mybatisplus一二级缓存
MyBatis-Plus 继承并优化了 MyBatis 的一级与二级缓存机制。一级缓存默认开启,作用于 SqlSession,适用于单次会话内的重复查询;二级缓存需手动开启,跨 SqlSession 共享,适合提升多用户并发性能。支持集成 Redis 等外部存储,增强缓存能力。
|
5月前
|
缓存 并行计算 PyTorch
PyTorch CUDA内存管理优化:深度理解GPU资源分配与缓存机制
本文深入探讨了PyTorch中GPU内存管理的核心机制,特别是CUDA缓存分配器的作用与优化策略。文章分析了常见的“CUDA out of memory”问题及其成因,并通过实际案例(如Llama 1B模型训练)展示了内存分配模式。PyTorch的缓存分配器通过内存池化、延迟释放和碎片化优化等技术,显著提升了内存使用效率,减少了系统调用开销。此外,文章还介绍了高级优化方法,包括混合精度训练、梯度检查点技术及自定义内存分配器配置。这些策略有助于开发者在有限硬件资源下实现更高性能的深度学习模型训练与推理。
994 0
|
4月前
|
缓存 Java 数据库连接
Mybatis一级缓存详解
Mybatis一级缓存为开发者提供跨数据库操作的一致性保证,有效减轻数据库负担,提高系统性能。在使用过程中,需要结合实际业务场景选择性地启用一级缓存,以充分发挥其优势。同时,开发者需注意其局限性,并做好事务和并发控制,以确保系统的稳定性和数据的一致性。
147 20
|
6月前
|
缓存 Java 数据库连接
Mybatis一级缓存、二级缓存详讲
本文介绍了MyBatis中的查询缓存机制,包括一级缓存和二级缓存。一级缓存基于同一个SqlSession对象,重复查询相同数据时可直接从缓存中获取,减少数据库访问。执行`commit`操作会清空SqlSession缓存。二级缓存作用于同一namespace下的Mapper对象,支持数据共享,需手动开启并实现序列化接口。二级缓存通过将数据存储到硬盘文件中实现持久化,为优化性能,通常在关闭Session时批量写入缓存。文章还说明了缓存的使用场景及注意事项。
205 7
Mybatis一级缓存、二级缓存详讲
|
7月前
|
缓存 Java 数据库连接
十、MyBatis的缓存
十、MyBatis的缓存
130 6
|
7月前
|
存储 缓存 分布式计算
【赵渝强老师】Spark RDD的缓存机制
Spark RDD通过`persist`或`cache`方法可将计算结果缓存,但并非立即生效,而是在触发action时才缓存到内存中供重用。`cache`方法实际调用了`persist(StorageLevel.MEMORY_ONLY)`。RDD缓存可能因内存不足被删除,建议结合检查点机制保证容错。示例中,读取大文件并多次调用`count`,使用缓存后执行效率显著提升,最后一次计算仅耗时98ms。
158 0
【赵渝强老师】Spark RDD的缓存机制
|
6月前
|
SQL Java 数据库连接
MyBatis 实现分页的机制
MyBatis 的分页机制主要依赖于 `RowBounds` 对象和分页插件。`RowBounds` 实现内存分页,适合小数据量场景,通过设定偏移量和限制条数对结果集进行筛选。而针对大数据量,则推荐使用分页插件(如 PageHelper),实现物理分页。插件通过拦截 SQL 执行,动态修改语句添加分页逻辑,支持多种数据库方言。配置插件后,无需手动调整查询方法即可完成分页操作,提升性能与灵活性。
140 0
|
8月前
|
缓存 NoSQL Java
Mybatis学习:Mybatis缓存配置
MyBatis缓存配置包括一级缓存(事务级)、二级缓存(应用级)和三级缓存(如Redis,跨JVM)。一级缓存自动启用,二级缓存需在`mybatis-config.xml`中开启并配置映射文件或注解。集成Redis缓存时,需添加依赖、配置Redis参数并在映射文件中指定缓存类型。适用于查询为主的场景,减少增删改操作,适合单表操作且表间关联较少的业务。
160 6
|
10月前
|
SQL Java 数据库连接
Mybatis架构原理和机制,图文详解版,超详细!
MyBatis 是 Java 生态中非常著名的一款 ORM 框架,在一线互联网大厂中应用广泛,Mybatis已经成为了一个必会框架。本文详细解析了MyBatis的架构原理与机制,帮助读者全面提升对MyBatis的理解和应用能力。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
Mybatis架构原理和机制,图文详解版,超详细!
|
10月前
|
SQL 缓存 Java
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
本文详细介绍了MyBatis的各种常见用法MyBatis多级缓存、逆向工程、分页插件 包括获取参数值和结果的各种情况、自定义映射resultMap、动态SQL
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件