Redis 缓存实战:Spring Cache + Redis 整合
Spring Boot 整合 Redis 的完整缓存 Demo,涵盖缓存配置、@Cacheable/@CacheEvict 注解使用和多种序列化方式。
Spring Cache + Redis 缓存实战
从本地缓存到分布式缓存
Spring 的缓存抽象(spring-boot-starter-cache)最强大的特性是:切换缓存实现不需要改业务代码。
<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:
cache:
type: redis # 从 ConcurrentHashMap 切换到 Redis
业务代码中的 @Cacheable 注解不需要任何修改。这就是 AOP 的威力。
@Cacheable 原理
@Cacheable 基于 AOP 拦截方法调用:
调用方 → 代理对象 → 检查缓存中是否有 key
├─ 有 → 直接返回缓存值(跳过方法执行)
└─ 无 → 执行目标方法 → 将返回值存入缓存
@Cacheable(value = "products", key = "#id")
public Product findById(Long id) {
// 模拟耗时查询
TimeUnit.MILLISECONDS.sleep(200);
return productRepository.findById(id).orElse(null);
}
SpEL 表达式定义 Key
Spring 使用 SpEL(Spring Expression Language)定义缓存 key:
// 单个参数
@Cacheable(key = "#id")
// 多个参数组合
@Cacheable(key = "#category + ':' + #page")
// 对象属性
@Cacheable(key = "#user.id")
// 条件缓存:结果为空时不缓存
@Cacheable(key = "#id", unless = "#result == null")
其他注解
@CachePut:更新缓存
@CachePut(value = "products", key = "#product.id")
public Product update(Product product) {
return productRepository.save(product);
}
与 @Cacheable 的区别:方法始终执行,执行结果用来更新缓存。
@CacheEvict:清除缓存
@CacheEvict(value = "products", key = "#id")
public void delete(Long id) {
productRepository.deleteById(id);
}
@Cacheable 的局限性
注解缓存适合 80% 的简单场景。但遇到以下情况时需要直接使用 RedisTemplate:
- 缓存同步锁:高并发下防止缓存击穿
- 批量淘汰:按前缀匹配删除多个 key
- 自定义过期时间:不同缓存不同的 TTL
- 缓存预热:启动时加载热点数据
@Service
public class ProductService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public Product findByIdWithLock(Long id) {
String key = "products:" + id;
// 先查缓存
Product cached = (Product) redisTemplate.opsForValue().get(key);
if (cached != null) return cached;
// 缓存未命中:加锁防止并发击穿
String lockKey = "lock:product:" + id;
if (redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS)) {
try {
Product product = productRepository.findById(id).orElse(null);
if (product != null) {
redisTemplate.opsForValue().set(key, product, 30, TimeUnit.MINUTES);
}
return product;
} finally {
redisTemplate.delete(lockKey);
}
} else {
// 其他线程在加载,短暂等待后重试
TimeUnit.MILLISECONDS.sleep(50);
return findByIdWithLock(id);
}
}
}
生产配置
spring:
redis:
host: 192.168.8.133
port: 6379
password: redis123
timeout: 3000ms
lettuce:
pool:
max-active: 16
max-idle: 8
min-idle: 4
注意连接池配置——在 Java_Online 的生产调优中,Redis 连接池大小也是需要关注的参数。连接池过小导致获取连接等待,过大会浪费资源。
总结
Spring 的缓存注解提供了声明式缓存的最佳体验。记住一个原则:读操作 @Cacheable,写操作 @CacheEvict 或 @CachePut。80% 的场景用注解解决,复杂场景用 RedisTemplate。