Spring Boot 缓存注解@Cacheable、@CachePut、@CacheEvict超详细实战
目录
- 前言
- 一、Spring Boot 缓存核心机制概述
- ✅ 缓存抽象原理
- ️ 二、快速入门:开启缓存支持
- Step 1:添加依赖(以 Redis 为例)
- Step 2:启用缓存
- 三、三大缓存注解详解与实战
- ✅ 1. @Cacheable:查询缓存,避免重复计算
- ✅ 2. @CachePut:更新缓存,保持一致性
- ✅ 3. @CacheEvict:清除缓存,强制刷新
- 四、进阶技巧与最佳实践
- ✅ 1. 自定义缓存 Key 生成策略
- ✅ 2. 设置缓存过期时间(以 Redis 为例)
- ✅ 3. 多级缓存策略(Redis + Caffeine)
- ✅ 4. 缓存穿透、击穿、雪崩解决方案
- 五、综合案例:商品信息缓存实战
- 六、常见问题与解决方案
- 七、总结对比表
- 八、结语
前言
在高并发、高性能的系统开发中,缓存是提升接口响应速度和降低数据库压力的重要手段。Spring Boot 提供了强大的缓存抽象层 —— spring-context-support
,并结合 jsR-107 标准,提供了多个缓存注解,如:
@Cacheable
@CachePut
@CacheEvict
这些注解可以让我们在不侵入业务逻辑的前提下,轻松实现方法级别的缓存管理。
本文将带你从零开始掌握 Spring Boot 中常用的缓存注解,并通过多个实际案例演示其强大功能,包括:
- 查询缓存优化
- 数据更新同步
- 删除缓存策略
- 多级缓存架构设计
- Redis 实战整合
- 缓存穿透、击穿、雪崩的解决方案
无论你是初学者,还是有一定经验的开发者,这篇文章都能让你快速上手 Spring Boot 缓存机制!
一、Spring Boot 缓存核心机制概述
✅ 缓存抽象原理
Spring 的缓存机制基于 AOP 实现,本质上是在方法执行前后插入缓存逻辑:
注解 | 功能 |
---|---|
@Cacheable | 先查缓存,有则返回;没有则执行方法并将结果缓存 |
@CachePut | 执行方法后更新缓存(常用于新增或更新操作) |
@CacheEvict | 清除缓存(常用于删除或刷新缓存) |
注意:
- 这些注解只能用在
public
方法上; - 只有当调用方通过 Spring 代理调用该方法时才生效(即不能在同一个类中直接调用带注解的方法);
- 需要启用缓存功能:
@EnableCaching
️ 二、快速入门:开启缓存支持
Step 1:添加依赖(以 Redis 为例)
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
也可以使用本地缓存如 Caffeine
或 Ehcache
。
Step 2:启用缓存
@SpringBootApplication @EnableCaching public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
三、三大缓存注解详解与实战
✅ 1. @Cacheable:查询缓存,避免重复计算
场景说明:
当你有一个查询接口,数据变更频率低,但查询频繁,就可以使用 @Cacheable
来缓存结果。
示例代码:
@Service public class UsephprService { @Autowired private UserRepository userRepository; @Cacheable(value = "users", key = "#userId") public User getUserById(Long userId) { System.out.println("【真实查询】用户ID:" + userId); return userRepository.findById(userId).orElse(null); } }
参数说明:
value
/cacheNames
:指定缓存名称,可理解为缓存区域;key
:缓存键,默认使用参数生成;unless
:条件判断,例如unless = "#result == null"
表示结果为空时不缓存;condition
:根据条件决定是否缓存,例如condition = "#userId > 0"
。
✅ 2. @CachePut:更新缓存,保持一致性
场景说明:
当你对数据进行了更新或新增操作,需要同步更新缓存,保证下次查询拿到的是最新数据。
示例代码:
@CachePut(value = "users", key = "#user.id") public User updateUser(User user) { return userRepository.save(user); }
注意:
@CachePut
总是会执行方法体,并将结果放入缓存;- 常用于新增、修改等写操作。
✅ 3. @CacheEvict:清除缓存,强制刷新
场景说明:
当你删除某个数据或批量清空缓存时,就需要使用 @CacheEvict
。
示例代码:
@CacheEvict(value = "users", key = "#userId") public void deleteUser(Long userId) { userRepository.deleteById(userId); }
清空整个缓存区:
@CacheEvict(value = "users", allEntries = true) public void clearAllUsers() { // 删除所有用户逻辑 }
参数说明:
allEntries = true
:清空该缓存区域下的所有键;beforeInvocation = true
:是否在方法执行前清空缓存(默认 false,在方法执行后)。
四、进阶技巧与最佳实践
✅ 1. 自定义缓存 Key 生成策略
可以通过实现 KeyGenerator
接口自定义缓存 key 的生成方式:
@Bean public KeyGenerator customKeyGenerator() { return (target, method, params) -> { StringBuilder sb = new StringBuilder(); sb.append(target.getClass().getSimpleName()); sb.append(method.getName()); for (Object obj : params) { sb.append(obj.toString()); } return sb.toString(); }; }
然后在注解中使用:
@Cacheable(value = "users", keyGenerator = "customKeyGenerator")
✅ 2. 设置缓存过期时间(以 Redis 为例)
在 application.yml
中配置:
spring: redis: timeout: 60s cache: redis: time-to-live: 3600000 # 1小时
✅ 3. 多级缓存策略(Redis + Caffeine)
为了进一步提升性能,可以采用“本地缓存 + 分布式缓存”的组合方案:
- 使用
Caffeine
作为本地缓存,减少网络开销; - 使用
Redis
作为分布式缓存,确保多节点间数据一致。
示例配置:
@Configuration public class CacheConfig { @Bean public CacheManager caffeineCacheManager() { CaffeineCacheManager cacheManagerphp = new CaffeineCacheManager("localUsers"); cacheManager.setCacheBuilder(Caffeine.newBuilder().expireAfterWrite(5, TimeUnit.MINUTES)); return cacheManager; } @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); template.afterPropertiesSet(); return template; }www.devze.com }
✅ 4. 缓存穿透、击穿、雪崩解决方案
问题 | 解决方案 |
---|---|
缓存穿透 | 缓存空值、使用布隆过滤器 |
缓存击穿 | 加互斥锁、设置永不过期 |
缓存雪崩 | 设置不同过期时间、集群缓存 |
示例:防止缓存穿透
@Cacheable(value = "products", key = "#productId", unless = "#result == null") public Product getProductById(Long productId) { Product product = productRepository.findById(productId).orElse(null); if (product == null) { // 缓存空值,防止缓存穿透 redisService.setWithExpire("product:" + productId, "", 60, TimeUnit.SECONDS); } return product; }
五、综合案例:商品信息缓存实战
背景:
我们有一个商品服务,包含如下功能:
- 查询商品详情(缓存)
- 更新商品信息(更新缓存)
- 删除商品(清除缓存)
代码示例:
@Service public class ProductService { @Autowired private ProductRepository productRepository; @Cacheable(value = "products", key = "#productId") public Product getProductById(Long productId) { return productRepository.findById(productId).orElse(null); } @CachePut(value = "products", key = "#product.id") public Pr编程客栈oduct updateProduct(Product product) { return productRepository.save(product); } @CacheEvict(value = "products", key = "#productId") public void deleteProduct(Long productId) { productRepository.deleteById(productId); } }
六、常见问题与解决方案
问题 | 原因 | 解决办法 |
---|---|---|
注解不生效 | 没加 @EnableCaching | 添加注解 |
同一类内调用失效 | AOP 代理失效 | 将方法抽离到另一个 Bean 中 |
缓存未更新 | 没使用 @CachePut 或 @CacheEvict | 加上对应注解 |
key 冲突 | key 重复导致覆盖 | 使用更细粒度的 key 策略 |
不支持序列化 | 缓存对象未实现 Serializable | 使用 JSON 序列化或实现接口 |
七、总结对比表
注解 | 作用 | 是否执行方法 | 适用场景 |
---|---|---|---|
@Cacheable | 查询缓存 | 有缓存则跳过方法 | 查询频繁、更新少的数据 |
@CachePut | 更新缓存 | 总是执行方法 | 新增/修改操作 |
@CacheEvict | 删除缓存 | 总是执行方法 | 删除或刷新缓存 |
八、结语
Spring Boot 的缓存注解为我们提供了一种声明式的缓存管理方式,极大地提升了开发效率和系统性能。无论是构建企业级后台系统,还是搭建高并发 API 平台,都应该合理使用缓存来优化系统表现。
通过本文的学习,你已经掌握了:
- 如何使用
@Cacheable
、@CachePut
、@CacheEvict
; - 如何结合 Redis 或本地缓存进行实际应用;
- 如何避免缓存穿透、雪崩、击穿等问题;
- 如何提高系统的响应速度和稳定性。
到此这篇关于Spring Boot 缓存注解详解:@Cacheable、@CachePut、@CacheEvict(超详细实战版)的文章就介绍到这了,更多相关Spring Boot @Cacheable、@CachePut、@CacheEvict内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!
精彩评论