跳到正文
Joeplover
后端开发·2026-01-28·约 3 分钟阅读

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:

  1. 缓存同步锁:高并发下防止缓存击穿
  2. 批量淘汰:按前缀匹配删除多个 key
  3. 自定义过期时间:不同缓存不同的 TTL
  4. 缓存预热:启动时加载热点数据
@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。