SSM之Spring注解式缓存Redis

目录

一、Redis整合

1.1 ssm整合redis

1.2 实现步骤(Spring-redis.xml )

1.2.1 注册redis.properties文件

1.2.2 配置Redis连接池

1.2.3 连接工厂配置

1.2.4 配置序列化器

1.2.5 配置缓存管理器

1.2.6 配置Redis的key生成策略

1.2.7 启用缓存注解功能

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

2.1 什么是Redis注解式开发

2.2 使用Redis注解式开发的好处与优势

2.3 Redis注解式开发应用

2.3.1 @Cacheable

2.3.2 @CachePut

2.3.3 @CacheEvict

2.4 总结

三、redis的击穿穿透雪崩


一、Redis整合

1.1 ssm整合redis

redis是 nosql数据库,mysql是sql数据库。

ssm整合redis可以参考mysql整合mybatis。

1.2 实现步骤(Spring-redis.xml )

实现步骤如下:

  1. SSM(Spring + Spring MVC + MyBatis)整合Redis的步骤通常包括以下几个关键步骤:

  2. 注册redis.properties文件:首先,您需要在项目中注册一个配置文件(通常是.properties文件),其中包含Redis服务器的连接信息,例如主机名、端口号、密码等。这个配置文件用来告诉应用程序如何连接到Redis服务器。

  3. 配置Redis连接池:Redis连接池是用来管理与Redis服务器的连接的。您需要在Spring配置文件中定义一个Redis连接池,以确保有效地管理连接资源,以及配置连接池的一些参数,如最大连接数、最小空闲连接数、连接超时等。

  4. 连接工厂配置:您需要配置Redis连接工厂,通常使用Spring的JedisConnectionFactoryLettuceConnectionFactory。连接工厂负责创建Redis连接,您需要将其与连接池结合使用,以确保连接的创建和管理。

  5. 配置序列化器:Redis是一个键值存储系统,因此您需要定义如何将对象序列化为Redis键和值。通常,您会配置一个序列化器,以便将Java对象序列化为字芍೴序列化,以及从Redis中获取值并将其反序列化为Java对象。

  6. 配置Redis的键生成策略:在使用Redis时,您通常需要定义一种策略来生成唯一的键,以便将数据存储到Redis中。这可能涉及到命名规范、前缀、后缀等,以确保键的一致性和唯一性。

这些步骤可以帮助您在SSM项目中成功整合Redis。确保您在Spring配置文件中正确定义这些组件,并将它们与您的应用程序逻辑集成在一起,以充分利用Redis的功能,如缓存、分布式锁等。

1.2.1 注册redis.properties文件

引入redis配置文件


    

1.2.2 配置Redis连接池

其中使用了JedisPoolConfig类来配置Jedis连接池的各种属性,这些属性允许您对Redis连接池的行为进行详细的配置,以满足您的应用程序的需求。通过调整这些属性,您可以控制连接的生命周期、连接池的性能和资源使用。


    
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
    

代码解释:

  1. maxIdle: 连接池中的最大空闲连接数。这是连接池允许的最大空闲连接数,当连接池中的连接数超过这个值时,多余的连接会被释放。

  2. maxTotal: 连接池的最大数据库连接数。这是连接池允许的最大连接数,包括空闲连接和正在使用的连接。当连接池中的连接总数达到这个值时,后续的连接请求会被阻塞。

  3. maxWaitMillis: 最大建立连接等待时间。如果连接池中的连接数达到maxTotal,新的连接请求会被阻塞,直到有连接可用,但最长等待时间不会超过此值。

  4. minEvictableIdleTimeMillis: 逐出连接的最小空闲时间。连接在连接池中的空闲时间达到此值后,连接可能会被逐出(关闭)。

  5. numTestsPerEvictionRun: 每次逐出检查时逐出的最大数目。这是在执行连接池逐出操作时,每次检查并逐出的最大连接数。如果为负数,表示逐出所有满足条件的连接。

  6. timeBetweenEvictionRunsMillis: 逐出扫描的时间间隔。连接池会定期执行逐出操作,以检查并逐出空闲连接。此属性定义了逐出线程执行的时间间隔(以毫秒为单位),如果为负数,则不运行逐出线程。

  7. testOnBorrow: 是否在从池中取出连接前进行检验。如果设置为true,则在从连接池中获取连接之前,会执行一次连接的可用性检验。如果检验失败,连接将被从池中移除,然后尝试获取另一个连接。

  8. testWhileIdle: 在空闲时检查连接的有效性。如果设置为true,连接池会定期检查空闲连接的有效性。如果发现连接无效,它将被从池中移除。

