缓存
Laravel 用
Cache::put()/Cache::remember()+config/cache.php配驱动;Spring Boot 用 Spring Cache 抽象:一个@Cacheable注解搞定「查缓存 → 没命中 → 执行方法 → 写缓存」,驱动从 Redis 到内存随意切换。
三步启用
1. 加依赖 + 开启缓存
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>@Configuration
@EnableCaching
public class CacheConfig { }2. 在方法上加注解
@Service
public class PostService {
@Cacheable(cacheNames = "posts", key = "#id")
public Post get(Long id) {
// 只有缓存没命中时才执行,执行结果自动写缓存
return repository.findById(id).orElseThrow(...);
}
}对照 Laravel
@Cacheable ≈ Cache::remember('posts.'.$id, 3600, fn() => ...):
- 命中缓存 → 直接返回缓存值,方法体不执行
- 未命中 → 执行方法 → 返回值按 key 写缓存
cacheNames是缓存区名,key是缓存键
3. 配置缓存区与过期时间
Spring Cache 的「过期时间」由缓存管理器决定(和 Laravel Cache::put(key, val, 秒) 的按次指定不同):
spring:
cache:
cache-names: posts, users
redis:
time-to-live: 3600s # 所有缓存区默认 1 小时
cache-null-values: false常用注解
| 注解 | 作用 | 类比 Laravel |
|---|---|---|
@Cacheable | 读缓存,未命中执行并写缓存 | Cache::remember |
@CachePut | 总是执行方法并更新缓存 | Cache::put |
@CacheEvict | 删除缓存 | Cache::forget |
@Caching | 组合多个缓存操作 | 多个缓存操作 |
@CacheEvict(cacheNames = "posts", key = "#id") // 更新后清掉旧缓存
public Post update(Long id, PostDto dto) { ... }
@CacheEvict(cacheNames = "posts", allEntries = true) // 清空整个缓存区
public void deleteAll() { ... }@CachePut vs @Cacheable
@Cacheable:先查缓存,命中就不执行方法。@CachePut:一定执行方法,结果写缓存。更新场景用 @CacheEvict 或 @CachePut,千万别在更新方法上加 @Cacheable(永远不会更新)。
key 怎么写
Spring Cache 用 SpEL 表达式取参数:
@Cacheable(cacheNames = "posts", key = "#id") // 单参数
@Cacheable(cacheNames = "posts", key = "#dto.title") // 参数属性
@Cacheable(cacheNames = "posts", key = "'post:' + #id") // 拼前缀
@Cacheable(cacheNames = "posts", key = "#root.methodName") // 方法名TIP
多参数时默认 key 是「所有参数的组合」。别依赖默认,显式写 key 可读性更好。
切换缓存驱动:Redis 最常用
Spring Cache 的驱动靠 CacheManager 抽象,换驱动不改业务代码。用 Redis:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>spring:
data:
redis:
host: localhost
port: 6379
cache:
type: redis # 显式指定,自动配置就会用 RedisCacheManager和 Laravel 换
CACHE_DRIVER一样,上层@Cacheable代码一行不改。
本地缓存(简单场景)
不想引 Redis,默认走内存缓存(ConcurrentMapCacheManager),进程内有效、重启丢失、多实例不共享。单机 demo 够用,生产建议 Redis。
手动缓存 API(对应 Cache::xxx)
@Service
public class CacheService {
private final CacheManager cacheManager;
public CacheService(CacheManager cacheManager) {
this.cacheManager = cacheManager;
}
public void demo() {
Cache posts = cacheManager.getCache("posts");
posts.put(1L, "hello"); // Cache::put
String v = posts.get(1L, String.class); // Cache::get
posts.evict(1L); // Cache::forget
}
}缓存失效策略(重点)
缓存最难的从来不是「怎么存」,而是「什么时候失效」。三种策略:
1. 主动失效(推荐,一致性最好)
写操作后主动清缓存 —— 上面 @CacheEvict 的用法。
2. TTL 过期
统一过期时间兜底,防止脏数据永驻。永远设 TTL,哪怕很长。
3. 先删后写 vs 先写后删
更新帖子:先 @CacheEvict 再写库(推荐)—— 删了之后并发读到的是新值或旧库值,不会缓存新库旧
先写库再 @CacheEvict —— 中间窗口可能读到旧缓存缓存穿透 / 击穿 / 雪崩
这是所有缓存系统都要防的三件事,Spring Cache 不能替你解决,要自己处理:
- 穿透:查不存在的 key,每次都打库 →
cache-null-values: true缓存空值 - 击穿:热点 key 过期瞬间并发打库 → 加锁或热点不设过期
- 雪崩:大量 key 同时过期 → TTL 加随机抖动 深入设计可参考 Laravel 社区对同样问题的解决方案,思路通用。
场景对照速查
| 需求 | Laravel | Spring Boot |
|---|---|---|
| 读缓存(命中即返) | Cache::remember | @Cacheable |
| 写缓存 | Cache::put | @CachePut |
| 删缓存 | Cache::forget | @CacheEvict |
| 清空区 | Cache::flush | @CacheEvict(allEntries=true) |
| 换驱动 | 改 CACHE_DRIVER | 改 spring.cache.type + 依赖 |
| 防穿透 | 手动处理 | cache-null-values: true + 手动处理 |
事务 + 缓存
在 @Transactional 方法里用 @CacheEvict,注意执行顺序。复杂场景建议「先提交事务、再清缓存」,用 @TransactionalEventListener(AFTER_COMMIT) 触发清缓存,避免「事务回滚了但缓存已清」或「缓存清了事务失败」的微妙问题。