MyBatis缓存机制要点解析以及如何与三方缓存组件Redis整合示例


我们知道,在所有的数据管理的中,查询是最多的操作,如果有一些大量的重复的操作(比如用户总是查询同一条数据)。是否可以把上次查到的内容,放到缓存中,而不必去数据库中建立查询数据,造成资源上的浪费, MyBatis就为我们实现了缓存功能。 Mybatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制,缓存可以极大的提升查询效率。
在这里插入图片描述

MyBatis中默认定义了两级缓存,分别是一级缓存和二级缓存。一级缓存和二级缓存级别不一样,一级缓存默认开启。缓存只对查询功能有效

  • 默认情况下,只有一级缓存(SqlSession级别的缓存,也称为本地缓存(Local Cache))开启
  • 二级缓存需要手动开启和配置,二级缓存是基于Mapper(namespace)级别的缓存
  • 为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存。
    在这里插入图片描述

一、MyBatis的一级缓存

一级缓存是SqlSession级别的,通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据,就 会从缓存中直接获取,不会从数据库重新访问。但是,如何界定哪些数据会从缓存中取用呢?
在这里插入图片描述
对于缓存失效的四种情况:
使一级缓存失效的四种情况:

  1. 不同的SqlSession对应不同的一级缓存
  2. 同一个SqlSession但是查询条件不同
  3. 同一个SqlSession两次查询期间执行了任何一次增删改操作
  4. 同一个SqlSession两次查询期间手动清空了缓存
    y

1、每个SqlSession都有自己的一级缓存

先表明现象最后推出本质,这里我定义的CacheMapper接口只是简单的根据员工的id查询出员工。这里可以自己定义,如果对如何定义MyBatis不是很熟的可以看博客从0到1搭建MyBatis实例思路剖析,跟着做完就能入门MyBatis

    @Test
    public void testCache() throws IOException {
        SqlSession sqlSession = getSqlSession();
        CacheMapper mapper = sqlSession.getMapper(CacheMapper.class);
        Emp emp1 = mapper.getEmpById(1);
        System.out.println(emp1);
        System.out.println("========第二次调用========从缓存中取数据");
        Emp emp2 = mapper.getEmpById(1);
        System.out.println(emp2);
        System.out.println("========即使用的不是同一个Mapper,也同样从缓存中取(同一个sqlsession)========");
        CacheMapper emp3 = sqlSession.getMapper(CacheMapper.class);
        System.out.println(emp3);
        System.out.println("\n========一级缓存的范围在sqlsession中,换一个新的sqlsession就会再次用sql读取数据========");
        SqlSession sqlSession1 = getSqlSession();
        CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class);
        Emp emp4 = mapper1.getEmpById(1);
        System.out.println(emp4);
    }

对结果进行分析
在这里插入图片描述

2、同一个SqlSession但是查询条件不同

给定两个不同的查询条件

 @Test
    public void testCache2() throws IOException {
        SqlSession sqlSession = getSqlSession();
        CacheMapper mapper = sqlSession.getMapper(CacheMapper.class);
        System.out.println("第一次获取数据,查询条件: eid = 1");
        Emp emp1 = mapper.getEmpById(1);
        System.out.println(emp1);
        System.out.println("第二次获取数据,查询条件:eid = 100");
        Emp emp2 = mapper.getEmpById(100);
        System.out.println(emp2);
    }

在这里插入图片描述
多提一嘴:对于所有的缓存结构,基本上可以猜到是键值对的形式。而通过这次分析,可以知道,对于键值对形式的缓存结构,key一定是和查询条件有关的。

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

对于缓存,我们知道最大的弊端之一可能就是缓存一致性的问题。如何保证用户从缓存查询到的数据一定是和数据库保持一致的,而不是保存老版的数据。其中一个操作就是,如果有增删改的操作就直接删缓存内容。
如果在MyBatis中进行了增删改,会将清空当前SqlSession会话下的LocalCache,这种比较极端,实际上最好的解决方案貌似是修改或者删除了哪条就删除缓存中的哪条。

   @Test
    public void testCache3() throws IOException {
        SqlSession sqlSession = getSqlSession();
        CacheMapper mapper = sqlSession.getMapper(CacheMapper.class);
        System.out.println("第一次获取数据,查询条件: eid = 1");
        Emp emp1 = mapper.getEmpById(1);
        System.out.println(emp1);
        Emp emp2 = mapper.getEmpById(1);
        System.out.println(emp2);
        System.out.println("\n=====进行增删改操作=====");
        mapper.updateNameByEid(100, "zhangsan");
        System.out.println("\n=====同一个sqlsession,再获取数据=====");
        Emp emp3 = mapper.getEmpById(1);
        System.out.println(emp3);
    }

最后的结果:
在这里插入图片描述

4、同一个SqlSession自己手动清空一级缓存

 @Test
    public void testCache4() throws IOException {
        SqlSession sqlSession = getSqlSession();
        CacheMapper mapper = sqlSession.getMapper(CacheMapper.class);
        System.out.println("第一次获取数据,查询条件: eid = 1");
        Emp emp1 = mapper.getEmpById(1);
        System.out.println(emp1);
        System.out.println("\n=====两次查询期间手动清空缓存=====");
        sqlSession.clearCache();
        System.out.println("第一次获取数据,查询条件: eid = 1");
        Emp emp2 = mapper.getEmpById(1);
        System.out.println(emp2);
    }