redis.properties代码如下:

redis.hostName=localhost
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

1.2.3 连接工厂配置

这段XML配置代码是用于配置Spring应用程序中连接到Redis数据库的连接工厂。如下:

    
    
        
        
        
        
        
        
        
        
        
    

代码总结: 

  • 这段配置代码创建了一个名为 connectionFactory 的Spring Bean,该Bean使用JedisConnectionFactory来连接到Redis数据库。
  • 它引用了另一个Bean,名为 poolConfig,用于配置连接池。
  • 此外,它从属性文件中获取Redis服务器的IP地址、端口号、密码和客户端超时时间,以便动态配置Redis连接的相关属性。
  • 这种方式使得在不修改代码的情况下,能够灵活地配置和管理与Redis的连接。

1.2.4 配置序列化器

这些配置非常重要,因为它们确保将数据正确序列化和反序列化,以便与 Redis 进行交互。例如,通过使用 JSON 序列化器,您可以在 Redis 中存储和检索复杂的数据结构,而不仅仅是字符串。如果不配置适当的序列化器,Redis 可能会尝试将对象存储为字符串,从而导致错误.

代码如下:


        
        
        
            
        
        
            
        
        
            
        
        
            
        
        
        
    

代码解释:

  • keySerializer:这是用于序列化 Redis 键的序列化器。在您的配置中,它被设置为 StringRedisSerializer,这意味着 Redis 键将以字符串形式进行存储。

  • valueSerializer:这是用于序列化 Redis 值的序列化器。在您的配置中,它被设置为 GenericJackson2JsonRedisSerializer,这意味着 Redis 值将以 JSON 格式进行存储。

  • hashKeySerializer:这是用于序列化 Redis 哈希表键的序列化器。在您的配置中,它也被设置为 StringRedisSerializer,这意味着哈希表的键将以字符串形式进行存储。

  • hashValueSerializer:这是用于序列化 Redis 哈希表值的序列化器。在您的配置中,它被设置为 GenericJackson2JsonRedisSerializer,这意味着哈希表的值将以 JSON 格式进行存储。

  • enableTransactionSupport:此属性用于指定是否启用事务支持。将其设置为 true 可以启用 Redis 事务支持,允许您执行多个操作作为一个事务。

SpringContext.xml中添加spring-redis.xml的注意事项: 

  • 注意1:当Spring-context.xml中需要注册多个.properties结尾的配置文件,那么不能在spring-*.xml添加注册。
  • 注意2:resources的配置必须要涵盖读取.properties结尾的文件。
  • 注意3:redisTemplate的使用,可以参照jdbcTemplate、amqpTemplae、rabbitMQtemplate...

1.2.5 配置缓存管理器

创建了一个RedisCacheManager,它会使用redisTemplate来与Redis交互,定义了默认的缓存过期时间,启用了缓存前缀,并配置了前缀的值。这允许您在Spring应用程序中使用Redis作为缓存存储,并根据需要对缓存进行配置。如下:


    
        
        
        
        
        
        
        
            
                
            
        
    

