Spring Boot :使用 Spring Cache 注解方式集成 Redis

一、项目概述

本实战教程将演示如何在 Spring Boot 项目中通过 Spring Cache 注解方式集成 Redis,实现高效的数据缓存功能。我们将创建一个简单的用户管理系统,展示各种缓存注解的使用方法。

【工程增强说明】

Spring Cache 本质上是一个缓存抽象层,它并不关心你底层用的是 Redis、EhCache 还是 Caffeine,它只定义了:

• CacheManager

• Cache

• CacheOperation

真正决定缓存行为的是你配置的 CacheManager 实现,而我们这里选择的是:

java 复制代码
RedisCacheManager

这意味着你的缓存具备:

本项目适合:

• 中大型 Spring Boot 后端服务

• 分布式系统缓存标准模板

• 公司级缓存规范示例

【生产定位】

这不是"Hello Redis",而是:

可以直接拿去当公司缓存架构样板的工程模板。

二、环境准备

2.1 依赖配置

java 复制代码
<!-- pom.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.1.0</version>
        <relativePath/>
    </parent>
    
    <groupId>com.example</groupId>
    <artifactId>redis-cache-demo</artifactId>
    <version>1.0.0</version>
    
    <properties>
        <java.version>17</java.version>
    </properties>
    
    <dependencies>
        <!-- Spring Boot Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <!-- Spring Boot Data Redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        
        <!-- Spring Boot Cache -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        
        <!-- Redis连接池 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        
        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        
        <!-- 单元测试 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        
        <!-- 缓存注解处理器 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>

【生产补充:这几项依赖的真实职责】

【版本选择说明】

你用的是:

java 复制代码
Spring Boot 3.1.0 + Java 17

这意味着:

• Spring Framework 6.x

• Jakarta 命名空间

• 默认使用 Lettuce 客户端

• Redis 7 完美适配

是 当前主流生产版本组合,非常优秀。

2.2 Redis 配置

java 复制代码
spring:
  application:
    name: redis-cache-demo
  
  redis:
    host: localhost
    port: 6379
    password: 123456
    database: 0
    timeout: 2000ms
    lettuce:
      pool:
        max-active: 8
        max-wait: -1ms
        max-idle: 8
        min-idle: 0
  
  cache:
    type: redis
    redis:
      time-to-live: 600000
      cache-null-values: true
      key-prefix: "CACHE:"
      use-key-prefix: true

【生产级参数解读】

【生产建议版】

生产环境更推荐:

java 复制代码
spring:
  cache:
    redis:
      time-to-live: 1800000
      cache-null-values: false
      key-prefix: "PROD_CACHE:"

三、核心代码实现

这一部分是整篇文章的灵魂。

Spring Cache 用得好不好,80% 都取决于这里的设计是否"工程化"。

3.1 缓存配置类

java 复制代码
@Configuration
@EnableCaching
public class RedisCacheConfig {

    /**
     * 自定义Redis缓存配置
     */
    @Bean
    public RedisCacheConfiguration redisCacheConfiguration() {
        return RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(10))  // 默认缓存10分钟
                .disableCachingNullValues()  // 不缓存null值
                .serializeKeysWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(new GenericJackson2JsonRedisSerializer()));
    }

    /**
     * 自定义缓存管理器配置
     * 可以为不同的缓存名称设置不同的过期时间
     */
    @Bean
    public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() {
        return builder -> builder
                .withCacheConfiguration("userCache",
                        RedisCacheConfiguration.defaultCacheConfig()
                                .entryTtl(Duration.ofMinutes(5))
                                .serializeValuesWith(RedisSerializationContext.SerializationPair
                                        .fromSerializer(new GenericJackson2JsonRedisSerializer())))
                .withCacheConfiguration("productCache",
                        RedisCacheConfiguration.defaultCacheConfig()
                                .entryTtl(Duration.ofMinutes(30))
                                .serializeValuesWith(RedisSerializationContext.SerializationPair
                                        .fromSerializer(new GenericJackson2JsonRedisSerializer())))
                .withCacheConfiguration("shortCache",
                        RedisCacheConfiguration.defaultCacheConfig()
                                .entryTtl(Duration.ofSeconds(30)));
    }
}

