Spring Cache 操作详解

Spring Cache 操作详解

在现代的应用程序中,缓存是提升性能、减少数据源压力的重要手段之一。Spring Boot 提供了一个简洁的缓存抽象框架——spring-boot-starter-cache,而 Redis 作为一个高性能的内存数据库,是缓存实现的理想选择。本文将介绍如何结合 Spring Boot Starter Cache 与 Redis 来实现缓存管理。

1. 添加依赖

首先,在 Spring Boot 项目中集成 Redis 和缓存功能,需要在 pom.xml 中添加相关依赖。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

spring-boot-starter-cache 提供了 Spring 的缓存抽象,而 spring-boot-starter-data-redis 使我们能够使用 Redis 作为缓存存储。

2. 配置 Redis 连接

application.ymlapplication.properties 中配置 Redis 连接信息。以下是使用 application.yml 的示例:

spring:
  redis:
    host: localhost
    port: 6379
    password: yourpassword  # 如果没有密码可以省略
    lettuce:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0
  cache:
    type: redis  # 将缓存类型指定为 redis

以上配置告诉 Spring Boot 使用 Redis 作为缓存存储,并配置了 Redis 连接的主机、端口和连接池。

3. 启用缓存功能

在主启动类或配置类中,添加 @EnableCaching 注解来开启 Spring Boot 的缓存支持:

import org.springframework.cache.annotation.EnableCaching;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@EnableCaching
public class CacheApplication {
    public static void main(String[] args) {
        SpringApplication.run(CacheApplication.class, args);
    }
}

@EnableCaching 将启用缓存机制,允许在应用程序中使用缓存注解。

4. 使用缓存注解

Spring 提供了一系列注解用于缓存操作,其中最常用的是 @Cacheable@CachePut@CacheEvict。下面通过示例介绍如何使用这些注解。

@Cacheable

@Cacheable 注解用于将方法的返回值缓存起来,之后调用相同参数时将直接返回缓存的内容,而不是再次执行方法。

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    @Cacheable(value = "users", key = "#id")
    public User getUserById(Long id) {
        // 模拟耗时操作
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return new User(id, "John Doe");
    }
}

上面的代码中,getUserById 方法被 @Cacheable 注解标注,缓存名称为 users,缓存的键为方法参数 id。当第一次调用该方法时,结果将被缓存起来,之后使用相同的 id 调用时将直接从缓存中获取数据,而不会再次执行方法逻辑。

@CachePut

@CachePut 用于更新缓存,不会跳过方法执行。它确保方法执行并将结果存入缓存。

@CachePut(value = "users", key = "#user.id")
public User updateUser(User user) {
    // 更新用户信息
    return userRepository.save(user);
}

即使缓存中已经存在相应的 user@CachePut 也会执行方法并更新缓存。

@CacheEvict

@CacheEvict 用于移除缓存中的某些数据或清空整个缓存。

@CacheEvict(value = "users", key = "#id")
public void deleteUser(Long id) {
    userRepository.deleteById(id);
}

在这个例子中,当用户被删除时,缓存中的数据也会被移除。

5. 缓存注解与 Redis Key-Value 映射

在使用 Spring Boot 的缓存注解时,缓存的具体存储介质可以是内存、数据库或像 Redis 这样的缓存系统。结合 Redis 时,Spring Boot 的缓存注解将以键值对的形式将缓存数据存入 Redis。以下是这些注解与 Redis 中的 key-value 映射过程的详细说明。

5.1 与 Redis 的映射

当使用 @Cacheable 注解时,方法返回的值会存储在 Redis 中,键值对的形式如下:

  • Key(键): Spring Cache 会根据 valuekey 属性构造 Redis 的键。value 表示缓存的名称,也可以视为 Redis 中的命名空间或前缀,key 通常由方法参数或自定义的 SpEL 表达式决定。默认情况下,key 是参数的字符串形式。

  • Value(值): 方法返回的对象会被序列化后存入 Redis。默认情况下,Spring Boot 使用 JdkSerializationRedisSerializer,但可以通过配置来指定不同的序列化方式,如 JSON 序列化。

例如,在如下的 @Cacheable 注解使用中:

@Cacheable(value = "users", key = "#id")
public User getUserById(Long id) {
    // 模拟耗时操作
    return new User(id, "John Doe");
}
  • Redis Key: users::1 (假设 id=1
    • users 是缓存名称 value1 是参数 id 的值。
  • Redis Value: User 对象的序列化形式。

通过 Redis 客户端可以看到类似的键值对:

> GET users::1
"{\"id\":1,\"name\":\"John Doe\"}"

5.2 自定义 Redis Key 和 Value 的序列化

默认情况下,Spring Boot 使用 JdkSerializationRedisSerializer 序列化对象为字节流存储在 Redis 中。如果我们希望以更人性化的方式查看 Redis 中的缓存内容,可以通过配置来使用 JSON 序列化。例如:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;

@Configuration
public class RedisConfig {

    @Bean
    public RedisCacheConfiguration cacheConfiguration() {
        return RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(10))  // 设置缓存过期时间
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))  // 使用字符串序列化key
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));  // 使用JSON序列化value
    }
}

在上述配置中,键将使用 StringRedisSerializer 序列化,值将使用 GenericJackson2JsonRedisSerializer 以 JSON 格式存储。

例如,对于上述的 @Cacheable 注解,如果使用该配置,Redis 中的键值对将如下显示:

  • Redis Key: users::1
  • Redis Value: {"id":1,"name":"John Doe"}

通过 Redis 客户端查看:

> GET users::1
"{\"id\":1,\"name\":\"John Doe\"}"

6. Redis 缓存配置

Spring Boot 提供了默认的 Redis 缓存管理器,但我们可以通过自定义缓存配置来实现更多功能,例如设置缓存过期时间。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;