以下是对每个属性的解释:

  1. redisCacheManager:这是RedisCacheManager的bean的标识符,您可以在应用程序中使用它来管理缓存。

  2. class="org.springframework.data.redis.cache.RedisCacheManager":指定了要实例化的RedisCacheManager类。

  3. constructor-arg name="redisOperations" ref="redisTemplate":这里注入了一个名为redisTemplate的bean,它是用于与Redis进行交互的RedisTemplate实例。RedisCacheManager将使用这个RedisTemplate来执行缓存操作。

  4. defaultExpiration:这是Redis缓存数据的默认过期时间,单位为秒。如果不设置缓存的特定过期时间,将使用这个默认值。

  5. usePrefix:这是一个布尔值,指定是否使用缓存前缀。如果设置为true,则会使用缓存前缀,这有助于在Redis中区分不同的缓存区域。

  6. cachePrefix:这是用于配置缓存前缀的属性。在这个示例中,DefaultRedisCachePrefix被配置为前缀,前缀的值是"-cache-"。这意味着缓存的键会附加这个前缀,以区分不同的缓存。

1.2.6 配置Redis的key生成策略

定义自定义的缓存键生成规则.

spring-redis.xml:


    

CacheKeyGenerator:

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;
    }
}

1.2.7 启用缓存注解功能


    

这段XML配置代码是用于启用Spring框架中的缓存注解功能的。它执行以下操作:

  1. :这是一个XML命名空间元素,通常用于配置Spring的缓存注解功能。它告诉Spring框架要启用缓存注解,允许在代码中使用@Cacheable@CachePut@CacheEvict等注解来管理缓存。

  2. cache-manager="redisCacheManager":这个属性指定了要用于缓存管理的CacheManager的bean的标识符。在这个示例中,它指定了之前定义的名为"redisCacheManager"的RedisCacheManager bean。这意味着Spring将使用该RedisCacheManager来管理缓存。

  3. key-generator="cacheKeyGenerator":这个属性指定了要用于生成缓存键的自定义键生成器(KeyGenerator)的bean的标识符。缓存键生成器用于生成唯一的键,以标识缓存中的不同数据项。在这里,它指定了名为"cacheKeyGenerator"的键生成器。您可以在其他地方定义和配置这个键生成器的bean。

总之,这段配置代码启用了Spring框架的缓存注解功能,并配置了CacheManager和缓存键生成器,以便在应用程序中使用缓存注解来管理缓存操作。RedisCacheManager将用于管理缓存,而cacheKeyGenerator将用于生成缓存键。这使得您可以轻松地在应用程序中利用缓存来提高性能和效率。

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

2.1 什么是Redis注解式开发

在Java应用程序中,Redis注解式开发通常指的是使用注解来集成和操作Redis缓存的开发方式。这允许开发者更轻松地与Redis数据库进行交互,而不需要编写大量的冗长代码来处理与缓存相关的操作。通常,Spring框架和一些相关的库提供了这种注解式开发的支持。

以下是一些常见的Redis注解和它们的作用:

  1. @Cacheable:这个注解用于标记一个方法的返回值应该被缓存。当方法被调用时,如果缓存中没有相应的数据,则方法的返回值将被缓存,以便以后快速获取。

  2. @CachePut:这个注解用于更新或新增缓存数据。它会强制方法执行,并将结果存储在缓存中,通常在方法执行后。

  3. @CacheEvict:这个注解用于从缓存中删除数据。它可以用于标记一个方法,以便在调用该方法时将缓存中的数据清除。

  4. @Caching:这个注解允许组合多个缓存注解以便在一个方法上执行多个缓存操作。

  5. @CacheConfig:这个注解可以在类级别上使用,用于配置缓存的默认行为,以减少重复的缓存注解配置。

2.2 使用Redis注解式开发的好处与优势

