MyBatis缓存

MyBatis的缓存

1、缓存概述

  • 什么是缓存

    缓存:存储在计算机上的一个原始数据复制集,就是数据交换的缓冲区(称作Cache),是存贮数据(使用频繁的数据)的临时地方。当用户查询数据,首先在缓存中寻找,如果找到了则直接执行。如果找不到,则去数据库中查找。

  • 缓存有什么用

    缓存的本质就是用空间换时间,牺牲数据的实时性,以服务器内存中的数据暂时代替从数据库读取最新的数据,减少数据库IO,减轻服务器压力,减少网络延迟,加快页面打开速度

  • 缓存的分类

    • 文件缓存:文件缓存是把一些需要高速存取的变量缓存在内存中。模板引擎用的就是文件缓存机制,把动态代码编译成静态文件放入硬盘,不用每次访问都编译,直接读出即可。
    • 浏览器缓存:浏览器缓存根据一套与服务器约定的规则进行工作,在同一个会话过程中会检查一次并确定缓存的副本足够新。如果在浏览过程中前进或后退时访问到同一个图片,这些图片可以从浏览器缓存中调出而即时显示。
    • 数据库缓存:常用的缓存方案有memcached、redis等。把经常需要从数据库查询的数据、或经常更新的数据放入到缓存中,这样下次查询时,直接从缓存直接返回,减轻数据库压力,提升数据库性能。
    • Web应用层缓存:应用层缓存指的是从代码层面上,通过代码逻辑和缓存策略,实现对数据、页面、图片等资源的缓存,可以根据实际情况选择将数据存在文件系统或者内存中,减少数据库查询或者读写瓶颈,提高响应效率。
    • 服务器缓存:包括代理服务器缓存CDN缓存
      • 代理服务器缓存:代理服务器是浏览器和源服务器之间的中间服务器,浏览器先向这个中间服务器发起Web请求,经过处理后(比如权限验证,缓存匹配等),再将请求转发到源服务器。
      • CDN缓存:也叫网关缓存、反向代理缓存。CDN缓存一般是由网站管理员自己部署,为了让他们的网站更容易扩展并获得更好的性能。

MyBatis的缓存属于Web应用层缓存,在MyBatis中缓存被分为两大类:系统缓存自定义缓存

  • 系统缓存:是MyBatis中自带的缓存,这些缓存都是MyBatis帮我们设计好了的,直接就能用,主要包含一级缓存二级缓存
  • 自定义缓存:顾名思义,就是自己定义的缓存,需要自己手动去设计,当然一般而言我们并不会真正的区设计一个缓存,而是直接使用第三方设计的缓存,比如:Redis、Enhance等

2、MyBatis的一级缓存

一级缓存:一级缓存是默认开启的,它的范围是SqlSession级别的,当我们用SqlSession来查询数据的时候,如果下一次再使用相同的SqlSession进行查询的时候,就会直接从缓存中取数据,如果没有才从数据库中取数据,缓存只针对查询功能有效

2.1 一级缓存的使用

示例

import com.hhxy.mapper.CacheMapper;
import com.hhxy.pojo.Emp;
import com.hhxy.utils.SqlSessionFactoryUtil;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.Test;

/**
 * @author ghp
 * @date 2022/9/9
 */
public class CacheMapperTest {
    @Test
    public void cacheMapperTest(){
        //1、获取SqlSessionFactory对象
        SqlSessionFactory sqlSF = SqlSessionFactoryUtil.getSqlSF();
        //2、获取SqlSession对象
        SqlSession sqlS1 = sqlSF.openSession();
        SqlSession sqlS2 = sqlSF.openSession();
        //3、获取Mapper接口对象
        CacheMapper mapper1 = sqlS1.getMapper(CacheMapper.class);
        CacheMapper mapper2 = sqlS2.getMapper(CacheMapper.class);
        //4、执行SQL
        int empId = 20;
        /*使用SqlSession1执行SQL*/
        Emp emp1 = mapper1.getEmpById(empId);
        System.out.println("emp1 = " + emp1);
        Emp emp2 = mapper1.getEmpById(empId);
        System.out.println("emp2 = " + emp2);

        /*使用SqlSession2执行SQL*/
        Emp emp3 = mapper2.getEmpById(empId);
        System.out.println("emp3 = " + emp3);
        //5、释放资源
    }
}

