文档站点
Skip to content

缓存 ​

Laravel 用 Cache::put() / Cache::remember() + config/cache.php 配驱动;Spring Boot 用 Spring Cache 抽象:一个 @Cacheable 注解搞定「查缓存 → 没命中 → 执行方法 → 写缓存」,驱动从 Redis 到内存随意切换。

三步启用 ​

1. 加依赖 + 开启缓存 ​

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
java
@Configuration
@EnableCaching
public class CacheConfig { }

2. 在方法上加注解 ​

java
@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, 秒) 的按次指定不同):

yaml
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组合多个缓存操作多个缓存操作
java
@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 表达式取参数:

java
@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:

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
yaml
spring:
  data:
    redis:
      host: localhost
      port: 6379
  cache:
    type: redis                # 显式指定,自动配置就会用 RedisCacheManager

和 Laravel 换 CACHE_DRIVER 一样,上层 @Cacheable 代码一行不改。

本地缓存(简单场景)

不想引 Redis,默认走内存缓存(ConcurrentMapCacheManager),进程内有效、重启丢失、多实例不共享。单机 demo 够用,生产建议 Redis。

手动缓存 API(对应 Cache::xxx) ​

java
@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 社区对同样问题的解决方案,思路通用。

场景对照速查 ​

需求LaravelSpring 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) 触发清缓存,避免「事务回滚了但缓存已清」或「缓存清了事务失败」的微妙问题。

面向 PHP / Laravel 开发者的 Spring Boot 中文文档