SpringBoot 整合 Spring Cache + Redis 实现声明式缓存实战

举报
JiHua 发表于 2026/11/02 13:48:01 2026/11/02
【摘要】 在后端开发中,缓存是提升接口吞吐量、降低数据库压力最常用的手段。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;
}

新增用户时更新单条缓存、同时清空用户列表缓存。

八、生产避坑点

  1. 注解失效问题
    Spring Cache 基于 AOP,同一个类内方法互相调用,注解不会生效。比如本类A方法调用加了@Cacheable的B方法,缓存逻辑失效,要拆分到不同Service或者通过ApplicationContext获取代理对象。
  2. 缓存key冲突
    不同业务模块尽量分开 value,统一增加前缀,避免key覆盖。
  3. 缓存雪崩
    统一过期时间会导致大量key同时失效,可以在TTL基础上增加随机偏移量。
  4. 事务和缓存顺序
    默认先更新缓存再提交事务,高并发下会出现脏读。优先推荐:先操作数据库,事务提交成功后再操作缓存。Spring Cache 默认的机制需要结合事务仔细评估。
  5. null值缓存
    开启 cache-null-values=true 可以拦截大量不存在ID的请求,抵御简单缓存穿透,但不能替代布隆过滤器。

九、总结

Spring Cache 把缓存逻辑抽象成注解,大幅减少样板代码,开发效率很高。它的优势是解耦,底层缓存中间件可以随时切换;缺点是抽象层屏蔽了很多Redis原生能力,复杂场景(分布式锁、Lua脚本、流式操作)仍然需要直接使用 RedisTemplate。

日常业务单表查询、基础列表缓存场景,优先选择 Spring Cache;高并发复杂缓存控制场景,再手写 Redis 原生代码。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。