近期在基于 SpringBoot 3.x 集成最新版 JetCache 时,发现该组件对 SpringBoot 3.x 的支持并不完善。最突出的问题是:使用 @Cached 注解配置本地缓存时,以变量作为缓存 key 的方式完全不生效,经过多轮排查和资料查阅后仍未找到有效解决方案。因此,我最终决定采用 SpringCache 作为替代方案,该方案能够与 SpringBoot 3.x 完美兼容。
一、纯本地缓存(Caffeine)完整版
1)依赖(正确版)
xml
<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)配置类(直接用)
java
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)本地缓存 · 增删改查(带参数解释)
java
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 查询缓存:先查缓存,没有才执行方法
* value = 缓存名称(必须)
* key = 缓存key,#p0 = 第一个参数
* unless = 结果为null时不缓存(防穿透)
* sync = true 并发只放一个请求查库(防击穿)
*/
@Cacheable(value = "sysUser", key = "#p0", unless = "#result == null", sync = true)
public SysUserDetailResponse getUserById(Long userId) {
System.out.println("查询数据库");
return new SysUserDetailResponse();
}
// ==================== 新增 ====================
/**
* @CachePut 新增/更新缓存:方法一定执行,结果写入缓存
* key = #result.id 用返回对象的id做缓存key
*/
@CachePut(value = "sysUser", key = "#result.id", unless = "#result == null")
public SysUserDetailResponse createUser(SysUserAddRequest request) {
System.out.println("新增数据库");
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("修改数据库");
return new SysUserDetailResponse();
}
// ==================== 删除 ====================
/**
* @CacheEvict 删除缓存
* allEntries = false(默认)删单个key
*/
@CacheEvict(value = "sysUser", key = "#p0")
public void deleteUser(Long userId) {
System.out.println("删除数据库");
}
// 清空整个缓存
@CacheEvict(value = "sysUser", allEntries = true)
public void clearAllUserCache() {
}
}
二、本地 + 远程(Redis)混合缓存完整版
1)依赖
xml
<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
yaml
spring:
redis:
host: 127.0.0.1
port: 6379
database: 0
3)混合缓存配置(本地优先 → Redis)
java
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 → 最后数据库
@Bean
public CacheManager cacheManager(SimpleCacheManager localCacheManager, RedisCacheManager redisCacheManager) {
CompositeCacheManager composite = new CompositeCacheManager();
composite.setCacheManagers(List.of(localCacheManager, redisCacheManager));
return composite;
}
}
三、注解所有参数 · 极简解释(一眼看懂)
| 注解 | 参数 | 核心含义 | 常用值示例 |
|---|---|---|---|
| @Cacheable | value | 缓存名称(必须,用于隔离不同业务缓存) | "sysUser"、"sysRole" |
| @Cacheable | key | 缓存唯一标识(SpEL表达式) | #p0、#userId、#result.id |
| @Cacheable | unless | 满足条件时不缓存(防穿透) | #result == null |
| @Cacheable | sync | 并发仅放行一个请求查库(防击穿) | 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 |
四、最主流、最标准、最推荐的写法(速记版)
java
// 查询(防穿透+防击穿)
@Cacheable(value = "sysUser", key = "#p0", unless = "#result == null", sync = true)
// 新增(用返回值ID做key)
@CachePut(value = "sysUser", key = "#result.id", unless = "#result == null")
// 修改(用入参ID做key)
@CachePut(value = "sysUser", key = "#p0.id", unless = "#result == null")
// 删除单个key
@CacheEvict(value = "sysUser", key = "#p0")
// 清空整个缓存
@CacheEvict(value = "sysUser", allEntries = true)
五、一页纸速记图(Markdown版)
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) | 先查缓存,未命中执行方法 |
| 新增 | @CachePut | @CachePut(value="sysUser",key="#result.id",unless="#result==null") | 执行方法后写入缓存 |
| 修改 | @CachePut | @CachePut(value="sysUser",key="#p0.id",unless="#result==null") | 执行方法后更新缓存 |
| 删除 | @CacheEvict| @CacheEvict(value="sysUser",key="#p0") | 删除指定key缓存 |
| 清空 | @CacheEvict| @CacheEvict(value="sysUser",allEntries=true) | 清空该缓存下所有key |
## 关键参数
| 参数 | 含义 | 避坑点 |
|----------|---------------------------------------|---------------------------------|
| value | 缓存名称(必须) | 不同业务用不同名称,避免冲突 |
| key | 缓存唯一标识 | 优先用#p0(参数索引),不报错 |
| unless | 结果过滤条件 | 必加#result==null,防缓存穿透 |
| sync | 并发控制 | 查询必加true,防缓存击穿 |
| allEntries| 清空所有key | 仅批量删除时用,避免误删 |
## 配置要点
| 缓存类型 | 配置核心 |
|----------------|--------------------------------------------------------------------------|
| 纯本地(Caffeine) | expireAfterWrite(60s) + maximumSize(10000) |
| Redis | JSON序列化 + entryTtl(300s) + key前缀隔离 |
| 混合缓存 | CompositeCacheManager,顺序:本地 → Redis → 数据库 |