一文让你彻底搞懂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月前
|
缓存 Java 数据库连接
mybatis复习05,mybatis的缓存机制(一级缓存和二级缓存及第三方缓存)
文章介绍了MyBatis的缓存机制,包括一级缓存和二级缓存的配置和使用,以及如何整合第三方缓存EHCache。详细解释了一级缓存的生命周期、二级缓存的开启条件和配置属性,以及如何通过ehcache.xml配置文件和logback.xml日志配置文件来实现EHCache的整合。
mybatis复习05,mybatis的缓存机制(一级缓存和二级缓存及第三方缓存)
|
3月前
|
缓存 应用服务中间件 nginx
Web服务器的缓存机制与内容分发网络(CDN)
【8月更文第28天】随着互联网应用的发展,用户对网站响应速度的要求越来越高。为了提升用户体验,Web服务器通常会采用多种技术手段来优化页面加载速度,其中最重要的两种技术就是缓存机制和内容分发网络(CDN)。本文将深入探讨这两种技术的工作原理及其实现方法,并通过具体的代码示例加以说明。
330 1
|
3天前
|
SQL Java 数据库连接
Mybatis架构原理和机制,图文详解版,超详细!
MyBatis 是 Java 生态中非常著名的一款 ORM 框架,在一线互联网大厂中应用广泛,Mybatis已经成为了一个必会框架。本文详细解析了MyBatis的架构原理与机制,帮助读者全面提升对MyBatis的理解和应用能力。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
Mybatis架构原理和机制,图文详解版,超详细!
|
12天前
|
SQL 缓存 Java
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
本文详细介绍了MyBatis的各种常见用法MyBatis多级缓存、逆向工程、分页插件 包括获取参数值和结果的各种情况、自定义映射resultMap、动态SQL
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
|
13天前
|
SQL 缓存 Java
MyBatis如何关闭一级缓存(分注解和xml两种方式)
MyBatis如何关闭一级缓存(分注解和xml两种方式)
41 5
|
28天前
|
缓存 Java 数据库连接
使用MyBatis缓存的简单案例
MyBatis 是一种流行的持久层框架,支持自定义 SQL 执行、映射及复杂查询。本文介绍了如何在 Spring Boot 项目中集成 MyBatis 并实现一级和二级缓存,以提高查询性能,减少数据库访问。通过具体的电商系统案例,详细讲解了项目搭建、缓存配置、实体类创建、Mapper 编写、Service 层实现及缓存测试等步骤。
|
1月前
|
存储 缓存 负载均衡
Nginx代理缓存机制
【10月更文挑战第2天】
65 4
|
1月前
|
存储 缓存 NoSQL
深入理解后端缓存机制的重要性与实践
本文将探讨在后端开发中缓存机制的应用及其重要性。缓存,作为提高系统性能和用户体验的关键技术,对于后端开发来说至关重要。通过减少数据库访问次数和缩短响应时间,缓存可以显著提升应用程序的性能。本文将从缓存的基本概念入手,介绍常见的缓存策略和实现方式,并通过实例展示如何在后端开发中有效应用缓存技术。最后,我们将讨论缓存带来的一些挑战及其解决方案,帮助您在实际项目中更好地利用缓存机制。
|
2月前
|
存储 缓存 Android开发
Android RecyclerView 缓存机制深度解析与面试题
本文首发于公众号“AntDream”,详细解析了 `RecyclerView` 的缓存机制,包括多级缓存的原理与流程,并提供了常见面试题及答案。通过本文,你将深入了解 `RecyclerView` 的高性能秘诀,提升列表和网格的开发技能。
66 8