Spring Boot 缓存注解详解:@Cacheable、@CachePut、@CacheEvict(超详细实战版)

💡 前言

在高并发、高性能的系统开发中,缓存是提升接口响应速度和降低数据库压力的重要手段。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>

也可以使用本地缓存如 CaffeineEhcache

Step 2:启用缓存

@SpringBootApplication
@EnableCaching
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
}

🔁 三、三大缓存注解详解与实战

✅ 1. @Cacheable:查询缓存,避免重复计算

场景说明:

当你有一个查询接口,数据变更频率低,但查询频繁,就可以使用 @Cacheable 来缓存结果。

示例代码:
@Service
public class UserService {@Autowiredprivate 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: 60scache:redis:time-to-live: 3600000 # 1小时

✅ 3. 多级缓存策略(Redis + Caffeine)

为了进一步提升性能,可以采用“本地缓存 + 分布式缓存”的组合方案:

  • 使用 Caffeine 作为本地缓存,减少网络开销;
  • 使用 Redis 作为分布式缓存,确保多节点间数据一致。
示例配置:
@Configuration
public class CacheConfig {@Beanpublic CacheManager caffeineCacheManager() {CaffeineCacheManager cacheManager = new CaffeineCacheManager("localUsers");cacheManager.setCacheBuilder(Caffeine.newBuilder().expireAfterWrite(5, TimeUnit.MINUTES));return cacheManager;}@Beanpublic 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;}
}

