SpringBoot中整合Redis(myabtis二级缓存)

文章介绍

本文重要讲解了springboot如何整合Redis,以及相关操作。同时又通过自定义二级缓存方案,通过redis来实现mybatis二级缓存的分布式缓存。后续会更新用redis做spring的二级缓存。

SpringBoot Data Redis 简介

Spring Boot Data(数据)Redis 中提供了RedisTemplate和StringRedisTemplate,其中StringRedisTemplate是RedisTemplate的子类,两个方法基本一致,不同之处主要体现在操作的数据类型不同,RedisTemplate中的两个泛型都是object,意味着存储的key和value都可以是一个对象,而SstringRedisTemplatc的两个泛型都是String,意味着StringRedisTemplate的key和valuc都只能是字符串。
在这里插入图片描述

Java操作redis,key和value都是string。没有办法直接放对象或者集合。(java–>对象–>序列化)

Java如果想存储对象,必须将对象序列化,取数据时,必须反序列化。而RedisTempalte就可以自动的将java对象序列化和反序列化

RedisTemplate(Object,Object) 自动序列化和自动反序列化
StringRedisTemplate(String,String)
他们两个的区别就是传入参数的泛型不同,StringRedisTemplate默认还是集成RedisTemplate类的。
在这里插入图片描述
在这里插入图片描述

注意:使用RedisTemplate默认是将对象序列化到redis中,所以放入的对象必须实现对象序列化接口。

SpringBoot整合redis

引入依赖及配置

依赖

 <!--Spring boot Redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

配置

# 配置redis
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.database=0 表示使用第0个数据库

测试

由于SpringBoot已经自动帮我们生成了RedisTemplate和StringRedisTemplate的bean对象,所以我们通过 @Autowired引入即可。

扫描二维码关注公众号,回复: 12839524 查看本文章

stringRedisTemplate

我们在测试类中测试。

 @Autowired
    private  StringRedisTemplate stringRedisTemplate;
    @Test
    public void testRedisOne(){
    
    
    	//存
        stringRedisTemplate.opsForValue().set("name","小陈");
        //取
        stringRedisTemplate.opsForValue().get("name");
    }

在这里插入图片描述
关于key的相关操作
在这里插入图片描述
key的更名

 修改key的名字,判断key是否存在   
 1.如果oldkey不存在报错。
 2.如果newkey已存在,则覆盖newkey原本的内容。
 stringRedisTemplate.rename("name","name2");

rename之前
在这里插入图片描述

rename之后

在这里插入图片描述

 修改key的名字,判断key是否存在   
 1.如果oldkey不存在报错。
 2.如果newkey已存在,则不修改。newkey不存在,则更新key
  stringRedisTemplate.renameIfAbsent("name","name4");

string

@Test
public  void testRedisString(){
    
    
//存取值
    stringRedisTemplate.opsForValue().set("name","小陈");
stringRedisTemplate.opsForValue().get("name");
//设置key的过期时间   TimeUnit 可以选择时间的类型
    stringRedisTemplate.opsForValue().set("code","这是一段代码",120, TimeUnit.SECONDS);
//为key的value进行拼接。小陈是一个好人。
    stringRedisTemplate.opsForValue().append("name","是一个好人");
}

设置过期时间这一块,我们通常用于手机验证码的场景。当客户端请求手机发送验证码时,手机设置的验证码可以设置一个过期时间存储在redis里面,假如设置一分钟。如果过期,则自动在redis里面清除。则该验证码不可再使用。

在这里插入图片描述

list

//创建一个列表,并放入一个元素
stringRedisTemplate.opsForList().leftPush("names", "小陈");
//创建一个列表,放入多个元素
stringRedisTemplate.opsForList().leftPushAll("names", "小王", "小张");
List<String> list = new ArrayList<>();
list.add("小妹");
list.add("小猪");
//存放一个集合。
stringRedisTemplate.opsForList().leftPushAll("names", list);
//遍历redis中元素
List<String> names = stringRedisTemplate.opsForList().range("names", 0, -1);
names.forEach(item-> System.out.println(item));
//截取指定区间元素  保留第1-3个元素。
stringRedisTemplate.opsForList().trim("names",1,3);

存储时,可以存储一个动态数组,也可以直接存储一个集合。
在这里插入图片描述

截断前
在这里插入图片描述

截断后