使用Redis注解式开发提供了多个好处和优势,其中包括:

  1. 简化的缓存管理:通过注解,可以更轻松地管理缓存而无需手动编写大量的缓存代码。这降低了开发的复杂性,使得缓存配置和使用更为简单和直观。

  2. 提高代码可读性:使用注解可以清晰地标记出哪些方法需要进行缓存操作,从而增强了代码的可读性。开发者可以轻松地辨认出在哪些地方启用了缓存。

  3. 降低重复操作:通过@Cacheable注解标记的方法会在被调用时检查缓存中是否已存在相同输入参数的结果。这有助于避免多次执行相同的操作,节省了时间和资源。

  4. 提升应用程序性能:将数据缓存在Redis中可以显著提高应用程序的性能。由于Redis是内存数据库,读取速度非常快,因此可以加速数据检索,减少了与慢速数据库或其他外部资源的交互时间。

  5. 降低数据库负载:缓存常用数据项可以减少数据库的负担。频繁访问的数据可以从缓存中快速获取,减轻了数据库的压力,提高了数据库的响应速度。

  6. 分布式缓存支持:Redis支持分布式缓存,能够用于多个应用服务器间共享数据,保证了一致性和可扩展性。

  7. 定制缓存策略:Redis注解式开发允许开发者定义不同的缓存策略,如缓存的过期时间、条件等。这种灵活性有助于更好地满足特定应用需求。

  8. 适用于多种应用场景:适用于各种应用,包括Web应用、分布式系统、微服务架构等。通过使用Redis进行缓存,能够提高应用程序的性能和可伸缩性。

综上所述,Redis注解式开发简化了缓存管理,并通过简明的注解提供了一种便捷的方法,可以改善应用程序的性能、降低数据库压力,以及减少与外部数据源的频繁交互。

2.3 Redis注解式开发应用

2.3.1 @Cacheable

@Cacheable方法简介:

@Cacheable是Spring Framework中的一个注解,用于声明方法的返回值是可以被缓存的。这意味着方法的返回值将被缓存在内存中,以便之后的调用可以直接返回缓存的结果,而无需再次执行该方法。

@Cacheable方法作用:

  1. 提高性能:使用@Cacheable可以减少方法的执行次数,特别是在方法执行开销较大或需要频繁调用的情况下。通过将结果缓存在内存中,可以避免每次请求都重新执行方法,从而提高系统的响应速度和性能。

  2. 减少数据库或外部服务的负载:通过缓存方法的返回值,可以减少对数据库或外部服务的频繁访问。这对于需要频繁访问数据库或调用外部服务的情况下尤其有用,可以降低这些资源的负载压力。

  3. 灵活的缓存策略@Cacheable注解允许开发人员根据具体业务需求来设置缓存的名称、过期时间、条件等,从而实现灵活的缓存策略。通过设置不同的缓存参数,可以控制缓存的存储位置、缓存项的失效时间、缓存项的清除策略等。

  4. 简化缓存逻辑:通过使用@Cacheable注解,可以在不修改原始方法逻辑的情况下,轻松地将方法的返回值添加到缓存中。这简化了缓存管理的复杂性,使得开发人员能够更专注于业务逻辑的实现。

这个注解的作用是将方法的返回值缓存起来,以避免重复计算或执行相同的操作,提高应用程序的性能。同时,通过使用SpEL表达式生成键,你可以动态地控制缓存项的生成和存储,以适应不同的业务需求。

基本用法示例:

@Cacheable(value = "clz" ,key = "'cid'+#cid")

用法内属性解释: 

  • value: 用于指定缓存的名称,你可以在配置文件中定义不同的缓存管理器,这里的"value"指定了要使用的缓存。

  • key属性:使用SpEL表达式,"'cid'+#cid"生成缓存的键。这个键将由方法的cid参数的值动态生成。如果方法被调用时传入不同的cid值,将会生成不同的缓存键,因此不同的cid值将会对应不同的缓存项。这允许你在不同的上下文中存储和获取缓存数据。

2.3.2 @CachePut

@CachePut简介:

@CachePut 是 Spring Framework 中的一个注解,用于将方法的返回值存储到缓存中,通常用于更新缓存中的数据。