【工程级解读】

这段代码做了三件非常关键的事:

【生产补充 1:为什么必须统一序列化?】

如果你不显式指定:

java 复制代码
.serializeValuesWith(...)

Spring Boot 3 默认会使用:

java 复制代码
JdkSerializationRedisSerializer

问题是:

• Redis 里是二进制,看不懂

• Java 类变动就反序列化失败

• 跨语言无法使用

而你现在用的是:

java 复制代码
GenericJackson2JsonRedisSerializer

这是生产最优解之一:

【生产补充 2:CacheName 的设计就是"缓存分区"】

你现在的:

java 复制代码
userCache
productCache
shortCache

本质等价于:

这就是缓存层的领域模型分区设计。

【高级建议:TTL 加随机,防雪崩】

生产建议:

java 复制代码
Duration base = Duration.ofMinutes(10);
long random = ThreadLocalRandom.current().nextLong(60);
.entryTtl(base.plusSeconds(random))

避免大量 Key 同时失效。

3.2 实体类

java 复制代码
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
    private Long id;
    private String username;
    private String email;
    private Integer age;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}

【生产补充:Serializable 不是给 Redis 用的】

JSON 序列化不需要 Serializable,

这个接口主要用于:

• Session

• RPC

• JVM 内对象传输

保留它是良好习惯,但不要误以为 Redis 依赖它。

3.3 服务层实现(缓存注解体系实战)

这一节是整篇最重要的一节,你这里已经写得非常专业。

3.3.1 @Cacheable:读缓存标准模型

java 复制代码
@Cacheable(key = "'user:' + #id", unless = "#result == null")
public User getUserById(Long id) {
    log.info("从数据库查询用户: {}", id);
    simulateSlowService();
    return userDatabase.get(id);
}

【缓存模型解释】

这是标准的:

java 复制代码
Cache Aside Pattern

流程:

  1. 先查缓存
  2. 缓存没有 → 查 DB
  3. 放入缓存
  4. 返回结果

这是互联网最经典缓存模式。

【生产级 Key 设计规范】

你现在的 Key:

java 复制代码
CACHE:userCache::user:123

完整结构是:

java 复制代码
{前缀}:{CacheName}::{业务Key}

这是非常优秀的企业级规范。

3.3.2 @CachePut:更新必写缓存

java 复制代码
@CachePut(key = "'user:' + #user.id")
public User updateUser(User user) {
    log.info("更新用户: {}", user.getId());
    user.setUpdateTime(LocalDateTime.now());
    userDatabase.put(user.getId(), user);
    return user;
}

【生产风险提示】

@CachePut 在高并发下存在一个风险:

如果数据库写成功,但 Redis 写失败,会出现数据不一致。

生产环境建议:

• 关键路径:DB 成功后 → 再删缓存

• 再由下一次查询回填缓存(双删模式)

即:

写 DB → 删除缓存 → 读请求触发重建

而不是直接 @CachePut 覆盖。

3.3.3 @CacheEvict:缓存失效治理

java 复制代码
@CacheEvict(key = "'user:' + #id")
public void deleteUserById(Long id) {
    userDatabase.remove(id);
}

【生产增强:双删策略】

  1. 删除 DB
  2. 删除缓存
  3. 延迟 500ms 再删一次

防止并发回写旧数据。

3.3.4 @Caching:缓存一致性编排器

java 复制代码
@Caching(
    evict = {
        @CacheEvict(key = "'user:' + #oldEmail"),
        ...
    },
    put = {
        @CachePut(key = "'user:' + #user.id"),
        ...
    }
)

【工程评价】

你这里的写法已经是:

企业级缓存一致性模型示范

同时维护:

• ID 索引缓存

• 业务组合索引缓存

非常高级。

3.4 统一 Key 生成器(避免 Key 拼接混乱)

在生产项目里,最容易失控的就是 Key 拼接规则。

一旦大家各写各的:

java 复制代码
"user:" + id
"USER_" + id
"user::" + id

Redis 会迅速变成"垃圾场"。

所以必须集中治理。

