缓存注解实战
1. Spring Cache 概述
Section titled “1. Spring Cache 概述”Spring Cache 是 Spring 提供的缓存抽象层,通过注解声明式地为方法添加缓存,业务代码无需关心缓存的读写细节。底层缓存实现可以是 Redis、Caffeine、Ehcache 等,只需更换配置即可切换,业务代码不变。
方法调用流程(@Cacheable):
第一次调用:请求 ──▶ 查缓存(未命中)──▶ 执行方法 ──▶ 结果写入缓存 ──▶ 返回结果
后续调用:请求 ──▶ 查缓存(命中)──▶ 直接返回缓存值(方法不执行)2. 快速集成
Section titled “2. 快速集成”2.1. 添加依赖
Section titled “2.1. 添加依赖”<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId></dependency>2.2. 开启缓存 + 配置 Redis 序列化
Section titled “2.2. 开启缓存 + 配置 Redis 序列化”@Configuration@EnableCaching // 开启 Spring Cache 注解支持public class CacheConfig {
@Bean public RedisCacheManager cacheManager(RedisConnectionFactory factory) { RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofHours(1)) // 默认缓存过期时间 .serializeKeysWith(RedisSerializationContext.SerializationPair .fromSerializer(new StringRedisSerializer())) .serializeValuesWith(RedisSerializationContext.SerializationPair .fromSerializer(new Jackson2JsonRedisSerializer<>(Object.class))) .disableCachingNullValues(); // 不缓存 null 值
return RedisCacheManager.builder(factory) .cacheDefaults(config) .build(); }}3. 核心注解详解
Section titled “3. 核心注解详解”3.1. @Cacheable — 查询缓存
Section titled “3.1. @Cacheable — 查询缓存”方法执行前先查缓存,命中则直接返回,不执行方法;未命中则执行方法并将结果存入缓存。
@Servicepublic class UserService {
// 最基础用法:缓存 key = "user::1001" @Cacheable(cacheNames = "user", key = "#id") public User getUserById(Long id) { return userMapper.selectById(id); // 缓存命中时此行不执行 }
// 使用 SpEL 构造复合 key @Cacheable(cacheNames = "user", key = "#p0 + ':' + #p1") public List<User> getUserByPage(int page, int size) { return userMapper.selectPage(page, size); }
// 条件缓存:id > 0 才缓存 @Cacheable(cacheNames = "user", key = "#id", condition = "#id > 0") public User getUser(Long id) { return userMapper.selectById(id); }
// unless:方法执行后判断,结果为 null 时不缓存 @Cacheable(cacheNames = "user", key = "#id", unless = "#result == null") public User getUserOrNull(Long id) { return userMapper.selectById(id); }}SpEL 表达式速查:
| 表达式 | 说明 | 示例 |
|---|---|---|
#参数名 | 方法参数值 | #id |
#p0 / #p1 | 按位置取参数 | #p0 |
#result | 方法返回值(unless 中用) | #result.id |
#root.method | 当前方法 | — |
'字面量' | 字符串常量 | 'user' |
3.2. @CachePut — 更新缓存
Section titled “3.2. @CachePut — 更新缓存”方法一定执行,执行后将结果写入缓存(更新或新建)。适合 update 场景,保持缓存与数据库同步。
// 更新用户:DB 更新后同步刷新缓存@CachePut(cacheNames = "user", key = "#user.id")public User updateUser(User user) { userMapper.updateById(user); return user; // 返回值会被存入缓存}3.3. @CacheEvict — 删除缓存
Section titled “3.3. @CacheEvict — 删除缓存”方法执行时删除指定缓存,适合 delete 或复杂 update 场景(直接删除缓存,下次查询时重建)。
// 删除用户:同时清除缓存@CacheEvict(cacheNames = "user", key = "#id")public void deleteUser(Long id) { userMapper.deleteById(id);}
// 清空某个 cacheNames 下的所有缓存@CacheEvict(cacheNames = "user", allEntries = true)public void clearAllUserCache() { // 常用于数据批量变更后刷新全量缓存}
// beforeInvocation = true:方法执行前删缓存// 默认 false:方法执行后删缓存(方法抛异常则不删)@CacheEvict(cacheNames = "user", key = "#id", beforeInvocation = true)public void deleteUserSafe(Long id) { userMapper.deleteById(id);}3.4. @Caching — 组合注解
Section titled “3.4. @Caching — 组合注解”一个方法需要同时操作多个缓存时使用:
@Caching( put = { @CachePut(cacheNames = "user", key = "#user.id"), @CachePut(cacheNames = "user:email", key = "#user.email") }, evict = { @CacheEvict(cacheNames = "user:list", allEntries = true) })public User createUser(User user) { userMapper.insert(user); return user;}3.5. @CacheConfig — 类级别公共配置
Section titled “3.5. @CacheConfig — 类级别公共配置”抽取公共的 cacheNames,避免每个方法重复写:
@Service@CacheConfig(cacheNames = "user") // 类级别指定 cacheNamespublic class UserService {
@Cacheable(key = "#id") // 无需再写 cacheNames public User getById(Long id) { ... }
@CacheEvict(key = "#id") public void delete(Long id) { ... }}4. 核心注解对比
Section titled “4. 核心注解对比”| 注解 | 方法是否执行 | 缓存操作 | 典型场景 |
|---|---|---|---|
@Cacheable | 未命中才执行 | 读缓存,未命中则写入 | 查询接口 |
@CachePut | 一定执行 | 写入/更新缓存 | 更新接口 |
@CacheEvict | 一定执行 | 删除缓存 | 删除/复杂更新接口 |
@Caching | 一定执行 | 组合多个缓存操作 | 复杂业务 |
5. 自定义 Key 生成策略
Section titled “5. 自定义 Key 生成策略”默认 key 生成策略不直观,建议自定义:
@Beanpublic KeyGenerator keyGenerator() { return (target, method, params) -> { // 格式:类名:方法名:参数1:参数2 String className = target.getClass().getSimpleName(); String methodName = method.getName(); String paramStr = Arrays.stream(params) .map(Object::toString) .collect(Collectors.joining(":")); return className + ":" + methodName + ":" + paramStr; };}
// 使用自定义 KeyGenerator@Cacheable(cacheNames = "user", keyGenerator = "keyGenerator")public User getUser(Long id) { ... }6. 为不同 cacheNames 配置不同 TTL
Section titled “6. 为不同 cacheNames 配置不同 TTL”@Beanpublic RedisCacheManager cacheManager(RedisConnectionFactory factory) { // 默认配置(1 小时过期) RedisCacheConfiguration defaultConfig = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofHours(1)) .serializeKeysWith(...) .serializeValuesWith(...);
// 为特定 cacheNames 设置不同 TTL Map<String, RedisCacheConfiguration> configMap = new HashMap<>(); configMap.put("user", defaultConfig.entryTtl(Duration.ofHours(2))); configMap.put("product", defaultConfig.entryTtl(Duration.ofMinutes(30))); configMap.put("config", defaultConfig.entryTtl(Duration.ofDays(1)));
return RedisCacheManager.builder(factory) .cacheDefaults(defaultConfig) .withInitialCacheConfigurations(configMap) .build();}7. 完整使用示例
Section titled “7. 完整使用示例”@Service@CacheConfig(cacheNames = "product")@RequiredArgsConstructorpublic class ProductService {
private final ProductMapper productMapper;
// 查询:优先从缓存取 @Cacheable(key = "#id", unless = "#result == null") public Product getById(Long id) { return productMapper.selectById(id); }
// 更新:同步刷新缓存 @CachePut(key = "#product.id") public Product update(Product product) { productMapper.updateById(product); return product; }
// 删除:清除缓存 @CacheEvict(key = "#id") public void delete(Long id) { productMapper.deleteById(id); }
// 批量更新:清空全部缓存,下次查询重建 @CacheEvict(allEntries = true) public void batchUpdate(List<Product> products) { productMapper.batchUpdate(products); }}