在Java企业级应用开发中,缓存是提升系统性能的利器。Spring框架提供的缓存抽象层,通过简单的注解就能实现复杂的缓存逻辑。今天我们就来拆解Spring缓存体系中最核心的五大注解,结合我多年实战经验,带你掌握每个注解的细节用法和隐藏技巧。
先看一个典型场景:电商系统的商品详情查询,QPS高达5000+,直接查数据库显然不现实。通过Spring缓存注解,我们可以在方法层面轻松实现缓存逻辑,代码量减少70%的同时性能提升20倍。下面就从最基础的@Cacheable开始,逐步剖析每个注解的实战用法。
这是最常用的缓存注解,作用在方法上时,Spring会先检查缓存是否存在,有则直接返回,无则执行方法并将结果缓存。关键属性解析:
java复制@Cacheable(
value = "products", // 缓存名称
key = "#productId", // 动态键生成
condition = "#productId > 1000", // 缓存条件
unless = "#result == null" // 结果过滤
)
public Product getProductDetail(long productId) {
// 数据库查询逻辑
}
实战经验:
#root.methodName+#参数名组合构建唯一键典型踩坑案例:
曾遇到缓存穿透问题,大量查询不存在的ID导致DB压力剧增。解决方案是在condition中添加#productId != null判断,同时配置空值缓存:
java复制@Cacheable(value="products",
key="#productId",
condition="#productId != null",
unless="#result == null")
当需要保证缓存与数据库强一致时使用,典型场景是更新操作:
java复制@CachePut(value = "products", key = "#product.id")
public Product updateProduct(Product product) {
// 更新数据库
return product; // 返回值会被缓存
}
重要细节:
删除操作时需要同步清理缓存:
java复制@CacheEvict(value = "products",
key = "#productId",
allEntries = false, // 是否清空整个缓存
beforeInvocation = false) // 调用前/后清除
public void deleteProduct(long productId) {
// 删除数据库记录
}
性能优化技巧:
当需要同时使用多个缓存注解时:
java复制@Caching(
evict = {
@CacheEvict(value = "product_list", allEntries=true),
@CacheEvict(value = "products", key="#product.id")
},
put = {
@CachePut(value = "products", key="#result.id")
}
)
public Product updateProduct(Product product) {
// 复杂更新逻辑
}
设计建议:
简化同类方法的重复配置:
java复制@CacheConfig(cacheNames = "products")
public class ProductService {
// 类中方法无需重复指定value
@Cacheable(key = "#id")
public Product getById(long id) {...}
}
最佳实践:
结合本地缓存与Redis:
java复制@Cacheable(value = "products",
cacheManager = "compositeCacheManager",
key = "#productId")
public Product getProductWithMultiCache(long productId) {
// 查询逻辑
}
配置要点:
自定义CacheErrorHandler:
java复制public class CustomCacheErrorHandler implements CacheErrorHandler {
@Override
public void handleCacheGetError(...) {
// 记录日志但继续执行查询
}
// 其他方法实现...
}
容灾策略:
问题现象:更新数据后缓存未刷新
我的排查工具包:
logging.level.org.springframework.cache=DEBUG当缓存命中率低时:
java复制@Cacheable(value = "products",
key = "#productId",
sync = true) // 关键配置
public Product getProductDetail(long productId) {
// 查询逻辑
}
配合其他措施:
重要发现:在@Transactional方法内,缓存注解的执行时机受事务传播行为影响。我的经验法则是:
java复制@Cacheable(value = "products",
key = "#productId",
cacheResolver = "metricsCacheResolver")
public Product getProductWithMetrics(long productId) {
// 查询逻辑
}
监控维度建议:
基于配置中心的动态调整:
java复制@Cacheable(value = "products",
key = "#productId",
condition = "@cacheConfig.isEnabled('product')")
public Product getProductDynamic(long productId) {
// 查询逻辑
}
这种设计让我能在不停机的情况下:
在具体实现时,我通常会建立缓存配置中心,通过Nacos或Apollo管理所有缓存策略。当系统出现异常时,可以快速降级部分缓存功能。比如在大促期间,我们曾临时关闭了商品评论的缓存,保证核心交易链路的缓存资源充足。