Spring Cache 最佳實(shí)踐總結(jié)
Spring Cache 深度解析
一、什么是 Spring Cache
Spring Cache 是 Spring 框架提供的聲明式緩存抽象層,它通過少量注解即可為應(yīng)用添加緩存能力,無需侵入業(yè)務(wù)代碼。核心設(shè)計(jì)目標(biāo)是解耦緩存實(shí)現(xiàn)與業(yè)務(wù)邏輯,支持在運(yùn)行時(shí)靈活切換 EhCache、Redis、Caffeine、Hazelcast 等緩存實(shí)現(xiàn)。
關(guān)鍵特性:
- 聲明式編程:僅通過
@Cacheable、@CacheEvict等注解實(shí)現(xiàn)緩存 - 實(shí)現(xiàn)無關(guān)性:接口抽象,支持多種緩存提供者
- 與 Spring 生態(tài)無縫集成:事務(wù)、AOP、SpEL 表達(dá)式
二、核心注解與生命周期
1.@Cacheable:查詢緩存
作用:方法執(zhí)行前檢查緩存,存在則直接返回,不存在則執(zhí)行方法并將結(jié)果存入緩存。
@Service
public class UserService {
// 緩存key為用戶ID,緩存不存在時(shí)查詢數(shù)據(jù)庫
@Cacheable(value = "users", key = "#id")
public User getUser(Long id) {
return userMapper.selectById(id); // 僅緩存未命中時(shí)執(zhí)行
}
// 支持條件緩存:僅當(dāng)id > 100時(shí)緩存
@Cacheable(value = "users", key = "#id", condition = "#id > 100")
public User getVipUser(Long id) {
return userMapper.selectById(id);
}
// 使用SpEL生成key:如 "user:100:admin"
@Cacheable(value = "users", key = "'user:' + #id + ':' + #type")
public User getUserByType(Long id, String type) {
return userMapper.selectByIdAndType(id, type);
}
}執(zhí)行流程:
- 解析 SpEL 表達(dá)式生成緩存 Key
- 根據(jù)
value/cacheNames定位緩存對(duì)象 - 查詢緩存:
- 命中:直接返回緩存值(方法體不執(zhí)行)
- 未命中:執(zhí)行方法 → 將返回值存入緩存 → 返回結(jié)果
2.@CachePut:更新緩存
作用:無論緩存是否存在,都執(zhí)行方法,并將結(jié)果更新到緩存。適用于創(chuàng)建或更新操作。
@CachePut(value = "users", key = "#user.id")
public User updateUser(User user) {
userMapper.updateById(user);
return user; // 返回值會(huì)更新到緩存
}?? 注意:@CachePut 與 @Cacheable 不能用在同一方法(邏輯沖突)。
3.@CacheEvict:清除緩存
作用:刪除緩存項(xiàng),通常用于刪除或修改操作。
// 刪除指定key的緩存
@CacheEvict(value = "users", key = "#id")
public void deleteUser(Long id) {
userMapper.deleteById(id);
}
// 刪除整個(gè)緩存區(qū)的所有條目
@CacheEvict(value = "users", allEntries = true)
public void clearAllUsers() {
// 批量刪除后的清理操作
}
// 在方法執(zhí)行前清除緩存(默認(rèn)是執(zhí)行后)
@CacheEvict(value = "users", key = "#id", beforeInvocation = true)
public void deleteUserBefore(Long id) {
userMapper.deleteById(id);
}4.@Caching:組合操作
作用:?jiǎn)蝹€(gè)方法上組合多個(gè)緩存操作。
@Caching(
put = {
@CachePut(value = "users", key = "#user.id"),
@CachePut(value = "users", key = "#user.email")
},
evict = {
@CacheEvict(value = "userList", allEntries = true)
}
)
public User createUser(User user) {
userMapper.insert(user);
return user;
}5.@CacheConfig:類級(jí)別統(tǒng)一配置
作用:在類上統(tǒng)一指定緩存名稱等公共配置,避免重復(fù)書寫。
@Service
@CacheConfig(cacheNames = "users")
public class UserService {
@Cacheable(key = "#id") // 無需重復(fù)指定value
public User getUser(Long id) { /*...*/ }
@CacheEvict(key = "#id")
public void deleteUser(Long id) { /*...*/ }
}三、緩存管理器配置
1. 啟用緩存
@SpringBootApplication
@EnableCaching // 啟用緩存功能
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
2. 配置 Caffeine 本地緩存(推薦)
@Configuration
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
// 全局默認(rèn)配置
cacheManager.setCaffeine(Caffeine.newBuilder()
.initialCapacity(100)
.maximumSize(500)
.expireAfterWrite(10, TimeUnit.MINUTES)
.recordStats()); // 開啟統(tǒng)計(jì)
// 為特定緩存區(qū)定制配置
cacheManager.registerCustomCache("users",
Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterAccess(30, TimeUnit.MINUTES)
.build());
return cacheManager;
}
}3. 配置 Redis 分布式緩存
@Configuration
public class RedisCacheConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration
.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30)) // 默認(rèn)30分鐘過期
.serializeKeysWith(
RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(
RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.withCacheConfiguration("users",
RedisCacheConfiguration
.defaultCacheConfig()
.entryTtl(Duration.ofHours(2))) // users緩存2小時(shí)過期
.build();
}
}Spring Boot 自動(dòng)配置:
- 引入
spring-boot-starter-data-redis自動(dòng)配置RedisCacheManager - 引入
spring-boot-starter-cache+caffeine自動(dòng)配置CaffeineCacheManager
四、緩存 Key 生成策略
默認(rèn)策略
- 無參數(shù):
SimpleKey.EMPTY - 單個(gè)參數(shù):參數(shù)值作為 Key(如
"100") - 多個(gè)參數(shù):
SimpleKey [arg1, arg2, ...]
自定義 KeyGenerator
@Configuration
public class CacheConfig implements CachingConfigurer {
@Bean
@Override
public KeyGenerator keyGenerator() {
return (target, method, params) -> {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getSimpleName())
.append(":")
.append(method.getName())
.append(":");
// 自定義參數(shù)序列化邏輯
return sb.toString();
};
}
}五、應(yīng)用場(chǎng)景與解決方案
1. 緩存穿透(Cache Penetration)
場(chǎng)景:查詢不存在的數(shù)據(jù),大量請(qǐng)求直達(dá)數(shù)據(jù)庫。
解決方案:
@Cacheable(value = "users", key = "#id", unless = "#result == null")
public User findUser(Long id) {
return userMapper.selectById(id);
}
// 或使用空值緩存
@Cacheable(value = "users", key = "#id")
public User findUser(Long id) {
User user = userMapper.selectById(id);
return user == null ? User.NULL : user; // 緩存空對(duì)象
}2. 緩存擊穿(Cache Breakdown)
場(chǎng)景:熱點(diǎn)數(shù)據(jù)過期瞬間,大量請(qǐng)求涌入數(shù)據(jù)庫。
解決方案:
// 設(shè)置熱點(diǎn)數(shù)據(jù)永不過期 + 后臺(tái)異步更新
@Cacheable(value = "hotData", key = "#key")
public HotData getHotData(String key) {
return loadFromDatabase(key);
}
// 或使用分布式鎖(Redisson)
@Cacheable(value = "users", key = "#id")
public User getUserWithLock(Long id) {
RLock lock = redissonClient.getLock("user:lock:" + id);
try {
if (lock.tryLock(100, 10, TimeUnit.SECONDS)) {
return userMapper.selectById(id);
}
} finally {
lock.unlock();
}
return null;
}3. 緩存雪崩(Cache Avalanche)
場(chǎng)景:大量緩存同時(shí)過期,數(shù)據(jù)庫壓力驟增。
解決方案:
# application.yml
spring:
cache:
caffeine:
spec: maximumSize=1000,expireAfterWrite=30m,expireAfterAccess=25m
# 設(shè)置隨機(jī)過期時(shí)間,避免集中失效
# expireAfterWrite=30m,randomTime=5m4. 緩存與事務(wù)一致性
問題:事務(wù)未提交,緩存已更新,導(dǎo)致臟讀。
解決方案:
@Transactional
@CacheEvict(value = "users", key = "#id", beforeInvocation = true)
public void updateUser(Long id, User user) {
// beforeInvocation=true 確保事務(wù)回滾時(shí)緩存也被清除
userMapper.updateById(user);
}
六、緩存監(jiān)控與統(tǒng)計(jì)
1. 開啟 Caffeine 統(tǒng)計(jì)
Caffeine.newBuilder()
.recordStats()
.build();
2. 暴露監(jiān)控端點(diǎn)
@Component
public class CacheMetrics {
@Autowired
private CacheManager cacheManager;
@Scheduled(fixedRate = 60000)
public void printStats() {
Cache usersCache = cacheManager.getCache("users");
if (usersCache.getNativeCache() instanceof com.github.benmanes.caffeine.cache.Cache) {
com.github.benmanes.caffeine.cache.Cache nativeCache =
(com.github.benmanes.caffeine.cache.Cache) usersCache.getNativeCache();
System.out.println("緩存命中率: " + nativeCache.stats().hitRate());
}
}
}Spring Boot Actuator 自動(dòng)暴露 /actuator/caches 端點(diǎn),可查看和清除緩存。
七、最佳實(shí)踐總結(jié)
?推薦使用
- 優(yōu)先本地緩存:Caffeine(性能最優(yōu))或 EhCache
- 分布式場(chǎng)景:Redis(集群高可用)
- Key 設(shè)計(jì):
類名:方法名:參數(shù)格式,如user:100 - TTL 策略:熱點(diǎn)數(shù)據(jù)短過期,冷數(shù)據(jù)長(zhǎng)過期
- 異常處理:緩存操作不應(yīng)影響主流程,捕獲異常并降級(jí)
@Cacheable(value = "users", key = "#id")
public User getUser(Long id) {
try {
return userMapper.selectById(id);
} catch (Exception e) {
log.error("查詢用戶失敗", e);
return User.EMPTY; // 返回空對(duì)象避免緩存穿透
}
}??注意事項(xiàng)
- 緩存 Key 必須唯一:避免不同方法數(shù)據(jù)覆蓋
- 慎用
@CachePut:確保返回值為完整數(shù)據(jù),避免緩存不完整對(duì)象 - 序列化成本:Redis 緩存注意對(duì)象序列化開銷
- 緩存一致性:更新操作必須清除相關(guān)緩存
- 大對(duì)象緩存:評(píng)估內(nèi)存占用,避免 OOM
??性能對(duì)比
| 緩存類型 | 讀取性能 | 寫入性能 | 適用場(chǎng)景 |
|---|---|---|---|
| Caffeine | 極高(本地) | 極高 | 高頻讀、數(shù)據(jù)量適中 |
| Redis | 高(網(wǎng)絡(luò)) | 高 | 分布式、數(shù)據(jù)共享 |
| EhCache | 高 | 中 | 傳統(tǒng)項(xiàng)目、功能豐富 |
Spring Cache 通過統(tǒng)一的抽象層,讓開發(fā)者以極簡(jiǎn)的注解實(shí)現(xiàn)強(qiáng)大的緩存能力,是提升應(yīng)用性能的必備利器。
到此這篇關(guān)于Spring Cache 最佳實(shí)踐總結(jié)的文章就介紹到這了,更多相關(guān)Spring Cache深度解析內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- springboot中JetCache的使用方法小結(jié)
- Spring全家桶中@CacheEvict無效情況的問題解決
- 對(duì)spring的@Cacheable緩存的使用理解
- Spring Cache用法及常見問題解決方案
- Caffeine本地緩存核心原理與使用方法詳解(含與Spring?Cache集成)
- SpringBoot整合SpringCache緩存的實(shí)現(xiàn)示例
- Spring Boot 緩存注解@Cacheable、@CachePut、@CacheEvict超詳細(xì)實(shí)戰(zhàn)
- Spring?Cache注解@Cacheable的九個(gè)屬性詳解
- spring框架中的本地緩存之spring cache基本使用詳解
相關(guān)文章
JavaWeb-WebSocket瀏覽器服務(wù)器雙向通信方式
文章介紹了WebSocket協(xié)議的工作原理和應(yīng)用場(chǎng)景,包括與HTTP的對(duì)比,接著,詳細(xì)介紹了如何在Java中使用WebSocket,包括配置類、服務(wù)編寫和前端頁面的實(shí)現(xiàn)2025-02-02
springboot接口如何多次獲取request中的body內(nèi)容
這篇文章主要介紹了springboot接口多次獲取request中的body內(nèi)容的過程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06
Java 實(shí)戰(zhàn)項(xiàng)目錘煉之網(wǎng)上花店商城的實(shí)現(xiàn)流程
讀萬卷書不如行萬里路,只學(xué)書上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實(shí)戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用java+jsp+servlet+mysql+ajax實(shí)現(xiàn)一個(gè)網(wǎng)上花店商城系統(tǒng),大家可以在過程中查缺補(bǔ)漏,提升水平2021-11-11
springsecurity?登錄認(rèn)證流程分析一(ajax)
這篇文章主要介紹了springsecurity?登錄認(rèn)證一(ajax篇),本文通過實(shí)例代碼圖文相結(jié)合給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-08-08
Java實(shí)現(xiàn)Excel表單控件的添加與刪除
本文通過Java代碼示例介紹如何在Excel表格中添加表單控件,包括文本框、單選按鈕、復(fù)選框、組合框、微調(diào)按鈕等,以及如何刪除Excel中的指定表單控件,需要的可以參考一下2022-05-05
基于SpringBoot應(yīng)用監(jiān)控Actuator安全隱患及解決方式
這篇文章主要介紹了SpringBoot應(yīng)用監(jiān)控Actuator安全隱患及解決方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07
Java多線程實(shí)現(xiàn)之Executor詳解
這篇文章主要介紹了Java多線程實(shí)現(xiàn)之Executor詳解,Executor 給他一個(gè) Runnable,他就能自動(dòng)很安全的幫你把這個(gè)線程執(zhí)行完畢2023-08-08
Executor 通過創(chuàng)建線程池的方式來管理線程,需要的朋友可以參考下