@Configuration
public class RedisConfig {

    @Bean
    public RedisCacheConfiguration cacheConfiguration() {
        return RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(10))  // 设置缓存过期时间为10分钟
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
    }
}

通过自定义 RedisCacheConfiguration,可以控制缓存的过期时间、序列化方式等。

7. 效果演示

当你第一次调用 getUserById 方法时,应用会执行耗时操作(如访问数据库)。而当第二次调用相同方法时,结果将直接从 Redis 中获取,显著减少响应时间。

可以通过 Redis 客户端或 Redis 图形化工具(如 RedisInsight)来查看缓存的内容。

8. @Cacheable 高级用法

@Cacheable注解是Spring Cache中最常用的注解之一,它用于将方法的返回值缓存起来,以便后续调用时直接从缓存中获取,而不是再次执行方法。

指定缓存的Key

默认情况下,@Cacheable会使用方法参数作为缓存的Key。可以通过key属性指定自定义的Key:

@Cacheable(value = "exampleCache", key = "#param")
public String getData(String param) {
    // ...
}

条件缓存

可以使用condition属性指定条件,满足条件时才进行缓存:

@Cacheable(value = "exampleCache", condition = "#param.length() > 3")
public String getData(String param) {
    // ...
}

缓存同步

在并发环境中,可以使用sync属性启用缓存同步,防止缓存击穿:

@Cacheable(value = "exampleCache", sync = true)
public String getData(String param) {
    // 模拟耗时操作
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return "Data for " + param;
}

当多个线程同时请求缓存中没有的数据时,只有一个线程会执行方法,其他线程等待结果。这种机制能够有效避免多个线程同时执行相同的方法,从而减少资源消耗,提高系统性能。

例如,当大量并发请求同时查询某个缓存中没有的数据时,如果没有同步机制,每个请求都会触发一次方法执行,这不仅浪费资源,还可能导致数据库或其他底层服务过载。而启用sync属性后,只有一个请求会执行方法,其他请求等待结果,执行完毕后,结果会被缓存,后续请求直接从缓存中获取数据。

自定义的Key生成器keyGenerator

keyGenerator参数用于指定一个自定义的Key生成器,覆盖默认的Key生成方式。需要实现org.springframework.cache.interceptor.KeyGenerator接口,并在配置类或服务类中进行注册。

自定义Key生成器示例:

import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Arrays;

@Component("customKeyGenerator")
public class CustomKeyGenerator implements KeyGenerator {
    @Override
    public Object generate(Object target, Method method, Object... params) {
        return method.getName() + "_" + Arrays.toString(params);
    }
}

在使用@Cacheable时指定自定义Key生成器:

@Cacheable(value = "exampleCache", keyGenerator = "customKeyGenerator")
public String getData(String param) {
    // ...
}

详细介绍 cacheManager

cacheManager参数用于指定一个自定义的缓存管理器。如果没有指定,使用默认的缓存管理器。缓存管理器负责管理应用程序中的所有缓存实例。

自定义缓存管理器示例:

import org.springframework.cache.CacheManager;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class CacheConfig {

    @Bean
    public CacheManager customCacheManager() {
        return new ConcurrentMapCacheManager("exampleCache");
    }
}

在使用@Cacheable时指定自定义缓存管理器:

@Cacheable(value = "exampleCache", cacheManager = "customCacheManager")
public String getData(String param) {
    // ...
}

自定义的缓存解析器cacheResolver

cacheResolver参数用于指定一个自定义的缓存解析器。需要实现org.springframework.cache.interceptor.CacheResolver接口,缓存解析器负责根据某些逻辑来解析和提供缓存实例。

自定义缓存解析器示例:

import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.Collections;

@Component("customCacheResolver")
public class CustomCacheResolver implements CacheResolver {

    private final CacheManager cacheManager;

    public CustomCacheResolver(CacheManager cacheManager) {
        this.cacheManager = cacheManager;
    }

    @Override
    public Collection<? extends Cache> resolveCaches(org.springframework.cache.interceptor.CacheOperationInvocationContext<?> context) {
        // 自定义缓存解析逻辑
        return Collections.singleton(cacheManager.getCache("exampleCache"));
    }
}

在使用@Cacheable时指定自定义缓存解析器:

@Cacheable(value = "exampleCache", cacheResolver = "customCacheResolver")
public String getData(String param) {
    // ...
}

unless

unless参数是一个SpEL表达式,只有当表达式为false时才缓存方法的返回值。它的优先级高于condition,可以用于在某些条件下禁用缓存。

例如,可以在结果长度大于10时不缓存:

@Cacheable(value = "exampleCache", unless = "#result.length() > 10")
public String getData(String param) {
    // ...
}

通过unless参数,可以在不改变方法逻辑的情况下,灵活控制缓存行为,避免不必要的缓存操作,提高系统性能。

9. 缓存一致性问题

在使用缓存时,一个常见的问题是缓存一致性问题。当数据源(如数据库)中的数据发生变化时,缓存中的数据可能不会及时更新,导致缓存中的数据与实际数据不一致。为了解决这个问题,可以使用以下几种策略:

  1. 缓存失效策略:在更新或删除数据时,同时让相关缓存失效。
  2. 缓存刷新:定期刷新缓存,确保缓存中的数据是最新的。
  3. 消息队列:使用消息队列,在数据变化时通知缓存进行更新。

例如,在数据更新时手动清除缓存:

import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Service;

@Service
public class ExampleService {

    @CacheEvict(value = "exampleCache", key = "#param")
    public void updateData(String param, String newData) {
        // 更新数据库中的数据
    }
}

参考链接

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

黑风风

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

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

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

打赏作者

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

抵扣说明:

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

余额充值