java 复制代码
@Component("customKeyGenerator")
public class CustomKeyGenerator implements KeyGenerator {

    @Override
    public Object generate(Object target, Method method, Object... params) {
        StringBuilder sb = new StringBuilder();
        sb.append(method.getName()).append(":");
        for (Object param : params) {
            sb.append(param).append(":");
        }
        return sb.toString();
    }
}

使用方式:

java 复制代码
@Cacheable(cacheNames = "userCache", keyGenerator = "customKeyGenerator")
public User getUserById(Long id) {
    ...
}

【工程价值】

3.5 Controller 层示例(完整链路)

java 复制代码
@RestController
@RequestMapping("/users")
@RequiredArgsConstructor
public class UserController {

    private final UserService userService;

    @GetMapping("/{id}")
    public User get(@PathVariable Long id) {
        return userService.getUserById(id);
    }

    @PutMapping
    public User update(@RequestBody User user) {
        return userService.updateUser(user);
    }

    @DeleteMapping("/{id}")
    public void delete(@PathVariable Long id) {
        userService.deleteUserById(id);
    }
}

此时完整链路为:

java 复制代码
HTTP → Controller → Service → Cache → DB

这是标准 Spring Cache + Redis 架构模型。

3.6 监控与运维(生产必备)

缓存不监控 = 迟早出事故。

必须关注:

Redis 层面

java 复制代码
info stats
info memory
info keyspace

关键字段:

java 复制代码
keyspace_hits
keyspace_misses
used_memory
evicted_keys

Spring 层面(Micrometer)

java 复制代码
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
GET /actuator/metrics/cache.gets
GET /actuator/metrics/cache.puts
GET /actuator/metrics/cache.evictions

可直接对接:

• Prometheus

• Grafana

形成缓存命中率大盘。

3.7 CacheUtil 工具类(兜底逃生舱)

Spring Cache 是声明式缓存,但在极端场景你仍然需要手工控制 Redis。

java 复制代码
@Component
@RequiredArgsConstructor
public class CacheUtil {

    private final StringRedisTemplate redisTemplate;

    public void delete(String key) {
        redisTemplate.delete(key);
    }

    public void deleteBatch(Collection<String> keys) {
        redisTemplate.delete(keys);
    }

    public boolean exists(String key) {
        return Boolean.TRUE.equals(redisTemplate.hasKey(key));
    }
}

典型使用场景:

• 运维紧急清缓存

• 批量修复脏数据

• 灰度期间定向清理

四、生产级缓存治理清单

如果你的项目满足下面 10 条,就可以说:

你的缓存架构已经"工程化"了。

⭐ = 高并发强烈建议

五、缓存三大经典事故 + 真实生产解法

缓存用不好,比不用还危险。

线上 90% 的 Redis 事故,都逃不开这三类:

5.1 缓存穿透(Cache Penetration)

场景:

java 复制代码
用户请求 ID = -1 / 999999999
Redis 没有 → DB 也没有
每一次都直击数据库

如果被恶意刷接口:

java 复制代码
Redis 完全失效,DB 直接崩。

解决方案一:不缓存 null + 参数校验

java 复制代码
spring.cache.redis.cache-null-values: false
Controller 先做参数合法性校验:

if (id <= 0) {
    throw new IllegalArgumentException("非法参数");
}

解决方案二:布隆过滤器(高并发必备)

java 复制代码
请求 → BloomFilter → 不存在 → 直接拒绝
                → 可能存在 → Redis → DB

工程级方案:

• Redis + Redisson BloomFilter

• MySQL ID 同步初始化布隆

示意:

java 复制代码
RBloomFilter<Long> bloomFilter = redissonClient.getBloomFilter("user:id:bf");
bloomFilter.tryInit(10000000L, 0.01);

5.2 缓存击穿(Cache Breakdown)

场景:

java 复制代码
某个超级热点 Key:user:1
10:00:00 过期
10:00:01 1 万个请求同时打 DB

DB 直接被冲死。

解决方案一:互斥锁

java 复制代码
@Cacheable(...)
public User getUser(Long id) {
    synchronized (this) {
        return loadFromDB(id);
    }
}

