news 2026/6/26 12:38:51

【Spring Cache | 让接口性能提升】

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Spring Cache | 让接口性能提升】

🌈个人主页:一条泥憨鱼(欢迎各位大佬莅临)

🎬精选专栏:数据结构与算法,Java ,AI与Agent

前言:

写业务接口的时候,这种事情天天发生:一个查用户信息的方法,每次请求都去摸数据库。接口调用量一大,数据库就哐哐扛。问题是——用户信息半天都不变一次,这些查询全是白干的。

就是把第一次的结果记下来,下次直接用。Spring Cache干的就是这个。

它是个缓存抽象层。你用注解告诉 Spring「这个方法的返回值可以缓存」,具体怎么存、存哪,不用管。

它不是缓存,是缓存的遥控器

新手容易搞混:Spring Cache 不是 Redis,不是 Caffeine,不是任何一种具体的缓存技术。它是个统一接口层,背后可以接不同的缓存实现

- 本地:ConcurrentMapCache(默认,底层是 Map,只适合测试)、Caffeine(正经的高性能本地缓存)
- 分布式:Redis(生产环境主力,多实例共享)

关键在于——业务代码不用动。换个 CacheManager 配置,就能从本地缓存切到 Redis。解耦这件事,才是 Spring Cache 真正值钱的地方。

怎么开

Spring Boot 项目加两个依赖(Redis 为例):

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency>

启动类上拍一个注解,总开关就打开了:

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

四个核心注解

@Cacheable——查

用得最多的一个。先去缓存里找,有就直接返回;没有就执行方法,把结果塞进缓存。

@Cacheable(value = "userCache", key = "#id") }

第一次调 getUserById(1L) 打日志、查库。第二次同样参数,日志不打了,直接走缓存。

@CachePut——更新时刷新

跟 @Cacheable 的区别:它每次都会执行方法,只是顺手把返回值写回缓存。更新操作用这个:

@CachePut(value = "userCache", key = "#user.id") public User updateUser(User user) { userMapper.updateById(user); return user; }

@CacheEvict——删

数据删了、失效了,缓存也得跟着清。不然数据库已经变了,缓存里还是老数据:

@CacheEvict(value = "userCache", key = "#id") public void deleteUser(Long id) { userMapper.deleteById(id); }

也可以直接把整个缓存分组清掉:

@CacheEvict(value = "userCache", allEntries = true) public void clearAllUserCache() { }

@Caching——组合

一个方法要同时操作多个缓存,用这个拼起来:

@Caching( put = { @CachePut(value = "userCache", key = "#user.id") }, evict = { @CacheEvict(value = "userListCache", allEntries = true) } ) public User saveAndRefresh(User user) { userMapper.insert(user); return user; }

底层就是 AOP

跟 @Transactional 一模一样——动态代理

Spring 检测到 Bean 的方法上有缓存注解,就给这个 Bean 包一层代理。调用链路是这样的:

1. 拿 key 去 CacheManager 找缓存
2. 命中→直接返回,原方法不执行
3. 没命中→执行原方法,结果丢进缓存

这也是那个经典坑的来源:同类内部调用,缓存注解直接不生效。

public void doSomething() { this.getUserById(1L); // this 调用,不是代理对象,AOP 被绕过去了 } @Cacheable(value = "userCache", key = "#id") public User getUserById(Long id) { return userMapper.selectById(id); }

this.getUserById() 跳过了代理,AOP 根本没有介入的机会。修法通常是拆到另一个 Bean,或者注入自己的代理对象(AopContext.currentProxy())。

几个实用点

条件缓存:condition 和 unless

// condition:执行前判断,满足才缓存 @Cacheable(value = "userCache", key = "#id", condition = "#id > 0") // unless:执行后判断,满足就不缓存(能拿到返回值 #result) @Cacheable(value = "userCache", key = "#id", unless = "#result == null")

unless 特别有用。null 结果不缓存,不然缓存穿透问题会放大。

自定义 key

参数多的时候用 SpEL 拼:

@Cacheable(value = "orderCache", key = "#userId + '_' + #orderId") public Order getOrder(Long userId, Long orderId) { return orderMapper.selectOrder(userId, orderId); }

过期时间(Redis)

注解上没法直接设,要在 CacheManager 配置里统一处理:

@Bean public RedisCacheManager cacheManager(RedisConnectionFactory factory) { RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofMinutes(30)) .serializeValuesWith(RedisSerializationContext.SerializationPair .fromSerializer(new GenericJackson2JsonRedisSerializer())); return RedisCacheManager.builder(factory).cacheDefaults(config).build(); }

