SpringCache 緩存使用方案總結
近期在基于 SpringBoot 3.x 集成最新版 JetCache 時,發(fā)現(xiàn)該組件對 SpringBoot 3.x 的支持并不完善。最突出的問題是:使用 @Cached 注解配置本地緩存時,以變量作為緩存 key 的方式完全不生效,經過多輪排查和資料查閱后仍未找到有效解決方案。因此,我最終決定采用 SpringCache 作為替代方案,該方案能夠與 SpringBoot 3.x 完美兼容。
一、純本地緩存(Caffeine)完整版
1)依賴(正確版)
<dependencies>
<!-- Spring Cache 核心 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- Caffeine 本地緩存 -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
</dependencies>2)配置類(直接用)
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;
@Configuration
@EnableCaching // 開啟緩存
public class LocalCacheConfig {
@Bean
public CaffeineCacheManager cacheManager() {
CaffeineCacheManager manager = new CaffeineCacheManager();
manager.setCaffeine(Caffeine.newBuilder()
.expireAfterWrite(60, TimeUnit.SECONDS) // 緩存60秒過期
.maximumSize(10000) // 最多存1萬條
);
return manager;
}
}3)本地緩存 · 增刪改查(帶參數(shù)解釋)
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
public class SysUserService {
// ==================== 查詢 ====================
/**
* @Cacheable 查詢緩存:先查緩存,沒有才執(zhí)行方法
* value = 緩存名稱(必須)
* key = 緩存key,#p0 = 第一個參數(shù)
* unless = 結果為null時不緩存(防穿透)
* sync = true 并發(fā)只放一個請求查庫(防擊穿)
*/
@Cacheable(value = "sysUser", key = "#p0", unless = "#result == null", sync = true)
public SysUserDetailResponse getUserById(Long userId) {
System.out.println("查詢數(shù)據(jù)庫");
return new SysUserDetailResponse();
}
// ==================== 新增 ====================
/**
* @CachePut 新增/更新緩存:方法一定執(zhí)行,結果寫入緩存
* key = #result.id 用返回對象的id做緩存key
*/
@CachePut(value = "sysUser", key = "#result.id", unless = "#result == null")
public SysUserDetailResponse createUser(SysUserAddRequest request) {
System.out.println("新增數(shù)據(jù)庫");
SysUserDetailResponse user = new SysUserDetailResponse();
user.setId(100L);
return user;
}
// ==================== 修改 ====================
@CachePut(value = "sysUser", key = "#p0.id", unless = "#result == null")
public SysUserDetailResponse updateUser(SysUserUpdateRequest request) {
System.out.println("修改數(shù)據(jù)庫");
return new SysUserDetailResponse();
}
// ==================== 刪除 ====================
/**
* @CacheEvict 刪除緩存
* allEntries = false(默認)刪單個key
*/
@CacheEvict(value = "sysUser", key = "#p0")
public void deleteUser(Long userId) {
System.out.println("刪除數(shù)據(jù)庫");
}
// 清空整個緩存
@CacheEvict(value = "sysUser", allEntries = true)
public void clearAllUserCache() {
}
}二、本地 + 遠程(Redis)混合緩存完整版
1)依賴
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
</dependencies>2)application.yml
spring:
redis:
host: 127.0.0.1
port: 6379
database: 03)混合緩存配置(本地優(yōu)先 → Redis)
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCache;
import org.springframework.cache.support.CompositeCacheManager;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Configuration
@EnableCaching
public class HybridCacheConfig {
// 本地緩存
@Bean
public SimpleCacheManager localCacheManager() {
SimpleCacheManager manager = new SimpleCacheManager();
manager.setCaches(List.of(new CaffeineCache("sysUser",
Caffeine.newBuilder()
.expireAfterWrite(60, TimeUnit.SECONDS)
.maximumSize(10000)
.build()
)));
return manager;
}
// Redis緩存
@Bean
public RedisCacheManager redisCacheManager(RedisConnectionFactory factory) {
var serializer = new GenericJackson2JsonRedisSerializer();
var config = org.springframework.data.redis.cache.RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(java.time.Duration.ofSeconds(300))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer));
return RedisCacheManager.builder(factory).cacheDefaults(config).build();
}
// 組合:先本地 → 再Redis → 最后數(shù)據(jù)庫
@Bean
public CacheManager cacheManager(SimpleCacheManager localCacheManager, RedisCacheManager redisCacheManager) {
CompositeCacheManager composite = new CompositeCacheManager();
composite.setCacheManagers(List.of(localCacheManager, redisCacheManager));
return composite;
}
}三、注解所有參數(shù) · 極簡解釋(一眼看懂)
| 注解 | 參數(shù) | 核心含義 | 常用值示例 |
|---|---|---|---|
| @Cacheable | value | 緩存名稱(必須,用于隔離不同業(yè)務緩存) | “sysUser”、“sysRole” |
| @Cacheable | key | 緩存唯一標識(SpEL表達式) | #p0、#userId、#result.id |
| @Cacheable | unless | 滿足條件時不緩存(防穿透) | #result == null |
| @Cacheable | sync | 并發(fā)僅放行一個請求查庫(防擊穿) | true |
| @CachePut | value | 緩存名稱 | “sysUser” |
| @CachePut | key | 要更新的緩存key | #p0.id、#result.id |
| @CachePut | unless | 結果為null時不更新緩存 | #result == null |
| @CacheEvict | key | 要刪除的緩存key | #p0 |
| @CacheEvict | allEntries | 是否清空該緩存下所有key(默認false) | true、false |
四、最主流、最標準、最推薦的寫法(速記版)
// 查詢(防穿透+防擊穿) @Cacheable(value = "sysUser", key = "#p0", unless = "#result == null", sync = true) // 新增(用返回值ID做key) @CachePut(value = "sysUser", key = "#result.id", unless = "#result == null") // 修改(用入?yún)D做key) @CachePut(value = "sysUser", key = "#p0.id", unless = "#result == null") // 刪除單個key @CacheEvict(value = "sysUser", key = "#p0") // 清空整個緩存 @CacheEvict(value = "sysUser", allEntries = true)
五、一頁紙速記圖(Markdown版)
# Spring Cache 速記表(Spring Boot 3.x) ## 核心依賴 | 緩存類型 | 依賴坐標 | |----------------|--------------------------------------------------------------------------| | 純本地 | spring-boot-starter-cache + com.github.ben-manes:caffeine | | 本地+Redis | 純本地依賴 + spring-boot-starter-data-redis | ## 核心注解 | 操作 | 注解 | 標準寫法 | 核心作用 | |--------|------------|------------------------------------------|------------------------------| | 查詢 | @Cacheable | @Cacheable(value="sysUser",key="#p0",unless="#result==null",sync=true) | 先查緩存,未命中執(zhí)行方法 | | 新增 | @CachePut | @CachePut(value="sysUser",key="#result.id",unless="#result==null") | 執(zhí)行方法后寫入緩存 | | 修改 | @CachePut | @CachePut(value="sysUser",key="#p0.id",unless="#result==null") | 執(zhí)行方法后更新緩存 | | 刪除 | @CacheEvict| @CacheEvict(value="sysUser",key="#p0") | 刪除指定key緩存 | | 清空 | @CacheEvict| @CacheEvict(value="sysUser",allEntries=true) | 清空該緩存下所有key | ## 關鍵參數(shù) | 參數(shù) | 含義 | 避坑點 | |----------|---------------------------------------|---------------------------------| | value | 緩存名稱(必須) | 不同業(yè)務用不同名稱,避免沖突 | | key | 緩存唯一標識 | 優(yōu)先用#p0(參數(shù)索引),不報錯 | | unless | 結果過濾條件 | 必加#result==null,防緩存穿透 | | sync | 并發(fā)控制 | 查詢必加true,防緩存擊穿 | | allEntries| 清空所有key | 僅批量刪除時用,避免誤刪 | ## 配置要點 | 緩存類型 | 配置核心 | |----------------|--------------------------------------------------------------------------| | 純本地(Caffeine) | expireAfterWrite(60s) + maximumSize(10000) | | Redis | JSON序列化 + entryTtl(300s) + key前綴隔離 | | 混合緩存 | CompositeCacheManager,順序:本地 → Redis → 數(shù)據(jù)庫 |
到此這篇關于SpringCache 緩存使用總結的文章就介紹到這了,更多相關SpringCache 緩存使用內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
SpringBoot整合kafka遇到的版本不對應問題及解決
這篇文章主要介紹了SpringBoot整合kafka遇到的版本不對應問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03
Nacos點擊導入配置按鈕無反應nacos配置用戶名密碼實現(xiàn)方式
這篇文章主要介紹了Nacos點擊導入配置按鈕無反應nacos配置用戶名密碼實現(xiàn)方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2026-05-05
SpringBoot+Netty實現(xiàn)簡單聊天室的示例代碼
這篇文章主要介紹了如何利用SpringBoot Netty實現(xiàn)簡單聊天室,文中的示例代碼講解詳細,對我們學習SpringBoot有一定幫助,感興趣的同學可以了解一下2022-02-02