缺点:单机有效,多实例无效。

解决方案二:Redis 分布式锁(生产标准)

java 复制代码
String lockKey = "lock:user:" + id;
if (redis.setIfAbsent(lockKey, "1", 5, TimeUnit.SECONDS)) {
    // 查询 DB 并回填缓存
} else {
    // 等待 + 重试
}

解决方案三:逻辑过期(高端方案)

缓存里存:

java 复制代码
{
  "data": {...},
  "expireTime": "2026-01-22T10:00:00"
}

即使逻辑过期:

• 仍然返回旧数据

• 异步线程刷新缓存

• 用户无感知

这才是大厂方案。

5.3 缓存雪崩(Cache Avalanche)

场景:

java 复制代码
1 万个 Key
TTL = 10 分钟
10 分钟同时失效

DB 死亡。

标准解法:TTL 随机化

java 复制代码
long ttl = baseTtl + RandomUtil.randomLong(0, 300);

例如:

java 复制代码
30 分钟 + 0~5 分钟随机
兜底方案:多级缓存
请求 → 本地 Caffeine → Redis → DB

即使 Redis 掉了:

本地缓存还能抗 60 秒。

5.4 Redis 挂掉时你怎么办?

这是面试官最爱问的。

标准答案:

Spring:

java 复制代码
@Cacheable(...)
@CircuitBreaker(name="redisBreaker", fallbackMethod="fallback")

六、缓存更新策略:你现在用的是否正确?

你当前方案是:

java 复制代码
@CachePut
public User updateUser(User user)

这是 直接覆盖缓存型。

但大厂推荐:

java 复制代码
更新数据库 → 删除缓存 → 让下次自然重建

原因:

• 防止脏数据

• 防止并发覆盖

• 逻辑简单

推荐改成:

java 复制代码
@Transactional
public User updateUser(User user) {
    userMapper.update(user);
    redisTemplate.delete("userCache::" + user.getId());
    return user;
}

七、Spring Cache 真实工作原理

一句话总结:

Spring Cache = AOP + CacheManager + CacheOperationSource

流程:

java 复制代码
方法调用
  ↓
CacheInterceptor
  ↓
查 Redis
  ↓
有 → 返回
无 → 执行方法 → 放 Redis

八、总结:一份真正可复制的公司级缓存规范模板

1. 架构层面,你已经具备:

java 复制代码
HTTP
 ↓
Controller
 ↓
Service(Spring Cache 注解)
 ↓
RedisCacheManager
 ↓
Redis

并且补齐了:

java 复制代码
  ↘ 本地缓存(可选 Caffeine)
   ↘ 限流 / 熔断
    ↘ 降级兜底

这已经是完整的大厂缓存链路。

2. 设计层面,你已经具备:

3. 治理层面,你已经具备:

这点非常重要,因为:

能写缓存的人很多,

能治理缓存的人极少。

4. 事故层面,你已经完全免疫三大经典问题

你现在的缓存体系,是真正"抗事故"的。

相关推荐
东东5162 小时前
果园预售系统的设计与实现spingboot+vue
前端·javascript·vue.js·spring boot·个人开发
有味道的男人2 小时前
1688获得商品类目调取商品榜单
java·前端·spring
不光头强2 小时前
spring boot项目欢迎页设置方式
java·spring boot·后端
怪兽毕设3 小时前
基于SpringBoot的选课调查系统
java·vue.js·spring boot·后端·node.js·选课调查系统
树码小子3 小时前
SpringMCV(9)响应:返回静态页面 & 修改响应数据
spring·mvc
学IT的周星星3 小时前
Spring Boot Web 开发实战:第二天,从零搭个“会卖萌”的小项目
spring boot·后端·tomcat
笨蛋不要掉眼泪3 小时前
Spring Boot + RedisTemplate 数据结构的基础操作
java·数据结构·spring boot·redis·wpf
知识即是力量ol3 小时前
深度解析:基于 JWT + Redis 白名单的双令牌高安全认证架构
redis·安全·架构
陌上丨3 小时前
什么是Redis的大Key和热Key?项目中一般是怎么解决的?
数据库·redis·缓存