SpringBoot 整合 Spring Cache + Redis 实现声明式缓存实战
【摘要】 在后端开发中,缓存是提升接口吞吐量、降低数据库压力最常用的手段。Spring Cache 提供了一套注解化的缓存抽象,不用手动编写 Redis 操作代码,通过少量注解即可完成缓存新增、查询、更新、删除逻辑。本文基于 SpringBoot 整合 Redis 实现 Spring Cache,演示 @Cacheable、@CachePut、@CacheEvict、@Caching 注解用法,附带完整代码
前言
很多项目里缓存代码都是硬编码:查库前先查 Redis,没有数据再查数据库,查到数据后写入缓存,更新、删除数据时手动删缓存。大量重复的 Redis 模板代码,可读性差、容易漏写。
Spring Cache 是 Spring 框架提供的缓存抽象层,底层可以对接 Redis、Caffeine、Ehcache 等多种缓存实现。它采用 AOP 机制,通过注解对方法进行增强,自动完成缓存逻辑,业务代码只需要关注数据库操作,非常适合快速落地缓存能力。
本文选用 Redis 作为缓存介质,搭建一套可直接投入项目使用的声明式缓存方案。
一、环境依赖
Maven pom.xml 引入依赖
<!-- Spring Cache + Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 对象序列化需要 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
二、yml 配置文件
spring:
redis:
host: 127.0.0.1
port: 6379
password:
database: 0
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 2
max-wait: 1000ms
cache:
type: redis
redis:
# 缓存过期时间,单位毫秒
time-to-live: 3600000
# 是否缓存null值,防止缓存穿透
cache-null-values: true
# key前缀,方便区分不同业务缓存
key-prefix: "cache:user:"
三、Redis 序列化配置类
默认使用 JdkSerializationRedisSerializer,key 和 value 会出现乱码,自定义序列化规则,使用 Jackson JSON 序列化:
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
@Configuration
@EnableCaching // 开启SpringCache注解支持
public class RedisCacheConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(mapper);
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer))
.disableCachingNullValues();
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
}
}
重点:
@EnableCaching必须加上,否则所有缓存注解不会生效。
四、实体类 User
import lombok.Data;
import java.io.Serializable;
@Data
public class User implements Serializable {
private Long id;
private String username;
private String phone;
private Integer age;
}
五、Service 层缓存注解实战
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
public class UserService {
/**
* @Cacheable:查询缓存,缓存命中直接返回,不执行方法;未命中执行方法,结果存入缓存
* value:缓存名称
* key:缓存key,支持SpEL表达式
*/
@Cacheable(value = "user", key = "#id")
public User getById(Long id) {
System.out.println("===== 访问数据库查询用户 =====");
// 模拟数据库查询
User user = new User();
user.setId(id);
user.setUsername("test-" + id);
user.setPhone("13800138000");
user.setAge(22);
return user;
}
/**
* @CachePut:执行方法,并且把返回结果更新到缓存,用于更新操作
* 无论缓存是否存在,方法一定会执行
*/
@CachePut(value = "user", key = "#user.id")
public User updateUser(User user) {
System.out.println("===== 更新数据库用户 =====");
// 模拟数据库更新
return user;
}
/**
* @CacheEvict:清除缓存
* allEntries=true:清空当前value下全部缓存
*/
@CacheEvict(value = "user", key = "#id")
public void deleteUser(Long id) {
System.out.println("===== 删除数据库用户 =====");
}
}
六、Controller 测试接口
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
private UserService userService;
@GetMapping("/get")
public User get(Long id) {
return userService.getById(id);
}
@PostMapping("/update")
public User update(User user) {
return userService.updateUser(user);
}
@GetMapping("/delete")
public void delete(Long id) {
userService.deleteUser(id);
}
}
七、核心注解说明
| 注解 | 作用 | 执行特点 |
|---|---|---|
| @Cacheable | 查询缓存 | 缓存有数据 → 直接返回,方法不执行 |
| @CachePut | 更新缓存 | 方法一定执行,返回值写入缓存 |
| @CacheEvict | 删除缓存 | 清除指定key或者全部缓存 |
| @Caching | 组合注解 | 同时配置多个缓存操作 |
示例:@Caching 组合使用场景
@Caching(
put = {@CachePut(value = "user", key = "#user.id")},
evict = {@CacheEvict(value = "userList", allEntries = true)}
)
public User addUser(User user){
return user;
}
新增用户时更新单条缓存、同时清空用户列表缓存。
八、生产避坑点
- 注解失效问题
Spring Cache 基于 AOP,同一个类内方法互相调用,注解不会生效。比如本类A方法调用加了@Cacheable的B方法,缓存逻辑失效,要拆分到不同Service或者通过ApplicationContext获取代理对象。 - 缓存key冲突
不同业务模块尽量分开value,统一增加前缀,避免key覆盖。 - 缓存雪崩
统一过期时间会导致大量key同时失效,可以在TTL基础上增加随机偏移量。 - 事务和缓存顺序
默认先更新缓存再提交事务,高并发下会出现脏读。优先推荐:先操作数据库,事务提交成功后再操作缓存。Spring Cache 默认的机制需要结合事务仔细评估。 - null值缓存
开启cache-null-values=true可以拦截大量不存在ID的请求,抵御简单缓存穿透,但不能替代布隆过滤器。
九、总结
Spring Cache 把缓存逻辑抽象成注解,大幅减少样板代码,开发效率很高。它的优势是解耦,底层缓存中间件可以随时切换;缺点是抽象层屏蔽了很多Redis原生能力,复杂场景(分布式锁、Lua脚本、流式操作)仍然需要直接使用 RedisTemplate。
日常业务单表查询、基础列表缓存场景,优先选择 Spring Cache;高并发复杂缓存控制场景,再手写 Redis 原生代码。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)