在这里插入图片描述

RedisTemplate

问题:如果我用StringRedisTemplate存储一个键,那么我是用RedisTemplate能获取到吗?

        stringRedisTemplate.opsForValue().set("name","张三");
        Object name = redisTemplate.opsForValue().get("name");
        System.out.println(name);

java.lang.Exception: No tests found matching Method testRedisTemplate(com.marchsoft.group.manager.GroupManagerApplicationTests) from org.junit.internal.requests.ClassRequest@f5958c9
报错分析:使用redisTemplate存储的是对象,对象存redis之前是要经过序列化的。我们通过redisTemplate获取的name,也是通过序列化后,再去redis查。也就是说,我们拿一个序列化后的key去查一个没有序列化的key,那肯定是查不到的。

我们使用redis存储一个对象

    	UserTest userTest = new UserTest();
        userTest.setGender("男").setAge(18);
        redisTemplate.opsForValue().set("user", userTest);
        redisTemplate.opsForValue().get("user");

存储结果:
在这里插入图片描述
正是因为我们使用redisTemplate,导致存储的键值都进行了序列化。所以我们在redis终端操作时,看不到原本的信息,看到的都是序列化的信息。
在这里插入图片描述

这样,对于我们开发人员来说是不方便的,我们希望key还是存的string,value是Object。

我们想一下,redisTemplate默认使用的是jdk序列化,序列化后key和value乱码,导致显示不出来。stringRedisTemplate设置的key和value能正常显示,那么stringRedisTemplate使用的是什么序列化呢?
在这里插入图片描述
好,既然有StringRedisSerialize,那么尝试着更换redisTemplate的key 的序列化方式。

    UserTest userTest = new UserTest();
        userTest.setGender("男").setAge(18);
        //设置key的序列化方式为字符串序列化
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.opsForValue().set("user", userTest);
        System.out.println( redisTemplate.opsForValue().get("user"));

效果
在这里插入图片描述
操作其它类型的数据

   UserTest userTest = new UserTest();
        userTest.setGender("男").setAge(18);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.opsForValue().set("user", userTest);
        redisTemplate.opsForList().leftPushAll("list",userTest);
        redisTemplate.opsForSet().add("set",userTest);
        redisTemplate.opsForZSet().add("zset",userTest,10);
        redisTemplate.opsForHash().put("map","name","user");
        System.out.println( redisTemplate.opsForValue().get("map"));

我们发现,操作hash时,我们需要操纵两个set,第一个key正常,第二个key还是乱码。
在这里插入图片描述
我们还可以设置hash的key

        redisTemplate.setHashKeySerializer(new StringRedisSerializer());

这样,当我们查看时,就正常了。
在这里插入图片描述

redis提供的序列化方式

在这里插入图片描述
附:查看结构图
第一步:找到底层接口,点击show Diagram
在这里插入图片描述
第二步:展示实现的类
在这里插入图片描述

bound绑定key

基于一个key的操作

   public void testBound() {
    
    
        stringRedisTemplate.opsForValue().set("name", "张三");
        stringRedisTemplate.opsForValue().append("name", "是一个好人");
        stringRedisTemplate.opsForValue().get("name");
        //从上方操作我们可以看出,我们都是基于key为name的操作,每次操作前我们都必须指定key 麻烦!
        //当多次操作都是一个key时,我们可以绑定此key,之后进行任何操作都是基于它的操作。
        BoundValueOperations<String, String> stringBound = stringRedisTemplate.boundValueOps("name2");
        stringBound.set("李四");
        stringBound.append("是一个好人");
        stringBound.get();
    }

在这里插入图片描述
其他的绑定方法。同样redisTemplate里面也有这些操作。
在这里插入图片描述

Redis应用场景

  • 利用redis字符串类型完成项目中手机验证码存储的实现。
  • 利用redis字符串类型完成具有失效性业务功能。 下单之后,有30分钟的支付时间。
  • 利用redis分布式集群系统中,Session共享
  • 利用redis zset类型 可排序set类型 元素,分数,排行榜之类。 销量排行榜 sales(zset) [商品id,商品销量]
  • 利用redis 实现分布式缓存
  • 利用redis存储认证之后token信息 微信小程序/微信公众号 | 用户登陆之后,会有一个openid,拿着openid去生成一个token令牌。 用户每次访问时都需要携带该token,如果超时,则重新登陆。我们可以将此令牌存储到redis中。
  • 利用redis解决分布式集群系统中分布式锁问题。 redis 单线程 单进程。 n 20 定义 。
    jvm 一个进程开启多个线程 synchronize int n =20 当一个jvm时,使用synchronize完全没问题。
    但是有多个jvm时,每个上面的n都等于20,但是我们总量20,这时就可以通过redis存储来实现。

