缓存
@Cacheable(类比 Cache::remember)
java
@Service
public class UserService {
@Cacheable(value = "users", key = "#id") // 类比 Cache::remember("users.{$id}", 3600, fn())
public User findById(Long id) {
// 1. 首次调用:执行业务逻辑,结果缓存
// 2. 后续调用:直接返回缓存,不执行方法体
slowQuery(); // 模拟耗时的数据库查询
return userMapper.selectById(id);
}
@Cacheable(value = "users", key = "'list_' + #page")
public List<User> getList(int page) {
return userMapper.selectPage(new Page<>(page, 20));
}
}启用缓存
java
@SpringBootApplication
@EnableCaching // 必须加这个注解!否则 @Cacheable 无效
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}@CacheEvict(清除缓存)
java
@Service
public class UserService {
@CacheEvict(value = "users", key = "#id") // 类比 Cache::forget("users." . $id)
public void delete(Long id) {
userMapper.deleteById(id);
}
@CacheEvict(value = "users", allEntries = true) // 清除 users 所有缓存
public void clearAll() {
// 不执行任何操作,只触发清理
}
}@CachePut(更新缓存)
java
@Service
public class UserService {
@CachePut(value = "users", key = "#user.id") // 类比 Cache::put("users." . $user->id, $user, 3600)
public User update(User user) {
userMapper.updateById(user);
return user; // 返回值会被写入缓存
}
}三种注解对比
| 注解 | 执行时机 | 缓存操作 | 类比 |
|---|---|---|---|
@Cacheable | 方法执行前检查缓存 | 缓存不存在才执行方法 | Cache::remember() |
@CachePut | 方法执行后 | 把返回值写入缓存 | Cache::put() |
@CacheEvict | 方法执行后 | 清除缓存 | Cache::forget() |
@Caching | 组合多个注解 | 同时配读和写 | 组合操作 |
缓存配置
yaml
# application.yml
spring:
cache:
type: redis # 使用 Redis 作为缓存存储(默认是 ConcurrentHashMap)如果 spring.cache.type 不配置,Spring Boot 默认使用 ConcurrentHashMap(内存在进程内)。适合单机开发,生产环境必须用 Redis。
自定义 Redis 缓存过期时间
java
@Configuration
public class CacheConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
// 不同缓存名称设置不同的 TTL
Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
configMap.put("users", RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1))); // users 缓存 1 小时
configMap.put("posts", RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30))); // posts 缓存 30 分钟
return RedisCacheManager.builder(factory)
.cacheDefaults(RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10))) // 默认 10 分钟
.withInitialCacheConfigurations(configMap)
.build();
}
}⚠️ 常见坑
1. 同一个类中调用 @Cacheable 不生效
java
@Service
public class UserService {
public User getProfile(Long id) {
return findById(id); // ❌ 内部调用,@Cacheable 不生效
}
@Cacheable("users")
public User findById(Long id) { ... }
}原因:和 AOP 一样,
@Cacheable通过代理实现。内部调用this.findById()走的是原始对象而不是代理对象。解决方案:把
findById抽到另一个 Service 类中。
2. @Cacheable 默认 key 包含所有参数
java
@Cacheable("users")
public User findById(Long id) { }
// 默认 key 是 "users::1"、"users::2"
// 如果你还想要其他 key 生成策略,可指定 keyGenerator3. 缓存穿透问题
如果
findById()返回null,@Cacheable 默认不缓存 null 值。这意味着查询不存在的 ID 时,每次都会查数据库。java@Cacheable(value = "users", unless = "#result == null") public User findById(Long id) { ... }
4. 条件缓存
java// 只缓存 id > 100 的查询结果 @Cacheable(value = "users", condition = "#id > 100") public User findById(Long id) { ... }
5. SpEL 表达式中的特殊字符
java@Cacheable(key = "'list_' + #page") // 字符串拼接用单引号 @Cacheable(key = "#result.id") // 方法执行后的结果 @Cacheable(key = "#root.methodName") // 当前方法名