@CachePut方法作用:

  1. 更新缓存数据@CachePut 用于强制将方法的返回值存储到缓存中,无论缓存中是否已存在相同的键。这对于确保缓存中的数据是最新的非常有用,特别是在需要手动更新缓存数据时。

  2. 动态生成缓存键@CachePut 允许你使用 Spring Expression Language (SpEL) 表达式来动态生成缓存项的键。这使得你可以根据方法的参数或其他条件来生成缓存键,以确保不同的缓存项具有不同的键。

  3. 条件性更新@CachePut 支持 conditionunless 属性,可以根据条件来控制是否执行缓存更新操作。这使你可以在特定条件下才更新缓存,从而更加灵活地管理缓存。

  4. 清空指定缓存项:通过配置 allEntries 属性为 true,可以清空与指定缓存相关的所有缓存项,而不仅仅是更新一个特定的缓存项。

  5. 控制更新时机:使用 beforeInvocation 属性,你可以控制是在方法执行之前还是方法执行成功后触发缓存更新。

注:

  • @CachePut 注解用于更新缓存中的数据,不同于 @Cacheable,它会执行方法体,并将方法返回的值存入缓存中,以确保缓存中的数据是最新的
  • 如果缓存中的数据不存在,@CachePut将创建一个新的缓存项。

基本用法示例:

@CachePut(value = "xx",key = "'cid:'+#cid")

用法内属性解释: 

  • @CachePut注解用于将方法的返回值存储到缓存中,通常用于更新缓存中的数据。
  • @Cacheable不同,它不会检查缓存中是否已存在相同的键,而是直接将方法的返回值存入缓存,以确保缓存中的数据是最新的。
  • 这对于更新缓存项非常有用,以确保缓存中的数据与后端数据保持同步。

2.3.3 @CacheEvict

@CacheEvict方法简介:

@CacheEvict 是 Spring Framework 中的一个注解,用于从缓存中移除指定的缓存项或清空整个缓存。

@CacheEvict方法作用:

  1. 清空指定缓存项:你可以使用 @CacheEvict 来清空一个或多个特定缓存中的缓存项,以确保缓存中的数据保持最新或满足特定条件时清除缓存。

  2. 条件性清除@CacheEvict 支持 conditionunless 属性,可以根据条件来控制是否执行缓存清除操作。这允许你在满足特定条件时才清除缓存,从而更加灵活地管理缓存。

  3. 清空整个缓存:通过设置 allEntries 属性为 true,你可以清空整个缓存,而不仅仅是清除特定的缓存项。这对于需要在某些情况下全局清空缓存的场景非常有用。

  4. 控制清除时机:使用 beforeInvocation 属性,你可以控制是在方法执行之前还是方法执行成功后触发缓存清除操作。

  5. 清除缓存的多个条目:可以通过 key 属性设置一个缓存键表达式,以删除匹配特定键模式的缓存项。这允许你按一定的模式来删除缓存项。

@CacheEvict 注解的主要作用是从缓存中移除指定的缓存项或清空整个缓存,以确保缓存中的数据保持最新或根据条件来清除缓存。它提供了多个属性,使你可以根据需要来配置清除缓存的方式和时机,以满足特定的业务需求。

基本用法示例:

@CacheEvict(value = "xx",key = "'cid:'+#cid",allEntries = true)

用法内属性解释:

  • value 属性:设置为 "xx",表示要清除名为 "xx" 的缓存。通常,你需要在配置中定义相应的缓存管理器,以确保它与这个缓存名称关联。

  • key 属性:使用 SpEL 表达式 "'cid:'+#cid" 来生成缓存项的键。这个键将由方法的 cid 参数值动态生成,前缀为 "cid:"。这将导致匹配 "cid:" 后跟 cid 参数值的缓存项被清除。

  • allEntries 属性:设置为 true,表示清除整个缓存。如果 allEntries 设置为 true,则会忽略 key 属性,而是清除指定缓存中的所有缓存项。在这个示例中,不管 cid 参数的值如何,都会清空名为 "xx" 的缓存中的所有内容。

2.4 总结