缓存

概念简介

  1. 定义:计算机内存中的一段数据
  2. 内存中数据特点:1.读写快 2.断电立即消失
  3. 缓存解决的问题:
    1. 提高网站吞吐量,提高网站效率(吞:收到的请求。吐:针对请求进行的响应)
    2. 核心解决问题:缓存的存在是用来减轻数据库访问压力(假如100个人同时访问数据库中的同一条数据,则会建立100个数据库链接。。。)
  4. 缓存既然能提高速率,是不是越高越好?
    注意:使用缓存时,一定是数据库中数据极少发生修改,更多用于查询这种情况。 比如 地址 省份 , 城市 ,村。。。还有一些商品的基本信息等。
  5. 本地缓存和分布式缓存区别:
    1. 本地缓存:存在应用服务器内存中数据成为本地缓存(mybatis提供的缓存cache,缓存和应用在一起)
    2. 分布式缓存:存储在当前应用服务器内存之外的数据称为分布式缓存(redis缓存,缓存和应用服务器不在一起)
    3. 集群:将同一种服务的多个节点放在一起共同对系统提供服务过程称之为集群。
    4. 分布式:有多个不同服务集群共同对系统提供服务,这个系统称之为腹部是系统。
    5. 分布式一定建立在集群之上。(四个厨子,称为一个集群。有两个炒菜,两个切菜,称为分布式)

mybatis二级缓存

在这里插入图片描述

myabtis中应用级缓存(二级缓存) SqlSessionFactory中相同的namespace才能会话共享。

开启缓存的方式非常简单,只需要在sql映射文件中加上

service

public interface IAppService {
    
    
    List<App> findAll();
}

dao

@Component
public interface AppDAO {
    
    
    List<App> findAll();
}

mapper.xml

<?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.cache.mycache.dao.AppDAO">

    <cache/>

    <!--    查询-->
    <select id="findAll" resultType="com.cache.mycache.entity.App">
        select *from mnt_app order by app_id
    </select>

</mapper>

测试

	    System.out.println("第一次");
        appService.findAll();
        System.out.println("第二次");
        appService.findAll();

结果
在这里插入图片描述

在这里插入图片描述
总结:
mybatis自身提供的二级缓存是本地缓存,实际上是把数据缓存到了自身服务器上(tomcat)的虚拟中,所以当我们停止项目时,缓存就会清除。所以每次重启项目时,第一次都是从数据库中拿数据,这也是本地缓存的弊端。

mybatis二级缓存源码阅读

  1. 查看mybatis的缓存实现。

<cache/ >为我们提供了type属性,默认的属性值是PerpetualCache
相当于 < cache type=“org.apache.ibatis.cache.impl.PerpetualCache” />

在这里插入图片描述
cache源码
在这里插入图片描述


需要注意的是,我们下载源文档发现。
在这里插入图片描述
我们发现是没有使用的,也就是说,当我们使用mybatis自身的二级缓存时,是没有删除某个缓存的操作的,如遇到数据的增删改,是直接进行清空缓存的。

PerpetualCache

我们发现,缓存的底层其实就是一个HashMap,通过对hashmap的增删改查,来实现缓存操作。
在这里插入图片描述

我们通过断点调试,看一下执行流程

put操作
在这里插入图片描述
进行下一步。
在这里插入图片描述
我们发现,
key存储的是 编码+方法名称+编码+查询语句
value存储的是 数据库中查询的信息。

get操作
在这里插入图片描述

我们发现,get的key是我们put时,进行存储的key。

通过redis实现mybatis分布式缓存

在这里插入图片描述

之前我们学习redis时,我们可以通过springboot提供的reids的一些操作,将数据存储到reids中。那么我们是否可以改变一下mybatis二级缓存的默认实现,实现cache,重写方法时,用redis的操作进行重写,进而将缓存内容存入到reids中呢?

替换为redis缓存,实现cache接口

实现步骤:

