Spring Cache 深度解析
一、什么是 Spring Cache
Spring Cache 是 Spring 框架提供的声明式缓存抽象层 ,它通过少量注解即可为应用添加缓存能力,无需侵入业务代码 。核心设计目标是解耦缓存实现与业务逻辑,支持在运行时灵活切换 EhCache、Redis、Caffeine、Hazelcast 等缓存实现。
关键特性:
- 声明式编程 :仅通过
@Cacheable、@CacheEvict等注解实现缓存 - 实现无关性:接口抽象,支持多种缓存提供者
- 与 Spring 生态无缝集成:事务、AOP、SpEL 表达式
二、核心注解与生命周期
1. @Cacheable:查询缓存
作用:方法执行前检查缓存,存在则直接返回,不存在则执行方法并将结果存入缓存。
java
@Service
public class UserService {
// 缓存key为用户ID,缓存不存在时查询数据库
@Cacheable(value = "users", key = "#id")
public User getUser(Long id) {
return userMapper.selectById(id); // 仅缓存未命中时执行
}
// 支持条件缓存:仅当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);
}
}
执行流程:
- 解析 SpEL 表达式生成缓存 Key
- 根据
value/cacheNames定位缓存对象 - 查询缓存:
- 命中:直接返回缓存值(方法体不执行)
- 未命中:执行方法 → 将返回值存入缓存 → 返回结果
2. @CachePut:更新缓存
作用 :无论缓存是否存在,都执行方法 ,并将结果更新到缓存。适用于创建或更新操作。
java
@CachePut(value = "users", key = "#user.id")
public User updateUser(User user) {
userMapper.updateById(user);
return user; // 返回值会更新到缓存
}
⚠️ 注意 :@CachePut 与 @Cacheable 不能用在同一方法(逻辑冲突)。
3. @CacheEvict:清除缓存
作用 :删除缓存项,通常用于删除或修改操作。
java
// 删除指定key的缓存
@CacheEvict(value = "users", key = "#id")
public void deleteUser(Long id) {
userMapper.deleteById(id);
}
// 删除整个缓存区的所有条目
@CacheEvict(value = "users", allEntries = true)
public void clearAllUsers() {
// 批量删除后的清理操作
}
// 在方法执行前清除缓存(默认是执行后)
@CacheEvict(value = "users", key = "#id", beforeInvocation = true)
public void deleteUserBefore(Long id) {
userMapper.deleteById(id);
}
4. @Caching:组合操作
作用:单个方法上组合多个缓存操作。
java
@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:类级别统一配置
作用:在类上统一指定缓存名称等公共配置,避免重复书写。
java
@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. 启用缓存
java
@SpringBootApplication
@EnableCaching // 启用缓存功能
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
2. 配置 Caffeine 本地缓存(推荐)
java
@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()); // 开启统计
// 为特定缓存区定制配置
cacheManager.registerCustomCache("users",
Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterAccess(30, TimeUnit.MINUTES)
.build());
return cacheManager;
}
}
3. 配置 Redis 分布式缓存
java
@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 生成策略
默认策略
- 无参数:
SimpleKey.EMPTY - 单个参数:参数值作为 Key(如
"100") - 多个参数:
SimpleKey [arg1, arg2, ...]
自定义 KeyGenerator
java
@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(":");
// 自定义参数序列化逻辑
return sb.toString();
};
}
}
五、应用场景与解决方案
1. 缓存穿透(Cache Penetration)
场景 :查询不存在的数据,大量请求直达数据库。
解决方案:
java
@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)
场景:热点数据过期瞬间,大量请求涌入数据库。
解决方案:
java
// 设置热点数据永不过期 + 后台异步更新
@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)
场景:大量缓存同时过期,数据库压力骤增。
解决方案:
yaml
# application.yml
spring:
cache:
caffeine:
spec: maximumSize=1000,expireAfterWrite=30m,expireAfterAccess=25m
# 设置随机过期时间,避免集中失效
# expireAfterWrite=30m,randomTime=5m
4. 缓存与事务一致性
问题:事务未提交,缓存已更新,导致脏读。
解决方案:
java
@Transactional
@CacheEvict(value = "users", key = "#id", beforeInvocation = true)
public void updateUser(Long id, User user) {
// beforeInvocation=true 确保事务回滚时缓存也被清除
userMapper.updateById(user);
}
六、缓存监控与统计
1. 开启 Caffeine 统计
java
Caffeine.newBuilder()
.recordStats()
.build();
2. 暴露监控端点
java
@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 端点,可查看和清除缓存。
七、最佳实践总结
✅ 推荐使用
- 优先本地缓存:Caffeine(性能最优)或 EhCache
- 分布式场景:Redis(集群高可用)
- Key 设计 :
类名:方法名:参数格式,如user:100 - TTL 策略:热点数据短过期,冷数据长过期
- 异常处理:缓存操作不应影响主流程,捕获异常并降级
java
@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 必须唯一:避免不同方法数据覆盖
- 慎用
@CachePut:确保返回值为完整数据,避免缓存不完整对象 - 序列化成本:Redis 缓存注意对象序列化开销
- 缓存一致性:更新操作必须清除相关缓存
- 大对象缓存:评估内存占用,避免 OOM
📊 性能对比
| 缓存类型 | 读取性能 | 写入性能 | 适用场景 |
|---|---|---|---|
| Caffeine | 极高(本地) | 极高 | 高频读、数据量适中 |
| Redis | 高(网络) | 高 | 分布式、数据共享 |
| EhCache | 高 | 中 | 传统项目、功能丰富 |
Spring Cache 通过统一的抽象层,让开发者以极简的注解实现强大的缓存能力,是提升应用性能的必备利器。