Redis之与SSM集成Spring注解式缓存

🎉🎉欢迎来到我的CSDN主页!🎉🎉

🏅我是君易--鑨,一个在CSDN分享笔记的博主。📚📚

🌟推荐给大家我的博客专栏《Redis实战开发》。🎯🎯

🎁如果感觉还不错的话请给我关注加三连吧!🎁🎁

💖期待你的加入,一起学习,一起进步!💖💖

目录

前言

一、Redis集成到SSM中

[1. 导入pom.xml依赖配置](#1. 导入pom.xml依赖配置)

[2. 注册一个redis.properties](#2. 注册一个redis.properties)

[3. 新建spring-redis.xml文件](#3. 新建spring-redis.xml文件)

注意事项:

1.配置.properties文件注册事项

[2. pom.xm配置注意事项](#2. pom.xm配置注意事项)

[3. redisTemplate的使用](#3. redisTemplate的使用)

配置数据源

配置连接工厂

配置序列化器

​编辑

配置缓存管理器

配置缓存生成键名的生成规则

CacheKeyGenerator.java

[4. spring-context.xml中配置](#4. spring-context.xml中配置)

二、Redis注解开发及应用场景

[1. 未使用注解开发](#1. 未使用注解开发)

[2. @Cacheable](#2. @Cacheable)

[2.1 概述及作用](#2.1 概述及作用)

[2.2 注解参数说明](#2.2 注解参数说明)

[2.3 案例演示](#2.3 案例演示)

携带value参数的使用

携带包含key参数的使用

[携带 condition参数的使用](#携带 condition参数的使用)

情况一:符合条件

情况二:不符合触发条件

[3. @CachePut](#3. @CachePut)

[3.2 参数说明](#3.2 参数说明)

[3.3 案例演示](#3.3 案例演示)

[4. @CacheEvict](#4. @CacheEvict)

[4.1 概述及作用](#4.1 概述及作用)

[4.2 参数说明](#4.2 参数说明)

[4.3 案例演示](#4.3 案例演示)

三、Redis的三种极端现象

[1. 击穿](#1. 击穿)

概述

解决方案:

[2. 穿透](#2. 穿透)

概述

解决方案

[3. 雪崩](#3. 雪崩)

概述

解决方案

本期的Redis系列博客分享到此结束啦

希望老铁关注加三连

这是对博客最大的支持鼓励


前言

在Redis系列的前面两期博客中带大家了解了有关Redis在Linux及Windows两个系统的安装及使用,然后有分享了java中如何去使用Redis操作Redis数据库。这期博客带大家一起了解SSM中集成Redis及其中的用法。

一、Redis集成到SSM中

1. 导入pom.xml依赖配置

在项目的pom.xml中导入Redis的相关依赖。
首先在properties标签中添加以下代码

<redis.version>2.9.0</redis.version>
    <redis.spring.version>1.7.1.RELEASE</redis.spring.version>

然后在 dependencies的标签中添加以下依赖

XML 复制代码
<dependency>
      <groupId>redis.clients</groupId>
      <artifactId>jedis</artifactId>
      <version>${redis.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.data</groupId>
      <artifactId>spring-data-redis</artifactId>
      <version>${redis.spring.version}</version>
    </dependency>

到此我们的pom.xml文件的配置就告一段落了

2. 注册一个redis.properties

这个redis.properties文件是用于配置连接Redis客户端的文件,类似于我们之前配置MySQL数据库连接的jdbc.properties,用于编写连接的端口号、端口、账号、密码、最好连接数等等之类的,该文件也是用于后面有关redis.xml文件的配置。

html 复制代码
redis.hostName=127.0.0.1--->(数据库名)
redis.port=6379    --->(端口号)
redis.password=123456  ----->(密码)
redis.timeout=10000
redis.maxIdle=300
redis.maxTotal=1000
redis.maxWaitMillis=1000
redis.minEvictableIdleTimeMillis=300000
redis.numTestsPerEvictionRun=1024
redis.timeBetweenEvictionRunsMillis=30000
redis.testOnBorrow=true
redis.testWhileIdle=true
redis.expiration=3600

3. 新建spring-redis.xml文件

这是文件的作用是将Redis配置到我们的项目中去,以及读取redis.properties文件的信息。

XML 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:cache="http://www.springframework.org/schema/cache"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/cache
       http://www.springframework.org/schema/cache/spring-cache.xsd">

    <!-- 1. 引入properties配置文件 -->
    <!--<context:property-placeholder location="classpath:redis.properties" />-->

    <!-- 2. redis连接池配置-->
    <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <!--最大空闲数-->
        <property name="maxIdle" value="${redis.maxIdle}"/>
        <!--连接池的最大数据库连接数  -->
        <property name="maxTotal" value="${redis.maxTotal}"/>
        <!--最大建立连接等待时间-->
        <property name="maxWaitMillis" value="${redis.maxWaitMillis}"/>
        <!--逐出连接的最小空闲时间 默认1800000毫秒(30分钟)-->
        <property name="minEvictableIdleTimeMillis" value="${redis.minEvictableIdleTimeMillis}"/>
        <!--每次逐出检查时 逐出的最大数目 如果为负数就是 : 1/abs(n), 默认3-->
        <property name="numTestsPerEvictionRun" value="${redis.numTestsPerEvictionRun}"/>
        <!--逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1-->
        <property name="timeBetweenEvictionRunsMillis" value="${redis.timeBetweenEvictionRunsMillis}"/>
        <!--是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个-->
        <property name="testOnBorrow" value="${redis.testOnBorrow}"/>
        <!--在空闲时检查有效性, 默认false  -->
        <property name="testWhileIdle" value="${redis.testWhileIdle}"/>
    </bean>

    <!-- 3. redis连接工厂 -->
    <bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
          destroy-method="destroy">
        <property name="poolConfig" ref="poolConfig"/>
        <!--IP地址 -->
        <property name="hostName" value="${redis.hostName}"/>
        <!--端口号  -->
        <property name="port" value="${redis.port}"/>
        <!--如果Redis设置有密码  -->
        <property name="password" value="${redis.password}"/>
        <!--客户端超时时间单位是毫秒  -->
        <property name="timeout" value="${redis.timeout}"/>
    </bean>

    <!-- 4. redis操作模板,使用该对象可以操作redis
        hibernate课程中hibernatetemplete,相当于session,专门操作数据库。
    -->
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
        <property name="connectionFactory" ref="connectionFactory"/>
        <!--如果不配置Serializer,那么存储的时候缺省使用String,如果用User类型存储,那么会提示错误User can't cast to String!!  -->
        <property name="keySerializer">
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
        </property>
        <property name="valueSerializer">
            <bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
        </property>
        <property name="hashKeySerializer">
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
        </property>
        <property name="hashValueSerializer">
            <bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
        </property>
        <!--开启事务  -->
        <property name="enableTransactionSupport" value="true"/>
    </bean>

    <!--  5.配置缓存管理器  -->
    <bean id="redisCacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">
        <constructor-arg name="redisOperations" ref="redisTemplate"/>
        <!--redis缓存数据过期时间单位秒-->
        <property name="defaultExpiration" value="${redis.expiration}"/>
        <!--是否使用缓存前缀,与cachePrefix相关-->
        <property name="usePrefix" value="true"/>
        <!--配置缓存前缀名称-->
        <property name="cachePrefix">
            <bean class="org.springframework.data.redis.cache.DefaultRedisCachePrefix">
                <constructor-arg index="0" value="-cache-"/>
            </bean>
        </property>
    </bean>

    <!--6.配置缓存生成键名的生成规则-->
    <bean id="cacheKeyGenerator" class="com.zking.ssm.redis.CacheKeyGenerator"></bean>

    <!--7.启用缓存注解功能-->
    <cache:annotation-driven cache-manager="redisCacheManager" key-generator="cacheKeyGenerator"/>
</beans>

注意事项:

1.配置.properties文件注册事项

spring-context.xml文件中需要注册两个及两个以上的**.peoperties文件时,不能在spring-*.xml中注册,因此只能在spring-context.xml**文件中配置。因为一但分开单独注册则又一个文件的信息读取不到。
因此我们会将spring-*.xml文件的注册代码注释掉(以spring-redis.xml为例)

然后我们会在 spring-context.xml文件中进行总的设置配置

2. pom.xm配置注意事项

注意pom.xml文件中的resource标签的配置信息,resources的配置必须涵盖读取.properties结尾的文件。

3. redisTemplate的使用

redisTemplate的使用可以参照jdbcTemplate/amqpTemplate...的使用,也就是序列化的配置

配置数据源

配置连接工厂

配置序列化器

配置缓存管理器

redis中有很多数据,例如:用户数据、数据字典数据、系统设置数据等,不同的数据有着不同的有效时间,用于设置时长当然这只是其中之一;也可以用于设置缓存的前缀

配置缓存生成键名的生成规则

CacheKeyGenerator.java
java 复制代码
package com.zking.ssm.redis;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.util.ClassUtils;

import java.lang.reflect.Array;
import java.lang.reflect.Method;

@Slf4j
public class CacheKeyGenerator implements KeyGenerator {
    // custom cache key
    public static final int NO_PARAM_KEY = 0;
    public static final int NULL_PARAM_KEY = 53;

    @Override
    public Object generate(Object target, Method method, Object... params) {
        StringBuilder key = new StringBuilder();
        key.append(target.getClass().getSimpleName()).append(".").append(method.getName()).append(":");
        if (params.length == 0) {
            key.append(NO_PARAM_KEY);
        } else {
            int count = 0;
            for (Object param : params) {
                if (0 != count) {//参数之间用,进行分隔
                    key.append(',');
                }
                if (param == null) {
                    key.append(NULL_PARAM_KEY);
                } else if (ClassUtils.isPrimitiveArray(param.getClass())) {
                    int length = Array.getLength(param);
                    for (int i = 0; i < length; i++) {
                        key.append(Array.get(param, i));
                        key.append(',');
                    }
                } else if (ClassUtils.isPrimitiveOrWrapper(param.getClass()) || param instanceof String) {
                    key.append(param);
                } else {//Java一定要重写hashCode和eqauls
                    key.append(param.hashCode());
                }
                count++;
            }
        }

        String finalKey = key.toString();
//        IEDA要安装lombok插件
        log.debug("using cache key={}", finalKey);
        return finalKey;
    }
}

4. spring-context.xml中配置

二、Redis注解开发及应用场景

1. 未使用注解开发

我们编写一个测试类用于测试模拟从数据库中获取数据,以此来实现模拟数据库操作。

ClazzBizTest.java

java 复制代码
package com.zking.shiro;

import com.zking.ssm.biz.ClazzBiz;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * @author小李飞刀
 * @site www.javaxl.com
 * @company xxx公司
 * @create  2022-10-26 15:29
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:applicationContext.xml"})
public class ClazzBizTest {
    @Autowired
    private ClazzBiz clazzBiz;

    @Test
    public void test1(){
        System.out.println(clazzBiz.selectByPrimaryKey(10));
        System.out.println(clazzBiz.selectByPrimaryKey(10));
    }

    @Test
    public void test2(){
        clazzBiz.deleteByPrimaryKey(10);
    }
}

当我们执行测试test1()方法时我们可以通过查看控制台输出的结果得知当我们调用两次查询方法时执行了两次数据库操作。(控制台输出如下图)

接下来我们一起来看看使用Redis注解运行后的方法及应用场景

2. @Cacheable

2.1 概述及作用

@Cacheable用于具有返回值的方法,可以保存缓存键值对。一般用于查询操作。一般配置在方法或类上。简单的说就是即写入也读取。

**作用:**本方法执行后,先去缓存看有没有数据,如果没有,从数据库中查找出来,给缓存中存一份,返回结果, 下次本方法执行,在缓存未过期情况下,先在缓存中查找,有的话直接返回,没有的话从数据库查找。

2.2 注解参数说明

value:缓存位置的一段名称,不能为空
key:缓存的key,默认为空,表示使用方法的参数类型及参数值作为key,支持SpEL
condition:触发条件,满足条件就加入缓存,默认为空,表示全部都加入缓存,支持SpEL

2.3 案例演示

携带value参数的使用

然后我们再在测试类去测试该方法,查看控制台的输出内容,看看是否与未使用注解式标签是一样。

由此看到第二次时我们访问的是缓存,如果我们再执行一次则一次都不会访问操作数据库,而是访问我们的缓存。

我们可以在Redis客户端上查看我们的缓存数据

携带包含key参数的使用

使用了key参数是表自定义生成key

当我们再次去执行被改标签使用了的方法时,会在Redis缓存库中重新生产一个根据自己定义的key。

携带 condition参数的使用

condition代表的是触发执行的条件,满足条件就执行方法加入缓存

当我们分别执行一个符合条件一个不符合条件时,到redis缓存库中查看

情况一:符合条件

查看redis的缓存库

情况二:不符合触发条件

由图可知,当我们未达到触发条件执行该方法则不会生成数据缓存在我们的redis库中生成数据缓存。

3. @CachePut

3.1 概述及作用

与@Cacheable类似,也可以保存缓存键值对。一般用于插入和修改操作。简单的说只写入不读取。

作用:适用于写入或更新操作,需要将结果放入缓存并确保缓存的数据是最新的。每次调用被@CachePut注解的方法时,都会执行方法体内的逻辑,并将结果存入缓存中。

3.2 参数说明

value: 缓存的名称,在 spring 配置文件中定义,必须指定至少一个
key: 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合
condition:缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存

3.3 案例演示

参数和Cacheable的用法一致,所以我来演示一下CachePut与Cacheable的区别

4. @CacheEvict

4.1 概述及作用

用于void类型的方法,不需要保存任何值,一般用于删除缓存key的操作。

4.2 参数说明

value:缓存位置的一段名称,不能为空
key:缓存的key,默认为空,表示使用方法的参数类型及参数值作为key,支持SpEL
condition:触发条件,满足条件就加入缓存,默认为空,表示全部都加入缓存,支持SpEL
allEntries:true表示清除value中的全部缓存,默认为false

4.3 案例演示

首先我们添加几条数据到我们的redis缓存数据库中,方便我们测试使用

运行该方法在去查看redis缓存数据库

我们在redis客户端刷新数据库查看一下就会发现我们之前存储的数据被清理掉了

三、Redis的三种极端现象

描述图

1. 击穿

概述

在Redis中,击穿是指一个热点key,在高并发的请求下,由于这个key失效的瞬间,持续的高并发直接请求数据库。具体来说,当某个热点key失效或者被删除,而此时恰好有大量的请求正在访问这个key,这些请求就会直接穿透到数据库中,导致数据库压力骤增,甚至可能引发数据库崩溃等问题。

解决方案:

  • 限流:请求redis之前做流量削峰
  • 设置热点key永不过期
  • 设置锁
  1. 1.获取 Redis 锁,如果没有获取到,则回到任务队列继续排队
  2. 2.获取到锁,从数据库拉取数据并放入缓存中
  3. 3.释放锁,其他请求从缓存中拿到数据

2. 穿透

概述

在Redis中,缓存穿透是指查询一个根本不存在的数据,缓存层没有命中,然后去查数据库(持久层),数据库(持久层)也没有命中。 通常如果从存储层查不到数据则不写入缓存层。 比如:用户不断发起请求,通过文章的id来获取文章,如果这个id没有对应的数据,则每次都会请求到数据库。 如果这个用户是攻击者,在请求过多时会导致数据库压力过大,严重会击垮数据库。

解决方案

1.规则排除

可以增加一些参数检验。例如数据库数据 id 一般都是递增的,如果请求 id = -10 这种参数,势必绕过Redis。避免这种情况,可以对用户真实性检验等操作。

2.null值填充

当缓存穿透时,redis存入一个类似null的值,下次访问则直接缓存返回空,当数据库中存在该数据的值则需要把redis存在的null值清除并载入新值,此方案不能解决频繁随机不规则的key请求。

3.双重检测加锁

Redis在并发发情况下,发生多次访问数据库,可以使用双重检测加锁机制防止缓存击穿。

4.使用布隆过滤器

布隆过滤器是一种数据结构,可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。如果想要判断一个元素是不是在一个集合里,一般想到的是将集合中所有元素保存起来,然后通过比较确定。同时检索速度也越来越慢。使用布隆过滤器可以避免将所有可能缓存的数据都保存下来,只需要将可能的数据保存即可,从而节省空间。

3. 雪崩

概述

在Redis中,雪崩是指缓存层发生故障或失效时,所有请求都会直接到达数据库层,导致数据库压力剧增,甚至崩溃的情况。缓存雪崩通常是由于缓存层承载了大量的请求,有效的保护了存储层,但如果缓存由于某些原因整体不能够提供服务,所有的请求就会到达存储层,存储层的调用量就会暴增,从而引发雪崩现象。缓存雪崩可能会发生在缓存服务器重启或者大量缓存集中在某一个时间段失效的情况下,此时大量数据会去直接访问数据库,给数据库带来很大的压力。

解决方案

  • 给不同的热点key设置不同的缓存策略
  • 使用分布式缓存
  • 使用锁来更新key

本期的Redis系列博客分享到此结束啦

希望老铁关注加三连

这是对博客最大的支持鼓励

相关推荐
凌虚(失业了求个工作)27 分钟前
RAG 示例:使用 langchain、Redis、llama.cpp 构建一个 kubernetes 知识库问答
人工智能·redis·python·langchain·llama
武昌库里写JAVA3 小时前
Vue3与Vue2 对比
spring boot·spring·毕业设计·layui·课程设计
栗豆包3 小时前
w053基于web的宠物咖啡馆平台的设计与实现
java·struts·spring·tomcat·maven·intellij-idea
mit6.8244 小时前
[Redis#3] 通用命令 | 数据类型 | 内部编码 | 单线程 | 快的原因
linux·redis·分布式
CoderJia程序员甲4 小时前
重学SpringBoot3-Spring Retry实践
java·spring boot·spring·retry·重试机制
mit6.8244 小时前
[Redis#4] string | 常用命令 | + mysql use:cache | session
数据库·redis·后端·缓存
Beekeeper&&P...5 小时前
map和redis关系
数据库·redis·缓存
一朵忽明忽暗的云5 小时前
【Redis_Day6】Hash类型
redis·hash类型
Nightselfhurt5 小时前
RPC学习
java·spring boot·后端·spring·rpc
Ling_suu5 小时前
SpringMVC——简介及入门
spring·mvc