@Cacheable@CachePut@CacheEvict 是 Spring Framework 中用于管理缓存的注解,它们有不同的作用和行为:

  1. @Cacheable

    • 作用:@Cacheable 用于声明一个方法的返回值可以被缓存,即当方法被调用时,Spring会首先检查缓存,如果缓存中存在相应的结果,就会返回缓存的值而不执行方法体。
    • 主要用途:提高性能,避免重复执行相同的方法,适用于读取操作,不用于更新数据。
    • 配置:可以指定缓存的名称、缓存键、条件等。
  2. @CachePut

    • 作用:@CachePut 用于强制将方法的返回值存储到缓存中,通常用于更新缓存中的数据。
    • 主要用途:更新缓存,将方法返回的值放入缓存,适用于写入操作。
    • 配置:可以指定缓存的名称、缓存键、条件等。
  3. @CacheEvict

    • 作用:@CacheEvict 用于从缓存中移除指定的缓存项或清空整个缓存。
    • 主要用途:清除缓存项,使缓存中的数据保持最新或根据条件来清除缓存。
    • 配置:可以指定缓存的名称、缓存键、条件、是否清空整个缓存等。

区别总结:

  • @Cacheable 用于缓存方法的返回值,避免重复执行方法,适用于读取操作。
  • @CachePut 用于将方法的返回值存储到缓存中,通常用于更新缓存中的数据,适用于写入操作。
  • @CacheEvict 用于清除缓存项,可以清空指定缓存项或整个缓存。

关于使用: 

这些注解可以根据具体的业务需求来组合使用,以实现灵活的缓存策略。例如,可以使用 @Cacheable 缓存读取操作的结果,使用 @CachePut 更新缓存项,使用 @CacheEvict 清除缓存中的数据,以满足不同的缓存需求。

三、redis的击穿穿透雪崩

当涉及到 Redis 时,"击穿"、"穿透"和"雪崩"是一些常见的问题,涉及缓存系统的高可用性和稳定性。下面我将对这三个问题进行详细解释:

  1. 击穿(Cache Breakdown)

    • 出现在热点数据失效的情况下。当一个特定的键失效并且接收到了大量的并发请求,这些请求会绕过缓存直接访问数据库,导致数据库压力骤增。这是因为缓存失效后,下一次访问时无法从缓存中获取数据,而必须去数据库中获取。
  2. 穿透(Cache Penetration)

    • 当恶意用户请求一个不存在于缓存和数据库中的键时,缓存系统无法提供数据,这些请求会直接访问数据库。这可能会导致数据库负载增加,甚至引发拒绝服务攻击。为了应对穿透问题,可以在查询不存在键时设置一个空值或者采用布隆过滤器等方法来过滤无效请求。
  3. 雪崩(Cache Avalanche)

    • 当缓存中大量的键同时失效,导致大量请求直接访问后端数据库,使得数据库压力骤增,甚至崩溃。这通常是由于缓存中的数据设置了相同的过期时间,导致在同一时间大量的键失效,引发雪崩效应。为了避免雪崩问题,可以采用随机的过期时间、添加热点数据的预加载等策略。

为了应对这些问题,可以采取以下一些解决方案:

  • 对于击穿问题,可以使用互斥锁或者设置短暂的过期时间来保护热点数据,确保缓存中有有效的数据。
  • 对于穿透问题,可以使用布隆过滤器等技术来过滤无效请求,确保缓存系统只处理有效的请求。
  • 对于雪崩问题,可以采用分布式缓存、多级缓存、缓存预热等策略,保证缓存中的数据分布均匀,避免大量数据同时失效。

综合采取以上措施可以帮助提高 Redis 缓存系统的稳定性和可用性,有效应对击穿、穿透和雪崩问题。


最后SSM之spring注解式缓存redis就到这里,祝大家在敲代码的路上一路通畅!

感谢大家的观看 !

SSM之Spring注解式缓存Redis_第1张图片

你可能感兴趣的:(缓存,spring,redis,java,后端,maven,intellij-idea)