1. 创建RedisCache类,实现Cache接口。
public class RedisCache implements Cache {
    
    
    @Override
    public String getId() {
    
    return null;}
    @Override
    public void putObject(Object key, Object value) {
    
    }
    @Override
    public Object getObject(Object key) {
    
    return null;}
    @Override
    public Object removeObject(Object key) {
    
    return null;}
    @Override
    public void clear() {
    
    }
    @Override
    public int getSize() {
    
    return 0;}
    @Override
    public ReadWriteLock getReadWriteLock() {
    
    return null;}
}
2. < cache /> type指向rediscache的实现
 <cache type="com.cache.mycache.cache.RedisCache"/ >
3. 测试rediscache中需要的内容。所有方法空实现直接运行测试。

Base cache implementations must have a constructor that takes a String id as a parameter
出错一:需要一个构造方法,并且以一个string类型的id作为形参。
打印id:com.cache.mycache.dao.AppDAO 我们发现id就是namespace
Caused by: java.lang.IllegalArgumentException: name argument cannot be null
出错二:getId的返回值不能为空。(即getId的返回值不能为空) id是当前的文件对应的Dao层com.cache.mycache.dao.AppDAO 即namespace
在这里插入图片描述

4. 测试一下缓存的执行流程。我们打印set和get里面的key和value
    //缓存存入
    @Override
    public void putObject(Object key, Object value) {
    
    
        System.out.println(key.toString());
        System.out.println(value.toString());


    }
    //缓存取出
    @Override
    public Object getObject(Object key) {
    
    
        System.out.println(key.toString());
        return null;
    }

在这里插入图片描述

5. 使用redisRemplate来进行redis缓存,创建获取redisTemplate的工具类

问题:我们知道RedisCache的实例化是交给sql映射文件的。实例化时,传入的id是namespace。而RedisCache并不是我们的工厂类,所以我们不能直接注入RedisTemplate。
我们可以使用ApplicationContext的getBean(String name)通过工厂来获取RedisTemplate对象。

//用来获取springboot创建好的工厂
@Configuration
public class ApplicationContextUtils implements ApplicationContextAware {
    
    

    //保留下来的工厂
    private static ApplicationContext applicationContextProdect;

    //将创建好的工厂以参数形式传递给这个类
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    
    
        applicationContextProdect = applicationContext;
    }

    //提供在工厂中获取对象的方法 通过名字获取   RedisTemplate 在工厂中的对象就是redisTemplate
    public static Object getBean(String beanName) {
    
    
        return applicationContextProdect.getBean(beanName);
    }


    //getbean有两种方式拿:1)按类型拿  2)按名字拿

    //从应用上下文里面获得类实例(即bean容器里面获得类容器)
    //为什么我们现在要采用这种麻烦的方法(以前直接用Autowired注解自动装配进去了)--- 这与redis连接池有关
    //用Redis时,建了许多连接池,我们在redis里面拿缓存对象时,缓存对象与每个连接都有一个RedisTemplate,你在注入时用自动注入,不同
    // RedisTemplate是同类型同名的,注入时你得到的是哪个连接使用的redisTemplate呢?所以你注入时分不清
    //所以我们重新封装一个getBean的方法,按指定类型或名字来拿bean实例
    public static <T> T getBean(Class<T> tClass) {
    
    
        return applicationContextProdect.getBean(tClass);
    }

    //或者
//    public static <T> T getBean(String name) {
    
    
//        return (T) applicationContextProdect.getBean(name);
//    }
}
6. RedisConfig的类方法的put和get 的实现。
    //缓存存入
    @Override
    public void putObject(Object key, Object value) {
    
    
        //我们知道,id表示当前的namespace,key表示调用的方法,结果为值。相当于三个参数。此时我们可以使用Hash来存储
        RedisTemplate redisTemplate = (RedisTemplate)ApplicationContextUtils.getBean("redisTemplate");
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.opsForHash().put(id,key.toString(),value);

    }
    //缓存取出
    @Override
    public Object getObject(Object key) {
    
    
        RedisTemplate redisTemplate = (RedisTemplate)ApplicationContextUtils.getBean("redisTemplate");
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        return redisTemplate.opsForHash().get(id,key.toString());
    }

测试:
在这里插入图片描述
redis中
在这里插入图片描述

7. 重写清除缓存的方法

