spring-cache

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Spring Cache

Spring Cache

Quick Start

快速入门

java
@SpringBootApplication
@EnableCaching
public class Application {}

@Service
public class UserService {

    @Cacheable("users")
    public User findById(Long id) {
        return userRepository.findById(id).orElseThrow();
    }

    @CacheEvict(value = "users", key = "#id")
    public void deleteUser(Long id) {
        userRepository.deleteById(id);
    }
}
yaml
spring:
  cache:
    type: caffeine
    caffeine:
      spec: maximumSize=1000,expireAfterWrite=10m

java
@SpringBootApplication
@EnableCaching
public class Application {}

@Service
public class UserService {

    @Cacheable("users")
    public User findById(Long id) {
        return userRepository.findById(id).orElseThrow();
    }

    @CacheEvict(value = "users", key = "#id")
    public void deleteUser(Long id) {
        userRepository.deleteById(id);
    }
}
yaml
spring:
  cache:
    type: caffeine
    caffeine:
      spec: maximumSize=1000,expireAfterWrite=10m

Cache Annotations

缓存注解

@Cacheable

@Cacheable

java
@Cacheable("products")
public Product findById(Long id) { }

@Cacheable(value = "products", key = "#category + '-' + #status")
public List<Product> findByCategoryAndStatus(String category, String status) { }

@Cacheable(value = "products", condition = "#id > 0")
public Product findByIdConditional(Long id) { }

@Cacheable(value = "products", unless = "#result == null")
public Product findByIdUnlessNull(Long id) { }

@Cacheable(value = "products", sync = true)  // One thread populates
public Product findByIdSync(Long id) { }
java
@Cacheable("products")
public Product findById(Long id) { }

@Cacheable(value = "products", key = "#category + '-' + #status")
public List<Product> findByCategoryAndStatus(String category, String status) { }

@Cacheable(value = "products", condition = "#id > 0")
public Product findByIdConditional(Long id) { }

@Cacheable(value = "products", unless = "#result == null")
public Product findByIdUnlessNull(Long id) { }

@Cacheable(value = "products", sync = true)  // One thread populates
public Product findByIdSync(Long id) { }

@CacheEvict

@CacheEvict

java
@CacheEvict(value = "products", key = "#id")
public void deleteProduct(Long id) { }

@CacheEvict(value = "products", allEntries = true)
public void clearProductCache() { }

@CacheEvict(value = "products", key = "#id", beforeInvocation = true)
public void deleteProductBeforeInvocation(Long id) { }
java
@CacheEvict(value = "products", key = "#id")
public void deleteProduct(Long id) { }

@CacheEvict(value = "products", allEntries = true)
public void clearProductCache() { }

@CacheEvict(value = "products", key = "#id", beforeInvocation = true)
public void deleteProductBeforeInvocation(Long id) { }

@CachePut

@CachePut

java
@CachePut(value = "products", key = "#product.id")
public Product saveProduct(Product product) {
    return productRepository.save(product);
}

@CachePut(value = "products", key = "#result.id")
public Product createProduct(CreateProductRequest request) {
    return productRepository.save(new Product(request));
}
java
@CachePut(value = "products", key = "#product.id")
public Product saveProduct(Product product) {
    return productRepository.save(product);
}

@CachePut(value = "products", key = "#result.id")
public Product createProduct(CreateProductRequest request) {
    return productRepository.save(new Product(request));
}

@Caching (Multiple Operations)

@Caching (多操作组合)

java
@Caching(
    put = {
        @CachePut(value = "products", key = "#result.id"),
        @CachePut(value = "productsBySku", key = "#result.sku")
    },
    evict = {
        @CacheEvict(value = "productList", allEntries = true)
    }
)
public Product createProduct(CreateProductRequest request) { }
java
@Caching(
    put = {
        @CachePut(value = "products", key = "#result.id"),
        @CachePut(value = "productsBySku", key = "#result.sku")
    },
    evict = {
        @CacheEvict(value = "productList", allEntries = true)
    }
)
public Product createProduct(CreateProductRequest request) { }

@CacheConfig (Class-Level)

@CacheConfig (类级别配置)

java
@Service
@CacheConfig(cacheNames = "products", keyGenerator = "customKeyGenerator")
public class ProductService {

    @Cacheable  // Uses class config
    public Product findById(Long id) { }

    @Cacheable(cacheNames = "inventory")  // Override cache name
    public Inventory getInventory(Long productId) { }
}
Full Reference: See managers.md for Caffeine, Redis, EhCache configurations.

java
@Service
@CacheConfig(cacheNames = "products", keyGenerator = "customKeyGenerator")
public class ProductService {

    @Cacheable  // 继承类级别配置
    public Product findById(Long id) { }

    @Cacheable(cacheNames = "inventory")  // 覆盖缓存名称配置
    public Inventory getInventory(Long productId) { }
}
完整参考:查看 managers.md 了解Caffeine、Redis、EhCache的配置方式。

Quick Cache Manager Setup