image-20220909203903356

2.2 一级缓存的失效

使一级缓存失效的四种情况

  • 不同的SqlSession对应不同的一级缓存

    这是由一级缓存的级别决定的,详情见上面的示例,不在赘述

  • 同一个SqlSession但是查询条件不同

    这个也很好理解,不同条件查询得到的数据是不一致的

  • 同一个SqlSession两次查询期间执行了任何一次增删改操作

    因为增、删、改会清空一级缓存,原因:缓存中的数据毕竟不是数据库中的即时数据,在执行增删改操作后,可能会影响到我们上一次刚查询的数据(此时缓存中的数据就已经过时了,比如:进行第一次查询,数据进入缓存,但进行一个删除操作后将这条进入缓存的数据从数据库中删除了,第二次查询这条数据已经不存在了,直接使用缓存中的数据显然的错误的),所以MyBatis干脆就规定了增删改后,直接清空一级缓存,这样就能保证二次查询的正确性

    image-20220909205503862

  • 同一个SqlSession两次查询期间手动清空了缓存

    //清空一级缓存的代码
    sqlSession.clearCache();
    

3、MyBatis的二级缓存

二级缓存:二级缓存需要手动开启,它的范围是SqlSessionFactory级别的,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取

3.1 二级缓存的开启

  • 二级缓存的开启条件

    • 在核心配置文件中,设置全局配置属性cacheEnabled="true",默认为true,不需要设置

      <setting name="cacheEnabled" value="true"/>
      
    • 在映射文件中设置标签 <cache/>

      image-20220909212024565

    • 二级缓存必须在SqlSession关闭或提交之后有效,不进行关闭或提交,则查询的数据会自动进入一级缓存

    • 查询的数据所转换的实体类类型必须实现序列化的接口,否则报java.io.NotSerializableException: com.hhxy.pojo.Emp异常

      image-20220909213102173

示例

二级缓存测试代码:

import com.hhxy.mapper.CacheMapper;
import com.hhxy.pojo.Emp;
import com.hhxy.utils.SqlSessionFactoryUtil;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.Test;

/**
 * @author ghp
 * @date 2022/9/9
 */
public class CacheMapperTest {
    @Test
    public void cacheMapperTest(){
        //1、获取SqlSessionFactory对象
        SqlSessionFactory sqlSF = SqlSessionFactoryUtil.getSqlSF();
        //2、获取SqlSession对象
        SqlSession sqlS1 = sqlSF.openSession();
        SqlSession sqlS2 = sqlSF.openSession();
        //3、获取Mapper接口对象
        CacheMapper mapper1 = sqlS1.getMapper(CacheMapper.class);
        CacheMapper mapper2 = sqlS2.getMapper(CacheMapper.class);
        //4、执行SQL
        int empId = 20;
        /*使用SqlSession1执行SQL*/
        Emp emp1 = mapper1.getEmpById(empId);
        System.out.println("emp1 = " + emp1);

        sqlS1.close();//如果去掉这句话,就会执行两遍SQL

        /*使用SqlSession2执行SQL*/
        Emp emp2 = mapper2.getEmpById(empId);
        System.out.println("emp3 = " + emp2);

        //5、释放资源
    }
}

image-20220909215127924

备注:只有二级缓存才会输出缓存命中率

3.2 二级缓存的失效

  • 使二级缓存失效的情况: 两次查询之间执行了任意的增、删、改,会使一级和二级缓存同时失效

    因为增、删、改清除了二级缓存,当第二次进行查询时,先到缓存区中寻找是否具有所需数据,结果由于缓存区被清空了,所以重新执行SQL语句到数据库中查询并获取数据

    image-20220909220655790