之前我们提到过,虽然redis提供了remove和clear方法,但是mybatis的缓存操作时,remove方法是不调用的。也就是说,只要 我们进行了增删改操作,mybatis默认走的是清空clear。增删改都会清空缓存

    @Override
    public Object removeObject(Object key) {
    
    
        System.out.println("移除缓存");
        return null;
    }

    @Override
    public void clear() {
    
    
        System.out.println("清空缓存");
    }

测试

    @Test
    public void testApp(){
    
    
        System.out.println("第一次查询所有");
        appService.findAll();
        System.out.println("第二次查询所有");
        appService.findAll();
        System.out.println("删除id为1的数据");
        appService.deleteOne(1L);
    }

因为我们用的redis缓存,所以再次重启项目时,redis缓存中有的话,就不查询的。因为redis缓存是独立项目服务器之外的,所以重启项目并不会清空redis缓存。

在这里插入图片描述
我们看,虽然我们删除了一条信息,但是走的是清空缓存。
在这里插入图片描述

8.重写获取缓存数量的方法
    @Override
    public int getSize() {
    
    
        RedisTemplate redisTemplate = (RedisTemplate)ApplicationContextUtils.getBean("redisTemplate");
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        //返回int类型
        return redisTemplate.opsForHash().size(id).intValue();
    }
9.封装redisTemplate

我们在RedisCache调用就可以直接 redisTemplate.opsForHash().size(id).intValue();

    //获取redisTemplate  //每个连接池的连接都要获得RedisTemplate
    private RedisTemplate getRedisTemplate() {
    
    
        RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }
10.RedisCache
//自定义Redis缓存实现

public class RedisCache implements Cache {
    
    
    //当前放入缓存的namespace
    private final String id;
    public RedisCache(String id) {
    
    
        this.id = id;
        System.out.println(id);
    }
    @Override
    public String getId() {
    
    
        return this.id;
    }
    //缓存存入
    @Override
    public void putObject(Object key, Object value) {
    
    
        //我们知道,id表示当前的namespace,key表示调用的方法,结果为值。相当于三个参数。此时我们可以使用Hash来存储
        getRedisTemplate().opsForHash().put(id, key.toString(), value);
    }
    //缓存取出
    @Override
    public Object getObject(Object key) {
    
    
        return getRedisTemplate().opsForHash().get(id, key.toString());
    }

    //redis的保存方法,默认不会调用  后续版本可能会调用
    @Override
    public Object removeObject(Object key) {
    
    
        System.out.println("移除缓存");
        return null;
    }
    @Override
    public void clear() {
    
    
        System.out.println("清空缓存");
        getRedisTemplate().delete(id);
    }
    @Override
    public int getSize() {
    
    
        //返回int类型
        return getRedisTemplate().opsForHash().size(id).intValue();
    }
    @Override
    public ReadWriteLock getReadWriteLock() {
    
    
        return null;
    }
       //获取redisTemplate  //每个连接池的连接都要获得RedisTemplate
    private RedisTemplate getRedisTemplate() {
    
    
        RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}

关联关系的情况分析

实际项目中,我们通常会进行联表查询。比如我要查一个用户的所在部门,此时就是一个用户表,还有一个部门表。

查询示例:

    <select id="findUserAndDeptById" parameterType="com.cache.mycache.entity.dto.UserDTO" resultMap="userMessage">
        select u.nick_name,d.name from sys_user u,sys_dept d where u.dept_id=d.id and u.id=#{
    
    id}
    </select>
    
    <resultMap id="userMessage" type="com.cache.mycache.entity.dto.UserDTO">
        <association property="dept" javaType="com.cache.mycache.entity.Dept">
            <result property="name" column="name"/>
        </association>
    </resultMap>

 @Test
    public  void testAll(){
    
    
    	//查询一条用户信息
        userService.findOne(1L);
        //查询一条部门信息
        deptService.findOne(1L);
        //查询一条用户和部门信息
        userService.findUserAndDeptById(1L);
    }

缓存中:
在这里插入图片描述
我们发现,缓存中有两条数据,其中基于com.cache.mycache.dao.UserDao的有两条。包含一条用户信息和用户及其部门信息。

删除示例

1.删除一条部门信息(缓存中默认有上面的三条记录)

    @Test
    public  void testAll(){
    
    
        userService.findOne(1L);
        deptService.findOne(1L);
        userService.findUserAndDeptById(1L);
        deptService.deleteOne(1L);
    }