缓存管理器快速配置

Caffeine (Single Instance)

Caffeine (单实例场景)

java
@Bean
public CacheManager cacheManager() {
    CaffeineCacheManager manager = new CaffeineCacheManager();
    manager.setCaffeine(Caffeine.newBuilder()
        .maximumSize(10_000)
        .expireAfterWrite(Duration.ofMinutes(10))
        .recordStats());
    return manager;
}
java
@Bean
public CacheManager cacheManager() {
    CaffeineCacheManager manager = new CaffeineCacheManager();
    manager.setCaffeine(Caffeine.newBuilder()
        .maximumSize(10_000)
        .expireAfterWrite(Duration.ofMinutes(10))
        .recordStats());
    return manager;
}

Redis (Distributed)

Redis (分布式场景)

java
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
    RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
        .entryTtl(Duration.ofMinutes(10))
        .serializeValuesWith(SerializationPair
            .fromSerializer(new GenericJackson2JsonRedisSerializer()));

    return RedisCacheManager.builder(factory)
        .cacheDefaults(config)
        .build();
}
Full Reference: See advanced.md for Multi-Level Caching, Metrics, Synchronization.

java
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
    RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
        .entryTtl(Duration.ofMinutes(10))
        .serializeValuesWith(SerializationPair
            .fromSerializer(new GenericJackson2JsonRedisSerializer()));

    return RedisCacheManager.builder(factory)
        .cacheDefaults(config)
        .build();
}
完整参考:查看 advanced.md 了解多级缓存、指标监控、同步机制相关内容。

Best Practices

最佳实践

DoDon't
Use Caffeine for single-instanceSkip TTL configuration
Use Redis for distributedCache mutable objects
Configure TTL alwaysIgnore cache eviction
Use sync=true for expensive opsUse high cardinality keys
Implement cache metricsCache sensitive data unencrypted

推荐做法不推荐做法
单实例场景使用Caffeine跳过TTL配置
分布式场景使用Redis缓存可变对象
始终配置TTL忽略缓存驱逐策略
高开销操作使用sync=true使用高基数缓存键
实现缓存指标监控未加密缓存敏感数据

Production Checklist

生产环境检查清单

  • Cache provider configured (Caffeine/Redis)
  • TTL configured for every cache
  • Cache eviction on write operations
  • Metrics configured
  • Serialization tested
  • Distributed lock for critical ops

  • 已配置缓存提供方(Caffeine/Redis)
  • 所有缓存都已配置TTL
  • 写操作触发缓存驱逐
  • 已配置指标监控
  • 序列化逻辑已测试
  • 关键操作已配置分布式锁

When NOT to Use This Skill

何时不适合使用本技能

  • Distributed caching - Use
    spring-data-redis
  • Redis operations - Use
    redis
    skill
  • Session storage - Use Spring Session

  • 分布式缓存 - 使用
    spring-data-redis
  • Redis操作 - 使用
    redis
    skill
  • 会话存储 - 使用Spring Session

Common Pitfalls

常见陷阱

ErrorCauseSolution
Cache not workingInternal call (same bean)Use self-injection
Null pointerNull values cachedUse
unless = "#result == null"
Memory leakTTL not configuredSet expireAfterWrite
Serialization errorNon-serializable objectsImplement Serializable

错误原因解决方案
缓存不生效同一Bean内部调用使用自注入
空指针异常缓存了空值使用
unless = "#result == null"
内存泄漏未配置TTL设置expireAfterWrite
序列化错误对象未实现序列化实现Serializable接口

Anti-Patterns

反模式

Anti-PatternProblemSolution
Caching mutable objectsStale dataCache immutable data
No TTL configuredStale cache foreverSet expireAfterWrite
@Cacheable on voidNo effectOnly cache with return
No cache syncRace conditionsUse sync=true or locks

反模式问题解决方案
缓存可变对象数据过时缓存不可变数据
未配置TTL缓存永久过时设置expireAfterWrite
给void方法加@Cacheable无任何效果仅对有返回值的方法使用缓存
无缓存同步竞态条件使用sync=true或分布式锁

Quick Troubleshooting

快速排障

ProblemDiagnosticFix
Cache not workingCheck @EnableCachingAdd annotation
Wrong data cachedCheck cache keyDefine explicit key
Cache not evictedCheck key expressionVerify key matches
Self-invocation bypassSame class callInject self

问题排查方式修复方案
缓存不生效检查是否添加@EnableCaching添加对应注解
缓存数据错误检查缓存键显式定义缓存键
缓存未驱逐检查键表达式确认键匹配规则
自调用绕过缓存同一类内部调用注入自身实例

Reference Files

参考文件

FileContent
managers.mdCaffeine, Redis, EhCache, Key Generators
advanced.mdMulti-Level, Metrics, Sync, Testing

文件内容
managers.mdCaffeine、Redis、EhCache、键生成器
advanced.md多级缓存、指标监控、同步、测试

External Documentation

外部文档