在这里插入图片描述

二、MyBatis的二级缓存

对于一级缓存,通过上述分析知道是属于SqlSessionFactory级别的。而二级缓存是SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取。
对于二级缓存MyBatis不是默认开启的,需要我们手动进行开启。
二级缓存开启的条件

  • 在核心配置文件中,设置全局配置属性cacheEnabled=“true",默认为true,不需要设置
  • 在映射文件中设置标签<cache />
  • 二级缓存必须在SqlSession关闭或提交之后有效
  • 查询的数据所转换的实体类类型必须实现序列化的接口

使二级缓存失效的情况: 两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效。需要注意的是,如果没有提交sqlsession时,数据会保存在一级缓存中,提交后,会将一级缓存的内容刷到二级缓存中。
二级缓存执行图:
在这里插入图片描述
在映射文件中设置标签<cache />,比如说对于CacheMapper.xml开启。
在这里插入图片描述
最后直接开始测试,在测试之前需要注意的是:在你需要返回的实体类上实现接口implements Serializable
测试代码:

   @Test
    public void testCache5() throws IOException {
        // 二级缓存是基于sqlsessionfactory的
        // 加载核心配置文件
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        // 获取sqlsessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
        System.out.println("第一个sqlsession的操作");
        SqlSession sqlSession1 = sqlSessionFactory.openSession(false);
        System.out.println(sqlSession1.getMapper(CacheMapper.class).getEmpById(1));
        // 需要关闭/提交会话,才能将一级缓存刷到二级
        sqlSession1.commit(); // 提交,将一级缓存刷到二级缓存中
        System.out.println("=====二级缓存未打开,没从缓存中获取数据=====");
        SqlSession sqlSession2 = sqlSessionFactory.openSession(false);
        System.out.println(sqlSession2.getMapper(CacheMapper.class).getEmpById(1));
    }

在这里插入图片描述
开启二级缓存;
在这里插入图片描述

1、二级缓存的相关配置

其实对于xml中的cache标签还可以加很多属性

<!-- 声明这个namespace 使用二级缓存-->
<cache type="org.apache.ibatis.cache.impl.PerpetualCache"
	size="1024" <!—-最多缓存对象个数,默认1024-->
	eviction="LRU" <!—回收策略-->
	flushInterval="120000" <!—自动刷新时间ms,未配置时只有调用时刷新-->
	readOnly="false"/> <!—默认是false(安全),改为true 可读写时,对象必	须支持序列
化-->

cache属性详解:
在这里插入图片描述

三、一级\二级缓存总结

如果是开启了二级缓存,在MyBatis中总的查询流程如下所示:
在这里插入图片描述

  • 先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。
  • 如果二级缓存没有命中,再查询一级缓存
  • 如果一级缓存也没有命中,则查询数据库
  • SqlSession关闭之后,一级缓存中的数据会写入二级缓存。

有关于二级缓存和一级缓存的架构位置,是先走的是CachingExecutor查看是否有二级缓存信息,之后在走一级缓存。
在这里插入图片描述

四、整合第三方缓存组件Redis

对于第三方组件的使用,前提条件就是开启了二级缓存。这个操作无非就是将原来的二级缓存的数据移植到了第三方组件中。至于为什么,其中一个就是mybatis自带的二级缓存,这个缓存是单服务器工作,无法实现分布式缓存。
mybatis提供了一个cache接口,如果要实现自己的缓存逻辑,实现cache接口开发即可。mybatis本身默认实现了一个,但是这个缓存的实现无法实现分布式缓存,所以我们要自己来实现。
首先,需要导入jar

 <!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-redis -->
    <dependency>
      <groupId>org.mybatis.caches</groupId>
      <artifactId>mybatis-redis</artifactId>
      <version>1.0.0-beta2</version>
    </dependency>

修改mapper配置文件中的type

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.fckey.mybatis.mapper.CacheMapper">
    <!--    开启二级缓存-->
    <!-- 声明这个namespace 使用二级缓存-->
    <!-- 开启二级缓存, 需要修改为 -->
    <cache type="org.mybatis.caches.redis.RedisCache"/>

resource文件夹下新建文件redis.properties (名字必须叫这个,不然无法获取到里面的数据)

redis.host=127.0.0.1 
redis.port=6379 
redis.pass=
redis.database=0 
redis.maxIdle=300 
redis.maxWait=3000 
redis.testOnBorrow=true 

测试代码:

   @Test
    public void testCache5() throws IOException {
        // 二级缓存是基于sqlsessionfactory的
        // 加载核心配置文件
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        // 获取sqlsessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
        System.out.println("第一个sqlsession的操作");
        SqlSession sqlSession1 = sqlSessionFactory.openSession(false);
        System.out.println(sqlSession1.getMapper(CacheMapper.class).getEmpById(1));
        // 需要关闭/提交会话,才能将一级缓存刷到二级
        sqlSession1.commit(); // 提交,将一级缓存刷到二级缓存中
        System.out.println("=====二级缓存打开,从缓存中获取数据=====");
        SqlSession sqlSession2 = sqlSessionFactory.openSession(false);
        System.out.println(sqlSession2.getMapper(CacheMapper.class).getEmpById(1));
    }
    

最后的结果;
在这里插入图片描述
最后我们来看看redis中的数据变化。
在这里插入图片描述


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值