3.2 二级缓存相关配置

  • 在mapper配置文件中添加的cache标签可以设置一些属性:

    • eviction属性:缓存回收策略,默认的是 LRU
      • LRU(Least Recently Used):最近最少使用的,移除最长时间不被使用的对象
      • FIFO(First in First out):先进先出,按对象进入缓存的顺序来移除它们
      • SOFT :软引用1,内存不足时回收,JVM会根据当前堆的使用情况来判断何时回收(一般当缓存溢出时,就会回收软引用的对象)
      • WEAKReference:弱引用,GC触发就回收,JVM一旦发现使用弱引用规则指向的对象就会直接回收
      • StrongReference:强引用,不会被回收,JVM宁愿抛出异常也不会回收使用强引用规则指向的对象(这是Java中最普遍的引用)
      • PhantomReference:虚引用,虚引用主要用来跟踪对象的回收,并不影响对象的生命周期,使用虚引用规则指向的对象可能随时被回收

    推荐阅读:

  • flushInterval属性:刷新间隔,单位为毫秒。默认是不设置(也就是没有刷新间隔),只有缓存被调用时进行刷新

  • size属性:引用数目,为正整数。代表缓存最多可以存储多少个对象(太大容易导致内存溢出,太小缓存没什么意义,所以一般使用默认的)

  • readOnly属性:只读,取值为boolean类型,默认是false

    • true:只读缓存,会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。

      因为返回缓存对象的相同实例,所以才会将他设置成只读,因为返回缓存对象的相同实例时,如果调用者对这个对象进行修改,会导致直接修改掉缓存中的对象,存在安全隐患(只读就能解决这种安全隐患);性能优势是相较于取值时false的状态而言的,

      因为读写缓存,是返回一个缓存对象的拷贝(准确来说是深拷贝),拷贝是需要耗费时间、以及资源的

    • false:读写缓存,会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是 false。

      这种方式能进行读写缓存的原因,是因为返回的对象是缓存对象的拷贝,当我们对缓存对象进行修改时,由于是深拷贝,返回的缓存对象的修改并不会影响到缓存中的对象,所以更安全,但是相较于只读缓存性能更低

4、系统缓存的查询顺序

二级缓存 → 一级缓存 二级缓存 \to 一级缓存 二级缓存一级缓存

  • 先查询二级缓存

    原因:二级缓存的范围更大,二级缓存是SqlSessionFactory级别的,而一级缓存是SqlSession级别的,二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用

  • 如果二级缓存没有命中,再查询一级缓存

    原因:一级缓存有二级缓存中没有的数据,二级缓存虽然范围比一级缓存大,但并不包含所有的一级缓存,二级缓存必须使用close()方法后才能将数据读取到二级缓存中,有一些没有使用close()方法的查询结果只在一级缓存中

  • 如果一级缓存也没有命中,则查询数据库 SqlSession关闭之后,一级缓存中的数据会写入二级缓

5、EHCache的使用

img

5.1 EHCache基本介绍

  • EHCache是什么?

    EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate2中默认的CacheProvider3

    Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。它具有内存和磁盘存储,缓存加载器,缓存扩展,缓存异常处理程序,一个gzip缓存servlet过滤器,支持REST和SOAP api等特点。

    EHCache官网:EHCache

    Hibernate介绍:Hibernate 中文文档 | Hibernate 中文网

  • EHCache的作用

    EHCache是SqlSessionFactory级别,提供二级缓存的功能,相较MyBatis自带的二级缓存具有更加优秀的性能。

  • EHCache的特点

    • 使用起来快速、简单、灵活。因为是框架嘛,当然具有框架的一般特点,使用简单,灵活性是EHCache具有多种缓存策略,同时缓存数据有两级:内存和磁盘,因此无需担心容量问题
    • 健壮性、扩展性。
    • 分布式。可以通过RMI、可插入API等方式进行分布式缓存
    • 支持多缓存管理器实例,以及一个实例的多个缓存区域

    ……

PS:这些所谓的特点只需要了解即可,重点还是会使用,当你能够熟练使用多个缓存时,你就能自然而然地明白它的特点了

5.2 EHCache的基本使用