此时缓存:
在这里插入图片描述
我们发现,此时com.cache.mycache.dao.DeptDAO下面的数据已经删除了。
此时问题来了,com.cache.mycache.dao.UserDao中存储了一条关于用户及其部门的记录,并没有删除,那么当我们再次执行查询时,返回的并不是数据库中真实的数据,而是缓存中的假数据。

解决方案

mybatis为我们提供了cache-ref这个缓存标签,通过他,我们可以将两个或多个namespace绑定在一起,当其中一个发生增删改时,就清空相关联的所有namespace的内容。
比如,我使用com.cache.mycache.dao.UserDAO关联com.cache.mycache.dao.DeptDAO,当其中一个namespace发生增删改时,会把这两个namespace下缓存的所有记录清空。

在这里插入图片描述
存储缓存数据时,谁关联了谁,数据就缓存到主动关联的那个namespace(即com.cache.mycache.dao.UserDAO).
我们发现,com.cache.mycache.dao.UserDAO并没有设置缓存方式,他会自动使用com.cache.mycache.dao.DeptDAO的关联方式。

删除测试

    @Test
    public  void testAll(){
    
    
        userService.findOne(1L);
        deptService.findOne(1L);
        userService.findUserAndDeptById(1L);
        deptService.deleteOne(1L);
    }

在这里插入图片描述
缓存:
在这里插入图片描述

我们发现,当我们删除dept时,与他相关联的用户也删除了。说明关联起了作用。同理,当删除用户的一条信息时,dept的缓存也会随之清空。

注意:
因为user关联的dept,所以user的缓存存储方式(redis),以及存储位置(key,namespace)都是基于dept的。我们这次不进行删除,查看一下缓存的存储位置

    @Test
    public  void testAll(){
    
    
        userService.findOne(1L);
        deptService.findOne(1L);
        userService.findUserAndDeptById(1L);
    }

在这里插入图片描述

我们发现,二者的缓存都存储在了dept的namespace下面。

mybatis二级缓存优缺点分析

优点

  1. 实现简单,使用方便。只需要一个cache注解即可。
  2. 维护简单,只要进行增删改,就全部删除相关缓存数据,不需要考虑脏数据。

缺点

  1. 灵活性差,死板。不能移除某一个缓存,只要发生增删改,就清除所有的缓存。
  2. 使用太过局限,在增删改比较多的系统,但同时数据量比较大的系统时,频繁的清空缓存,并不利于性能的提升。

缓存穿透

客户端查一个数据库中没有的数据。某些木马程序,大量请求数据库中没有的程序时,导致系统崩溃。(id=-1,id=random等。一直请求)
解决方案:将没有查询到的数据进行缓存,value设置为null。(不同担心日后添加了该key的值,缓存里面没有。因为我们进行增删改操作,缓存都会进行清空处理的)

    public  void testAll(){
    
    
        userService.findOne(-1L);
        deptService.findOne(-2L);
        userService.findUserAndDeptById(-3L);
        }

没有的数据,myabtis自动缓存了空值。避免恶意的数据库请求。
在这里插入图片描述

缓存雪崩

某一时刻,所有缓存失效。同时客户端进行大量请求。
解决方案: 1.缓存永久存储(即不设置超时时间。不推荐) 2.不同模块设置不同的缓存超时时间。

在这里插入图片描述

附:redis数据乱码解决方法

之前我们的演示,存储在redis中的value都是乱码的。是因为我们默认使用的是jdk的序列化方式。我们使用FastJson的序列化方式即可解决这个问题。
使用方式
1.引入依赖

 <!--fastjson-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.58</version>
        </dependency>
  1. 设置redisTemplate的序列化方式
 //获取redisTemplate  //每个连接池的连接都要获得RedisTemplate
    private RedisTemplate getRedisTemplate() {
    
    
        RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
        redisTemplate.setValueSerializer(fastJsonRedisSerializer);
        redisTemplate.setHashValueSerializer(fastJsonRedisSerializer);
        return redisTemplate;
    }

3.效果展示
在这里插入图片描述

参考文章:

  1. 用Redis做Mybatis二级缓存

猜你喜欢

转载自blog.csdn.net/zhang19903848257/article/details/114896873
今日推荐