【SpringBoot实战指南】使用 Spring Cache
文章目录
- 一、Spring Cache简介
- 核心特点:
- 二、基础配置
- 1. 添加依赖
- 2. 启用缓存
- 3. 缓存配置方案
- 方案1:通过 yml 配置文件
- 方案2:自定义 Bean
- 三、 缓存注解使用示例
- 1.@Cacheable - 数据查询缓存
- 2.@CachePut - 更新数据并缓存
- 3.@CacheEvict - 删除缓存
- 4. @Caching - 组合操作
- 5. 条件缓存 (condition/unless)
- 6. 异步缓存加载
- 四、特殊场景处理
- 1. 缓存空值防御
- 2. 复合缓存键
- 五、经验之谈
一、Spring Cache简介
Spring Cache 是 Spring 框架提供的一套声明式缓存抽象层,通过注解方式简化缓存操作,它通过在方法上添加注解(如 @Cacheable、@CacheEvict
)来地管理缓存操作,无需手动编写缓存逻辑。
它支持多种缓存实现(如 Caffeine、Redis、EhCache),并统一了缓存访问的 API。
这里需要注意两点:
- Spring Cache 只是一个声明式的抽象缓存层,意思是它只提供了接口,不提供实现
- 具体的实现可以有很多,比如 Caffeine,Redis,EhCache 这些,只要实现了这些接口,就可以被 Spring Cache 使用。
核心特点:
- 基于注解的声明式缓存
- 支持 SpEL 表达式
- 自动与 Spring 生态集成
- 支持条件缓存
二、基础配置
1. 添加依赖
<!-- Spring Boot Cache Starter -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId>
</dependency><!-- 如果使用Redis -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency><!-- 如果使用caffeine -->
<dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>caffeine</artifactId>
</dependency>
2. 启用缓存
在启动类添加@EnableCaching
注解:
@SpringBootApplication
@EnableCaching
public class MyApp {public static void main(String[] args) {SpringApplication.run(MyApp.class, args);}
}
3. 缓存配置方案
方案1:通过 yml 配置文件
spring:cache:type: caffeinecaffeine:spec: maximumSize=500,expireAfterWrite=60s# 或者分开配置cache-names: users,productscaffeine.spec: # 全局默认配置maximumSize=1000,expireAfterAccess=30m
方案2:自定义 Bean
@Configuration // 标记这是一个Spring配置类
public class CacheConfig {/*** 创建并配置Caffeine缓存管理器** @return CacheManager 实例,用于管理应用中所有缓存** 主要配置参数说明:* - initialCapacity: 初始缓存空间大小(提升初始性能)* - maximumSize: 缓存最大容量(基于条目数)* - expireAfterWrite: 写入后过期时间(数据一致性优先场景)* - recordStats: 开启统计功能(用于监控和调优)*/@Beanpublic CacheManager cacheManager() {// 创建Caffeine缓存管理器实例CaffeineCacheManager cacheManager = new CaffeineCacheManager();// 配置Caffeine缓存参数cacheManager.setCaffeine(Caffeine.newBuilder().initialCapacity(100) // 初始容量100个条目.maximumSize(1000) // 最大缓存1000个条目,超过后按LRU淘汰.expireAfterWrite(10, TimeUnit.MINUTES) // 写入10分钟后过期.recordStats()); // 启用缓存统计(命中率等)return cacheManager;}/*** 创建短期缓存实例(独立于主缓存管理器)** @return Cache 实例,适用于高频访问的临时数据** 典型使用场景:* - 高频访问的临时数据* - 需要快速失效的验证码等* - 与其他缓存不同生命周期的数据*/@Bean(name = "shortTermCache") // 指定Bean名称便于按名称注入public Cache shortTermCache() {return Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES) // 1分钟过期(短期存储).maximumSize(100) // 最大100个条目.build(); // 构建Cache实例}
}
三、 缓存注解使用示例
@Cacheable
:用于标记方法,表示该方法将结果缓存起来,下次调用时直接从缓存中获取结果,而不需要重新执行方法。@CacheEvict
:用于标记方法,表示该方法将清除缓存,通常用于删除缓存。@CachePut
:用于标记方法,表示该方法将更新缓存,通常用于更新缓存。@Caching
:用于组合多个缓存注解,可以同时使用多个缓存注解。@CacheConfig
:用于标记类,表示该类中的所有方法将使用指定的缓存配置。
1.@Cacheable - 数据查询缓存
/*** 根据ID获取用户信息(带缓存)* @param id 用户ID* @return 用户对象,如果不存在返回null* * @Cacheable 参数说明:* - value/cacheNames: 指定缓存名称(对应Caffeine配置)* - key: 缓存键,使用SpEL表达式(#参数名引用方法参数)* - unless: 条件表达式,当结果满足条件时不缓存*/
@Cacheable(value = "users", key = "#id", unless = "#result == null")
public User getUserById(Long id) {log.info("执行数据库查询,用户ID: {}", id);return userRepository.findById(id).orElse(null);
}
2.@CachePut - 更新数据并缓存
/*** 更新用户信息(同时更新缓存)* @param user 用户对象* @return 更新后的用户对象* * @CachePut 特点:* - 总是执行方法体* - 用返回值更新缓存* - 适用于"先写后读"场景*/
@CachePut(value = "users", key = "#user.id")
public User updateUser(User user) {log.info("更新用户数据: {}", user.getId());return userRepository.save(user);
}
3.@CacheEvict - 删除缓存
/*** 删除用户(同时移除缓存)* @param id 用户ID* * @CacheEvict 参数说明:* - beforeInvocation: 是否在方法执行前清除缓存(默认false)* - allEntries: 是否清空整个缓存区域(慎用)*/
@CacheEvict(value = "users", key = "#id")
public void deleteUser(Long id) {log.info("删除用户: {}", id);userRepository.deleteById(id);
}
4. @Caching - 组合操作
组合操作允许在单个方法上同时使用多个缓存注解,以实现更复杂的缓存策略。
/**
* 更新用户状态(复杂缓存操作)
* @param userId 用户ID
* @param status 新状态
*
* 典型场景:
* - 更新用户缓存
* - 同时失效用户列表缓存*/@Caching(put = @CachePut(value = "users", key = "#userId"),evict = @CacheEvict(value = "userList", allEntries = true))public void updateUserStatus(Long userId, UserStatus status) {log.info("更新用户{}状态为{}", userId, status);userRepository.updateStatus(userId, status);}
5. 条件缓存 (condition/unless)
条件缓存允许在缓存注解中添加 SpEL 条件表达式,以控制缓存的触发时机。
/*** 获取用户详情(带条件缓存)* @param id 用户ID* * 缓存条件说明:* - condition: 只有id>1000时才走缓存* - unless: 结果中status=DELETED时不缓存*/
@Cacheable(value = "users", key = "#id",condition = "#id > 1000",unless = "#result != null && #result.status == T(com.example.UserStatus).DELETED")
public User getUserDetail(Long id) {log.info("查询用户详情: {}", id);return userRepository.findDetailById(id);
}
6. 异步缓存加载
/*** 获取用户订单列表(异步缓存)* @param userId 用户ID* * @sync = true 表示:* - 多线程并发时,只有一个线程会执行加载* - 其他线程等待结果*/
@Cacheable(value = "orders", key = "#userId", sync = true)
public List<Order> getUserOrders(Long userId) {log.info("加载用户{}订单数据...", userId);return orderService.getOrdersByUser(userId);
}
四、特殊场景处理
1. 缓存空值防御
/*** 查询用户(防穿透处理)* @param name 用户名* * 特殊处理:* - 对null结果也进行缓存(特殊标记对象)* - 设置较短过期时间(配置文件中定义)*/
@Cacheable(value = "usersByName", key = "#name",unless = "#result == null || #result == T(com.example.CacheConstants).NULL_OBJECT")
public User getUserByName(String name) {User user = userRepository.findByName(name);return user != null ? user : CacheConstants.NULL_OBJECT;
}
2. 复合缓存键
/*** 获取用户在某系统的权限列表* @param userId 用户ID * @param systemCode 系统编码(如:"OA", "CRM"等)* @return 权限字符串集合* * 缓存Key设计说明:* 1. 使用复合Key结构:`用户ID_系统编码`(如:123_OA)* 2. 优点:* - 避免不同系统权限缓存冲突* - 支持按用户+系统维度独立管理缓存* 3. 缓存条件:仅当结果非空时缓存*/
@Cacheable(value = "userPermissions", key = "#userId + '_' + #systemCode",unless = "#result == null || #result.isEmpty()")
public Set<String> getUserSystemPermissions(Long userId, String systemCode) {log.debug("查询用户[{}]在系统[{}]的权限", userId, systemCode);return permissionService.findPermissions(userId, systemCode);
}/*** 获取用户角色列表(带枚举参数的Key示例)* @param userId 用户ID* @param roleType 角色类型枚举* * 枚举类型处理技巧:* 1. 调用枚举的name()方法转换为字符串* 2. 最终Key格式:`userId:roleType`(如:123:ADMIN)*/
@Cacheable(value = "userRoles", key = "#userId + ':' + #roleType.name()")
public List<Role> getUserRoles(Long userId, RoleType roleType) {return roleService.findByUserAndType(userId, roleType);
}
五、经验之谈
- 推荐为每个
@Cacheable
方法添加unless
条件防御null
值 - 业务更新方法建议同时使用
@CachePut
和@CacheEvict
- 高频访问数据考虑设置
sync=true
相关文章:
【SpringBoot实战指南】使用 Spring Cache
文章目录 一、Spring Cache简介核心特点: 二、基础配置1. 添加依赖2. 启用缓存3. 缓存配置方案方案1:通过 yml 配置文件方案2:自定义 Bean 三、 缓存注解使用示例1.Cacheable - 数据查询缓存2.CachePut - 更新数据并缓存3.CacheEvict - 删除缓…...

centos8 配置网桥,并禁止kvm默认网桥
环境背景: 我使用vmware部署了一台kvm服务器,网络模式是nat。我想要kvm创建的虚拟机可以访问公网;所以kvm默认的地址不行,我必须使用nat地址才可以; 实现方式: 创建一个网桥,将本地的网络接口…...
C++:list容器,deque容器
list容器:双向链表容器,底层是双向链表。 简单使用如下: #include<iostream> #include<list> using namespace std;int main() {list<int> lst;lst.push_back(1);lst.push_back(2);lst.push_back(3);lst.push_front(4);l…...

【Node.js】全栈开发实践
个人主页:Guiat 归属专栏:node.js 文章目录 1. Node.js 全栈开发概述1.1 全栈开发的优势1.2 Node.js 全栈开发技术栈 2. 开发环境搭建2.1 Node.js 和 npm 安装2.2 开发工具安装2.3 版本控制设置2.4 项目初始化流程 3. 后端开发 (Node.js)3.1 Express 框架…...
自定义类型-联合体
概念 联合体是一种特殊的数据类型,允许在相同的内存位置存储不同的数据类型 联合体的所有成员共享同一块内存空间,大小由最大的成员决定 用于在同一块内存单元内存放不同类型的变量 语法结构 结构与结构体类似, union 共用体名 {成员列…...
Qt项目开发中所遇
讲述下面代码所表示的含义: QWidget widget_19 new QWidget(); QVBoxLayout *touchAreaLayout new QVBoxLayout(widget_19);QWidget *buttonArea new QWidget(widget_19); 1、新建一个名为widget_19的QWidget,将给其应用垂直管路布局。 2、新建一个…...

ubuntu sh安装包的安装方式
ubuntu sh安装包的安装方式以Miniconda2为例 https://repo.anaconda.com/miniconda/ 如果需要python2.7版本可下载以下版本 Miniconda2-latest-Linux-x86_64.sh 打开终端输入安装命令 sudo sh Miniconda2-latest-Linux-x86_64.sh 然后按提示安装,注意安装位置 …...
Redis语法大全
一、String(字符串) 特点:单键值存储,值可为字符串、数字,支持原子操作。 常用命令 SET 语法:SET key value [EX seconds] [PX milliseconds] [NX|XX]说明:设置键值对,可指定过期时…...

OpenAI宣布:核心API支持MCP,助力智能体开发
今天凌晨,OpenAI全资收购io的消息成为头条。同时,OpenAI还宣布其核心API——Responses API支持MCP服务。过去,开发智能体需通过函数调用与外部服务交互,过程复杂且延迟高。而今,Responses API支持MCP后,开发…...
我的爬虫夜未眠:一场与IP限流的攻防战
深夜的办公室里,键盘声此起彼伏,屏幕的蓝光映在程序员的脸上。我揉了揉酸胀的眼睛,第8次刷新日志页面——依旧是刺眼的“429 Too Many Requests”(请求过多)。这是本月第三次因为IP被目标网站封禁而被迫中断爬虫任务了…...
git:The following paths are ignored by one of your
遇到错误: The following paths are ignored by one of your .gitignore files: www hint: Use -f if you really want to add them. 说明:Git 拒绝添加 www/html/index.php,因为你的 .gitignore 中忽略了整个 www/ 目录(即 ww…...
算法--js--组合总和
题:给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。candidates 中的 同一个 数字可以 无限制重复…...

微服务中的 AKF 拆分原则:构建可扩展系统的核心方法论
在数字化浪潮的推动下,互联网应用规模呈指数级增长,传统单体架构逐渐暴露出难以扩展、维护成本高等问题,微服务架构应运而生并成为企业应对复杂业务场景的主流选择。然而,随着业务的不断扩张和用户量的持续增加,如何确…...

vue element-plus 集成多语言
main.js中 // 引入i18n import i18n from /i18n/index 使用i18 app.use(i18n) 在App.vue中 <template><el-config-provider :locale"locale" namespace"el" size"small"><router-view /></el-config-provider> </tem…...

如何测试JWT的安全性:全面防御JSON Web Token的安全漏洞
在当今的Web应用安全领域,JSON Web Token(JWT)已成为身份认证的主流方案,但OWASP统计显示,错误配置的JWT导致的安全事件占比高达42%。本文将系统性地介绍JWT安全测试的方法论,通过真实案例剖析典型漏洞,帮助我们构建全…...

车载网关策略 --- 车载网关重置前的请求转发机制
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 钝感力的“钝”,不是木讷、迟钝,而是直面困境的韧劲和耐力,是面对外界噪音的通透淡然。 生活中有两种人,一种人格外在意别人的眼光;另一种人无论…...

EtpBot:安卓自动化脚本开发神器
EtpBot 是什么? EtpBot是一款专为安卓设备设计的自动化脚本开发工具,支持用户通过编写脚本实现自动化操作。该模块提供了丰富的API接口,涵盖点击、滑动、输入、截图等常见操作,帮助开发者快速构建自动化任务。ETPBot支持多设备并行…...

连锁企业管理系统对门店运营的促进作用
连锁企业管理系统通过整合数字化工具与流程优化,能从多维度提升门店运营效率与竞争力,以下是其对门店运营的具体促进作用: 一、数据化管理:精准决策与运营监控 实时数据同步与分析 系统可整合各门店销售数据、库存信息、客流统计…...

现代生活健康养生新策略
在充满挑战的现代生活中,各种健康问题悄然来袭,亚健康状态困扰着不少人。摒弃中医概念,运用现代科学理念,也能找到行之有效的养生之道。 饮食上,遵循 “彩虹饮食法” 能让营养摄入更全面。不同颜色的蔬果富含不同的…...

车载以太网网络测试-27【SOME/IP-SD简述】
文章目录 1 摘要2 SOME/IP-SD协议介绍2.1 定义与作用2.2 SOMEIP/SD协议通俗易懂的理解2.2.1 SOMEIP/SD协议是什么?2.2.2 通信流程(简化)2.2.3 车载功能示例2.2.4 类比理解 2.3 SOME/IP-SD报文结构2.3.1 Flags2.3.1.1 REBOOT (Bit 7)2.3.1.2 U…...
云南安全员考试报名需要具备哪些条件?
云南安全员考试分为 A、B、C 三类,不同类别报名条件有所不同,具体如下: 安全员 A 证 年龄 18 周岁以上。具有中专及以上的文化程度、中级及以上专业技术职称(法定代表人除外)。其中分管安全生产的副总经理(…...
Android Binder线程池饥饿与TransactionException:从零到企业级解决方案(含实战代码+调试技巧)
简介 在Android系统中,Binder作为进程间通信(IPC)的核心机制,承载着大量跨进程调用任务。然而,当Binder线程池资源耗尽时,可能导致严重的线程饥饿问题,最终引发TransactionException异常,甚至导致应用崩溃或系统卡顿。本文将从零开始,系统讲解Binder线程池的工作原理…...
FFmpeg 超级详细安装与配置教程(Windows 系统)
FFmpeg 是一款功能强大的开源多媒体处理工具集,能够进行音视频的编码、解码、转码、混流、推流、滤镜、格式转换等多种操作。本教程将详细介绍如何在 Windows 系统上安装和配置 FFmpeg,并提供一些常用的使用示例,助你从入门到基本掌握。 目录…...

【Redis8】最新安装版与手动运行版
1. 下载 Redis 百度网盘 2. 解压后直接运行 redis-server.exe 3. 使用安装版 双击 install_redis_service.bat 输入安装路径(请提前创建好安装路径)后直接回车下一步直接回车即可,因为是使用配置模板文件为默认解压出来的,然后…...
PyQt 探索QMainWindow:打造专业的PyQt5主窗
在PyQt5的世界里,窗口的创建和管理是构建图形用户界面(GUI)的基础。QMainWindow作为主窗口类,为开发者提供了强大而灵活的应用程序框架。今天,就让我们一起深入了解QMainWindow的奥秘。 QMainWindow简介 QMainWindow…...

Spring Boot 集成 Elasticsearch【实战】
前言: 上一篇我们简单分享了 Elasticsearch 的一些概念性的知识,本篇我们来分享 Elasticsearch 的实际运用,也就是在 Spring Booot 项目中使用 Elasticsearch。 Elasticsearch 系列文章传送门 Elasticsearch 基础篇【ES】 Elasticsearch …...

06算法学习_58. 区间和
58. 区间和 06算法学习_58. 区间和题目描述:个人代码:学习思路:第一种写法:题解关键点: 个人学习时疑惑点解答: 06算法学习_58. 区间和 卡码网题目链接: 59. 螺旋矩阵 II 题目描述: 58. 区间…...
如何在Java中进行PDF合并
引言 Java 开发者在处理 PDF 文档时,常常需要增强文档工作流的功能。市场上有多种 Java PDF SDK 库可供选择,其中一项关键功能就是 PDF 合并。 PDF 合并在许多场景中都非常重要,例如: 1 优化用户下载流程 2 合并多份报告 3…...

Python爬虫之路(14)--playwright浏览器自动化
playwright 前言 你有没有在用 Selenium 抓网页的时候,体验过那种「明明点了按钮,它却装死不动」的痛苦?或者那种「刚加载完页面,它又刷新了」的抓狂?别担心,你不是一个人——那是 Selenium 在和现代前…...

Python开启智能之眼:OpenCV+深度学习实战
开篇导言 场景痛点 "某汽车零部件厂每月因人工质检遗漏损失300万,直到部署了基于Python的视觉检测系统..." 传统质检效率低下、成本高昂 深度学习技术带来的产业变革 Python在视觉识别领域的独特优势 一、技术架构解析 1.1 系统组成模块 图表 代码 下载 检测结…...