创建Maven工程
导入依赖
编写ehcache配置文件
编写logback配置文件
编写Java代码
编写MyBatis配置文件
  • Step1:创建Maven工程

    目录结构:

    image-20220911154339002

  • Step2:导入依赖

    EHCache所需依赖:

            <!--mysql驱动-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.16</version>
            </dependency>
            <!--mybatis-->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.5.10</version>
            </dependency>
    		<!-- Mybatis EHCache整合包 -->
            <dependency>
                <groupId>org.mybatis.caches</groupId>
                <artifactId>mybatis-ehcache</artifactId>
                <version>1.2.2</version>
            </dependency>
            <!-- slf4j日志门面的一个具体实现 -->
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-classic</artifactId>
                <version>1.2.3</version>
            </dependency>
    

    备注:日志门面就是日志提供的接口(这里说的就是slf4j-api),即要使用EHCache必须先实现slf4j-api(slf4j日志门面),但是EHCache却没有实现slf4j-api,所以需要我们导入第三方slf4j-api的实现,也就是logback-classic

    这个可以在Maven中看到,EHCache中是有slf4j的:

    image-20220911145134010

    推荐阅读:浅谈日志门面

  • Step3:编写MyBatis配置文件

    略……

    推荐阅读:快速上手MyBatis(该文通俗易懂,步骤说明详细,是很适合快速上手MyBatis的不二之选)

  • Step4:编写ehcache配置文件

    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="D:\Test\ehcache"/>
        <defaultCache
                maxElementsInMemory="1000"
                maxElementsOnDisk="10000000"
                eternal="false"
                overflowToDisk="true"
                timeToIdleSeconds="120"
                timeToLiveSeconds="120"
                diskExpiryThreadIntervalSeconds="120"
                memoryStoreEvictionPolicy="LRU">
        </defaultCache>
    </ehcache>
    
  • Step5:编写logback配置文件

    logback.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration debug="true">
        <!-- 指定日志输出的位置 -->
        <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <!-- 日志输出的格式 -->
                <!-- 按照顺序分别是: 时间、日志级别、线程名称、打印日志的类、日志主体内容、换行
                -->
                <pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger]
                    [%msg]%n</pattern>
            </encoder>
        </appender>
        <!-- 设置全局日志级别。日志级别按顺序分别是: DEBUG、INFO、WARN、ERROR -->
        <!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 -->
        <root level="DEBUG">
            <!-- 指定打印日志的appender,这里通过“STDOUT”引用了前面配置的appender -->
            <appender-ref ref="STDOUT" />
        </root>
        <!-- 根据特殊需求指定局部日志级别 -->
        <logger name="com.atguigu.crowd.mapper" level="DEBUG"/>
    </configuration>
    
  • Step6:编写Java代码

    这里可以直接使用前面测试二级缓存的代码,因为他们都是属于二级缓存,所以我们就直接看测试结果吧(●’◡’●)

    1)不在中间使用删除方法

    image-20220911153951503

    2)在中间使用删除方法:

    image-20220911154043561

    image-20220911154057967

    image-20220911154103063

5.3 EHCache配置文件参数详解

参数名作用
maxElementsInMemory设置在内存中缓存的element的最大数目
maxElementsOnDisk设置在磁盘上缓存的element的最大数目,其中0表示无穷大
etenrnal设定缓存的elements是否永远不过期。 如果为 true,则缓存的数据始终有效, 如果为false那么还 要根据timeToIdleSeconds、timeToLiveSeconds 判断
overflowToDisk设定当内存缓存溢出的时候是否将过期的element 缓存到磁盘上
timeToIdleSeconds当缓存在EhCache中的数据前后两次访问的时间超 过timeToIdleSeconds的属性取值时, 这些数据便 会删除,默认值是0,也就是可闲置时间无穷大
timeToLiveSeconds缓存element的有效生命期,默认是0.,也就是 element存活时间无穷大
diskSpoolBufferDiskStore(磁盘缓存)的缓存区大小。默认是 30MB。每个Cache都应该有自己的一个缓冲区
diskPersistend在VM重启的时候是否启用磁盘保存EhCache中的数 据,默认是false
diskExpiryThreadIntervalSeconds磁盘缓存的清理线程运行间隔,默认是120秒。每 个120s, 相应的线程会进行一次EhCache中数据的 清理工作
memoryStoreEvictionPolicy当内存缓存达到最大,有新的element加入的时 候, 移除缓存中element的策略。 默认是LRU (最 近最少使用),可选的有LFU (最不常使用)和 FIFO (先进先出)

备注颜色加深的是必须参数

推荐阅读:页面置换算法相关概念和计算:含LRU、LFU和FIFO三个算法的详解

分割图片 的图像结果


参考文章


  1. Java中的引用一般是指变量对对象的引用,栈中的变量引用堆中的对象,让变量拥有对象的属性和行为,这个变量和对象的这种关系叫做引用 ↩︎

  2. Hibernate和MyBatis一样都是ORM框架,同属于持久层(也叫数据访问层)的框架 ↩︎

  3. CacheProvider(缓存提供者)是Java缓存中的一个提供缓存技术的角色,主要用来提供缓存功能 ↩︎

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

知识汲取者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值