Spring Cache 最佳實踐總結
Spring Cache 深度解析
一、什么是 Spring Cache
Spring Cache 是 Spring 框架提供的聲明式緩存抽象層,它通過少量注解即可為應用添加緩存能力,無需侵入業(yè)務代碼。核心設計目標是解耦緩存實現(xiàn)與業(yè)務邏輯,支持在運行時靈活切換 EhCache、Redis、Caffeine、Hazelcast 等緩存實現(xiàn)。
關鍵特性:
- 聲明式編程:僅通過
@Cacheable、@CacheEvict等注解實現(xiàn)緩存 - 實現(xiàn)無關性:接口抽象,支持多種緩存提供者
- 與 Spring 生態(tài)無縫集成:事務、AOP、SpEL 表達式
二、核心注解與生命周期
1.@Cacheable:查詢緩存
作用:方法執(zhí)行前檢查緩存,存在則直接返回,不存在則執(zhí)行方法并將結果存入緩存。
@Service
public class UserService {
// 緩存key為用戶ID,緩存不存在時查詢數(shù)據(jù)庫
@Cacheable(value = "users", key = "#id")
public User getUser(Long id) {
return userMapper.selectById(id); // 僅緩存未命中時執(zhí)行
}
// 支持條件緩存:僅當id > 100時緩存
@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 表達式生成緩存 Key
- 根據(jù)
value/cacheNames定位緩存對象 - 查詢緩存:
- 命中:直接返回緩存值(方法體不執(zhí)行)
- 未命中:執(zhí)行方法 → 將返回值存入緩存 → 返回結果
2.@CachePut:更新緩存
作用:無論緩存是否存在,都執(zhí)行方法,并將結果更新到緩存。適用于創(chuàng)建或更新操作。
@CachePut(value = "users", key = "#user.id")
public User updateUser(User user) {
userMapper.updateById(user);
return user; // 返回值會更新到緩存
}?? 注意:@CachePut 與 @Cacheable 不能用在同一方法(邏輯沖突)。
3.@CacheEvict:清除緩存
作用:刪除緩存項,通常用于刪除或修改操作。
// 刪除指定key的緩存
@CacheEvict(value = "users", key = "#id")
public void deleteUser(Long id) {
userMapper.deleteById(id);
}
// 刪除整個緩存區(qū)的所有條目
@CacheEvict(value = "users", allEntries = true)
public void clearAllUsers() {
// 批量刪除后的清理操作
}
// 在方法執(zhí)行前清除緩存(默認是執(zhí)行后)
@CacheEvict(value = "users", key = "#id", beforeInvocation = true)
public void deleteUserBefore(Long id) {
userMapper.deleteById(id);
}4.@Caching:組合操作
作用:單個方法上組合多個緩存操作。
@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:類級別統(tǒng)一配置
作用:在類上統(tǒng)一指定緩存名稱等公共配置,避免重復書寫。
@Service
@CacheConfig(cacheNames = "users")
public class UserService {
@Cacheable(key = "#id") // 無需重復指定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();
// 全局默認配置
cacheManager.setCaffeine(Caffeine.newBuilder()
.initialCapacity(100)
.maximumSize(500)
.expireAfterWrite(10, TimeUnit.MINUTES)
.recordStats()); // 開啟統(tǒng)計
// 為特定緩存區(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)) // 默認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小時過期
.build();
}
}Spring Boot 自動配置:
- 引入
spring-boot-starter-data-redis自動配置RedisCacheManager - 引入
spring-boot-starter-cache+caffeine自動配置CaffeineCacheManager
四、緩存 Key 生成策略
默認策略
- 無參數(shù):
SimpleKey.EMPTY - 單個參數(shù):參數(shù)值作為 Key(如
"100") - 多個參數(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();
};
}
}五、應用場景與解決方案
1. 緩存穿透(Cache Penetration)
場景:查詢不存在的數(shù)據(jù),大量請求直達數(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; // 緩存空對象
}2. 緩存擊穿(Cache Breakdown)
場景:熱點數(shù)據(jù)過期瞬間,大量請求涌入數(shù)據(jù)庫。
解決方案:
// 設置熱點數(shù)據(jù)永不過期 + 后臺異步更新
@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)
場景:大量緩存同時過期,數(shù)據(jù)庫壓力驟增。
解決方案:
# application.yml
spring:
cache:
caffeine:
spec: maximumSize=1000,expireAfterWrite=30m,expireAfterAccess=25m
# 設置隨機過期時間,避免集中失效
# expireAfterWrite=30m,randomTime=5m4. 緩存與事務一致性
問題:事務未提交,緩存已更新,導致臟讀。
解決方案:
@Transactional
@CacheEvict(value = "users", key = "#id", beforeInvocation = true)
public void updateUser(Long id, User user) {
// beforeInvocation=true 確保事務回滾時緩存也被清除
userMapper.updateById(user);
}
六、緩存監(jiān)控與統(tǒng)計
1. 開啟 Caffeine 統(tǒng)計
Caffeine.newBuilder()
.recordStats()
.build();
2. 暴露監(jiā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 自動暴露 /actuator/caches 端點,可查看和清除緩存。
七、最佳實踐總結
?推薦使用
- 優(yōu)先本地緩存:Caffeine(性能最優(yōu))或 EhCache
- 分布式場景:Redis(集群高可用)
- Key 設計:
類名:方法名:參數(shù)格式,如user:100 - TTL 策略:熱點數(shù)據(jù)短過期,冷數(shù)據(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; // 返回空對象避免緩存穿透
}
}??注意事項
- 緩存 Key 必須唯一:避免不同方法數(shù)據(jù)覆蓋
- 慎用
@CachePut:確保返回值為完整數(shù)據(jù),避免緩存不完整對象 - 序列化成本:Redis 緩存注意對象序列化開銷
- 緩存一致性:更新操作必須清除相關緩存
- 大對象緩存:評估內(nèi)存占用,避免 OOM
??性能對比
| 緩存類型 | 讀取性能 | 寫入性能 | 適用場景 |
|---|---|---|---|
| Caffeine | 極高(本地) | 極高 | 高頻讀、數(shù)據(jù)量適中 |
| Redis | 高(網(wǎng)絡) | 高 | 分布式、數(shù)據(jù)共享 |
| EhCache | 高 | 中 | 傳統(tǒng)項目、功能豐富 |
Spring Cache 通過統(tǒng)一的抽象層,讓開發(fā)者以極簡的注解實現(xiàn)強大的緩存能力,是提升應用性能的必備利器。
到此這篇關于Spring Cache 最佳實踐總結的文章就介紹到這了,更多相關Spring Cache深度解析內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
- springboot中JetCache的使用方法小結
- Spring全家桶中@CacheEvict無效情況的問題解決
- 對spring的@Cacheable緩存的使用理解
- Spring Cache用法及常見問題解決方案
- Caffeine本地緩存核心原理與使用方法詳解(含與Spring?Cache集成)
- SpringBoot整合SpringCache緩存的實現(xiàn)示例
- Spring Boot 緩存注解@Cacheable、@CachePut、@CacheEvict超詳細實戰(zhàn)
- Spring?Cache注解@Cacheable的九個屬性詳解
- spring框架中的本地緩存之spring cache基本使用詳解
相關文章
springboot接口如何多次獲取request中的body內(nèi)容
這篇文章主要介紹了springboot接口多次獲取request中的body內(nèi)容的過程,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-06-06
Java 實戰(zhàn)項目錘煉之網(wǎng)上花店商城的實現(xiàn)流程
讀萬卷書不如行萬里路,只學書上的理論是遠遠不夠的,只有在實戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用java+jsp+servlet+mysql+ajax實現(xiàn)一個網(wǎng)上花店商城系統(tǒng),大家可以在過程中查缺補漏,提升水平2021-11-11
springsecurity?登錄認證流程分析一(ajax)
這篇文章主要介紹了springsecurity?登錄認證一(ajax篇),本文通過實例代碼圖文相結合給大家介紹的非常詳細,感興趣的朋友跟隨小編一起看看吧2024-08-08
基于SpringBoot應用監(jiān)控Actuator安全隱患及解決方式
這篇文章主要介紹了SpringBoot應用監(jiān)控Actuator安全隱患及解決方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07