避坑清单

1. 缓存穿透:

大量请求查不存在的数据,每次都绕过缓存打到 DB。用 unless 缓存空对象,或者上前置的布隆过滤器。


2. 缓存雪崩:

一堆 key 同时过期,瞬间流量全压到数据库。TTL 加随机偏移,别让它们集体去世。


3. 数据一致性:

更新数据库忘了清缓存,或者顺序搞反了。常规做法是先更新数据库再删缓存(反过来会有并发问题)。


4. 同类自调用失效:

上面说过了,AOP 只在外部调用时拦截。

总结

Spring Cache 把「要不要缓存」「用什么缓存」拆开了。业务代码只需要几个注解,底层从 ConcurrentMap 换到 Redis 一行业务代码都不用改。理解了 AOP 代理这件事,大部分坑就能绕开。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/26 12:38:20

如何快速掌握阴阳师百鬼夜行自动化脚本:面向游戏玩家的完整指南

如何快速掌握阴阳师百鬼夜行自动化脚本&#xff1a;面向游戏玩家的完整指南 【免费下载链接】OnmyojiAutoScript Onmyoji Auto Script | 阴阳师脚本 项目地址: https://gitcode.com/gh_mirrors/on/OnmyojiAutoScript 阴阳师百鬼夜行自动化脚本是一款专为《阴阳师》玩家设…

作者头像 李华
网站建设 2026/6/26 12:33:33

NXP QN902x BLE芯片驱动开发与低功耗设计实战指南

1. 项目概述与核心价值在物联网和可穿戴设备的设计中&#xff0c;低功耗蓝牙&#xff08;BLE&#xff09;芯片的选择与驱动开发往往是决定项目成败的关键。NXP的QN902x系列SoC&#xff0c;凭借其高度集成的ARM Cortex-M0内核、丰富的模拟与数字外设&#xff0c;以及完整的BLE 4…

作者头像 李华
网站建设 2026/6/26 12:32:53

Nginx ssl_reject_handshake指令实战:彻底隐藏CDN背后的源站IP

1. 项目概述&#xff1a;当CDN“隐身衣”失效&#xff0c;你的源站IP还安全吗&#xff1f;最近在排查一个线上服务的问题时&#xff0c;我遇到了一个典型的场景&#xff1a;一个部署在云服务器上的Web应用&#xff0c;为了提升访问速度和安全性&#xff0c;前面套了一层CDN。理…

作者头像 李华
网站建设 2026/6/26 12:24:26

微信聊天记录导出新境界:用WeChatMsg打造你的专属数字记忆库

微信聊天记录导出新境界&#xff1a;用WeChatMsg打造你的专属数字记忆库 【免费下载链接】WeChatMsg 提取微信聊天记录&#xff0c;将其导出成HTML、Word、CSV文档永久保存&#xff0c;对聊天记录进行分析生成年度聊天报告 项目地址: https://gitcode.com/GitHub_Trending/we…

作者头像 李华
网站建设 2026/6/26 12:22:53

嵌入式GPU性能优化实战:OpenCL与OpenVX在i.MX平台的高效开发指南

1. 项目概述&#xff1a;在嵌入式GPU上榨干每一分性能在嵌入式视觉和AI应用里&#xff0c;我们总在和有限的功耗、紧张的算力以及捉襟见肘的内存带宽作斗争。当你的算法模型在服务器GPU上跑得飞快&#xff0c;移植到像NXP i.MX这样的嵌入式SoC上却可能卡成幻灯片&#xff0c;这…

作者头像 李华