✅ 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 {@Autowiredprivate 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 Product 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 或本地缓存进行实际应用;
  • 如何避免缓存穿透、雪崩、击穿等问题;
  • 如何提高系统的响应速度和稳定性。

🎯 点赞、收藏、转发本文,让更多开发者受益!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/pingmian/84102.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue中ref的详解以及react的ref对比

文章目录 1. ref是什么2. ref的使用3. ref的特性4. 使用场景5. 注意事项6. 与 React 的对比7. 动态 ref8. 函数式组件中的 ref9. 组合式 API 中的 ref10. 总结 1. ref是什么 ref 被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。可以通过实例对象…

通过ca证书的方式设置允许远程访问Docker服务

设置允许远程访问Docker服务 使用场景 环境 系统&#xff1a;anolis7.9 修改Docker服务配置&#xff0c;配置安全证书 生成ca证书到/etc/docker目录中&#xff0c;后续会要用到 #该步骤需要设置密码&#xff0c;后面步骤会要用到&#xff0c;此处设置密码为123456 openss…

Qt Quick Layout功能及架构

Qt Quick Layouts 是 Qt Quick 中用于管理用户界面布局的模块&#xff0c;在 Qt 6.0 中继续提供强大的布局管理功能。 一、主要功能 主要布局类型 RowLayout - 水平排列项目 ColumnLayout - 垂直排列项目 GridLayout - 网格排列项目 StackLayout - 堆叠项目&#xff08;一…

Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?

Golang 面试经典题&#xff1a;map 的 key 可以是什么类型&#xff1f;哪些不可以&#xff1f; 在 Golang 的面试中&#xff0c;map 类型的使用是一个常见的考点&#xff0c;其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…

从 ClickHouse、Druid、Kylin 到 Doris:网易云音乐 PB 级实时分析平台降本增效

网易云音乐基于 Apache Doris 替换了早期架构中 Kylin、Druid、Clickhouse、Elasticsearch、HBase 等引擎&#xff0c;统一了实时分析架构&#xff0c;并广泛应用于广告实时数仓、日志平台和会员报表分析等典型场景中&#xff0c;带来导入性能提升 3&#xff5e;30 倍&#xff…

Android 本地存储路径说明

一、背景 作为一个开发者,我们经常需要通过缓存一些文件到SD卡中,常见的方式就是,通过: File sdCard Environment.getExternalStorageDirectory(); 获取SD卡根目录,然后自定义文件/文件名进行文件存储.这样做法的结果就是,当手机安装了大量的app时&#xff0c;SD卡根目录会…

开发的几种格式,TCP的十个重要机制

自定义协议中&#xff0c; 我们有几种常见的数据格式&#xff1a; 1.xml 通过标签来组织数据 请求&#xff1a; 优势&#xff1a; 让数据的可读性变更好了 劣势&#xff1a; 标签非常繁琐&#xff0c;传输的时候也占用更多网络带宽&#xff08;maven会使用xml来管理项目配…

dify打造数据可视化图表

一、概述 在日常工作和学习中&#xff0c;我们经常需要和数据打交道。无论是分析报告、项目展示&#xff0c;还是简单的数据洞察&#xff0c;一个清晰直观的图表&#xff0c;往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server&#xff0c;由蚂蚁集团 AntV 团队…

自然语言处理——文本分类

文本分类 传统机器学习方法文本表示向量空间模型 特征选择文档频率互信息信息增益&#xff08;IG&#xff09; 分类器设计贝叶斯理论&#xff1a;线性判别函数 文本分类性能评估P-R曲线ROC曲线 将文本文档或句子分类为预定义的类或类别&#xff0c; 有单标签多类别文本分类和多…

任务调度器-关于中心化调度 vs 去中心化调度的核心区别

1. 定义与架构模型 维度中心化调度去中心化调度核心角色存在一个中央调度器&#xff08;如XXL-JOB的调度中心&#xff09;&#xff0c;统一管理任务分配、状态监控和故障处理。无中心节点&#xff0c;调度逻辑分散在多个节点&#xff0c;通过共识算法&#xff08;如选举机制&a…

[论文阅读] 人工智能+软件工程 | 结对编程中的知识转移新图景

当AI成为编程搭档&#xff1a;结对编程中的知识转移新图景 论文信息 论文标题&#xff1a;From Developer Pairs to AI Copilots: A Comparative Study on Knowledge Transfer&#xff08;从开发者结对到AI副驾驶&#xff1a;知识转移的对比研究&#xff09; 作者及机构&#…

CAD多面体密堆积3D插件

插件介绍 CAD多面体密堆积3D插件可在AutoCAD内建立三维随机多面体密堆积模型。 插件内置物理动力学模拟算法&#xff0c;通过模拟重力、碰撞等现象&#xff0c;使多面体在虚拟环境中发生自然堆积&#xff0c;进而实现真实的堆积效果。多面体堆积模拟中存在的局部穿模问题可通…

VSCode CUDA C++进行Linux远程开发

环境准备 确保在本地和远程Linux服务器上安装了以下软件&#xff1a; Visual Studio Code&#xff08;简称VS Code&#xff09;Remote Development extension pack for VS CodeCUDA Toolkit&#xff0c;推荐版本为11.0或更高GCC编译器&#xff0c;用于C代码的编译 此外&…

Python爬虫(一):爬虫伪装

一、网站防爬机制概述 在当今互联网环境中&#xff0c;具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类&#xff1a; 身份验证机制&#xff1a;直接将未经授权的爬虫阻挡在外反爬技术体系&#xff1a;通过各种技术手段增加爬虫获取数据的难度…

快速使用 Flutter Card 组件指南

目录 一、引言 二、Card 的基本用法 三、主要属性 3.1 elevation (阴影高度) 3.2 shape (形状) 3.3 color (颜色) 3.4 margin (外边距) 3.5 完整示例 四、结合 ListTile 组件使用 五、带图片的 Card 示例 六、注意事项 相关推荐 一、引言 Card 是 Flutter 提供的一个…

C语言内存管理和编译优化实战

参考&#xff1a; C语言内存管理“玄学”&#xff1a;从崩溃到精通的避坑指南C语言编译优化实战&#xff1a;从入门到进阶的高效代码优化技巧

【产品业务设计】支付业务设计规范细节记录,含订单记录、支付业务记录、支付流水记录、退款业务记录

【产品业务设计】支付业务设计规范细节记录&#xff0c;含订单记录、支付业务记录、支付流水记录 前言 我为什么要写这个篇文章 总结设计经验生成设计模板方便后期快速搭建 一个几张表 一共5张表&#xff1b; 分别是&#xff1a; 订单主表&#xff1a;jjy_orderMain订单产…

CppCon 2015 学习:Live Lock-Free or Deadlock

这段内容是介绍一场关于**“实用无锁编程&#xff08;Practical Lock-Free Programming&#xff09;”**的讲座提纲&#xff0c;重点在C中的并发编程。下面是详细的中文理解和分析&#xff1a; 讲座大纲和内容理解 主题概览 适当的“guru崇拜”和“祈求” → 开场调侃&#…

centos7编译安装LNMP架构

一、LNMP概念 LNMP架构是一种常见的网站服务器架构&#xff0c;由Linux操作系统、Nginx Web服务器、MySQL数据库和PHP后端脚本语言组成。 1 用户请求&#xff1a;用户通过浏览器输入网址&#xff0c;请求发送到Nginx Web服务器。 2 Nginx处理&#xff1a;Nginx接收请求后&…

Spark 写文件

Repartition Spark 输出文件数量 假设每个 Task 的输出数据都包含了全部 8 个分区值,那么最终的文件生成情况如下: 总文件数 = Task 数量 分区组合数 假设: ​Task 数量​:200 ​分区组合数​:8 个 (from_cluster 和 ds 的组合) 则: ​总文件数​:200 8 = ​1600 …