从入门到精通:掌握 @Cacheable、@CachePut 和 @CacheEvict 注解
一、@Cacheable,@CachePut,@CacheEvict区别
当使用缓存时,Spring 提供了三个常用的注解:@Cacheable
、@CachePut
和 @CacheEvict
,它们的区别如下:
-
@Cacheable
注解:-
作用:将方法的返回值缓存起来,以便下次相同的方法调用时可以直接从缓存中获取结果。
-
使用场景:适用于读取操作频繁,但数据很少改变的场景。
-
示例代码:
@Cacheable(value = "products", key = "#productId") public Product getProductById(Long productId) { // 从数据库中获取产品信息 }
在上述示例中,使用
@Cacheable
注解将getProductById
方法的返回值缓存起来,缓存的名称为 "products"。每次调用该方法时,如果缓存中存在对应的结果,就直接返回缓存值;否则执行方法逻辑,并将结果放入缓存中。
-
-
@CachePut
注解:-
作用:将方法的返回值更新到缓存中。
-
使用场景:适用于写入或更新操作,需要将结果放入缓存并确保缓存的数据是最新的。
-
示例代码:
@CachePut(value = "products", key = "#product.id") public Product saveProduct(Product product) { // 保存产品信息到数据库 return product; }
在上述示例中,使用
@CachePut
注解将saveProduct
方法的返回值放入缓存中,缓存的名称为 "products"。每次调用该方法时,无论缓存中是否存在对应的值,都会执行方法逻辑,并将结果更新到缓存中。
-
-
@CacheEvict
注解:-
作用:从缓存中移除一个或多个缓存项。
-
使用场景:适用于删除操作或数据更新后的缓存清理。
-
示例代码:
@CacheEvict(value = "products", key = "#productId") public void deleteProduct(Long productId) { // 从数据库中删除产品信息 }
在上述示例中,使用
@CacheEvict
注解将deleteProduct
方法执行后,将缓存中指定productId
的缓存项移除,缓存的名称为 "products"。
-
二、进阶使用
进阶使用 @Cacheable
、@CachePut
和 @CacheEvict
注解的一些注意事项和高级用法包括:
-
多缓存管理器支持:如果应用程序中使用了多个缓存管理器,可以使用
cacheManager
属性指定具体的缓存管理器。@Cacheable(value = "products", key = "#productId", cacheManager = "cacheManager1") public Product getProductById(Long productId) { // ... }
-
条件缓存:可以使用 SpEL 表达式在注解中定义条件,只有满足条件时才进行缓存操作。
@Cacheable(value = "products", key = "#productId", condition = "#productId > 0") public Product getProductById(Long productId) { // ... }
-
自定义缓存策略:通过实现
CacheResolver
接口或者使用cacheResolver
属性,可以自定义缓存的解析和管理逻辑。@Cacheable(value = "products", key = "#productId", cacheResolver = "customCacheResolver") public Product getProductById(Long productId) { // ... }
-
缓存更新时机:可以使用
@CachePut
注解在方法执行后手动触发缓存更新,而不是每次方法调用都更新缓存。@CachePut(value = "products", key = "#product.id") public Product updateProduct(Product product) { // ... return product; }
-
缓存清除策略:使用
@CacheEvict
注解时,可以通过beforeInvocation
属性来控制清除缓存的时机,默认是在方法执行后清除缓存。@CacheEvict(value = "products", key = "#productId", beforeInvocation = true) public void deleteProduct(Long productId) { // ... }
-
缓存注解继承:可以使用注解继承方式,简化对多个方法应用相同缓存注解的操作。
@Inherited @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Cacheable(value = "products", key = "#productId") public @interface CachedProduct { // ... } @CachedProduct public Product getProductById(Long productId) { // ... } @CachedProduct public List<Product> getAllProducts() { // ... }
-
自定义缓存键生成策略:可以使用
keyGenerator
属性指定自定义的缓存键生成器。@Cacheable(value = "products", keyGenerator = "customKeyGenerator") public Product getProductById(Long productId) { // ... }
-
SpEL 表达式使用:在注解的属性值中可以使用 SpEL 表达式,动态计算缓存键、条件等。
@Cacheable(value = "products", key = "'product:' + #productId") public Product getProductById(Long productId) { // ... }
-
缓存注解的优先级:在方法上同时使用多个缓存注解时,它们的执行顺序和优先级可以通过
@Order
注解进行控制。@Cacheable(value = "products", key = "#productId") @CachePut(value = "products", key = "#productId") @Order(1) public Product getProductById(Long productId) { // ... }
-
配置缓存过期时间:可以使用缓存管理器的配置来设置缓存的过期时间,或者在注解中通过
expiration
属性指定缓存的过期时间。@Configuration @EnableCaching public class CacheConfig extends CachingConfigurerSupport { // ... @Override public CacheManager cacheManager() { SimpleCacheManager cacheManager = new SimpleCacheManager(); // 设置缓存过期时间 cacheManager.setCaches(Arrays.asList( new ConcurrentMapCache("products", getExpirationDuration(30)), // ... )); return cacheManager; } private Duration getExpirationDuration(int minutes) { return Duration.ofMinutes(minutes); } } @Cacheable(value = "products", key = "#productId", expiration = 10) public Product getProductById(Long productId) { // ... }
-
缓存条件判断:可以使用
condition
属性在注解中指定一个 SpEL 表达式,根据条件判断是否执行缓存操作。@Cacheable(value = "products", key = "#productId", condition = "#productId > 0") public Product getProductById(Long productId) { // ... }
-
同步缓存操作:使用
@CachePut
注解可以实现同步缓存操作,即先执行方法,然后更新缓存。@CachePut(value = "products", key = "#product.id") public Product updateProduct(Product product) { // ... return product; }
-
缓存清除策略:
@CacheEvict
注解可以用于清除缓存中的数据,可以通过key
属性指定要清除的缓存项。@CacheEvict(value = "products", key = "#productId") public void deleteProduct(Long productId) { // ... }
-
缓存注解顺序:当一个方法上同时存在多个缓存注解时,可以使用
@CacheConfig
注解或@Order
注解来控制注解的执行顺序。@CacheConfig(cacheNames = "products") public class ProductRepository { @Cacheable(key = "#id") @CachePut(key = "#result.id") public Product getProductById(Long id) { // ... } }
-
使用 SpEL 表达式:可以在注解中使用 SpEL 表达式动态地生成缓存键。
@Cacheable(value = "products", key = "'product:' + #productId") public Product getProductById(Long productId) { // ... }
-
缓存与事务管理:在使用缓存注解时,需要注意与事务管理的交互。默认情况下,Spring 的事务管理会在方法执行前将缓存清空,以保证数据的一致性。如果希望在事务提交后再执行缓存操作,可以使用
@CachePut
注解并将方法放在一个新的事务中。@Transactional public void updateProduct(Product product) { // 更新数据库中的数据 // ... // 手动执行缓存操作 updateProductCache(product); } @CachePut(value = "products", key = "#product.id") @Transactional(propagation = Propagation.REQUIRES_NEW) public Product updateProductCache(Product product) { // 更新缓存中的数据 // ... return product; }
-
多级缓存配置:可以配置多个级别的缓存,例如使用一级缓存作为本地缓存,二级缓存作为分布式缓存。可以通过
@CacheConfig
注解和CacheManager
进行配置。@CacheConfig(cacheNames = "products") public class ProductRepository { @Autowired private CacheManager cacheManager; @Cacheable(key = "#id", cacheManager = "localCacheManager") public Product getProductById(Long id) { // ... } @Cacheable(key = "#id", cacheManager = "distributedCacheManager") public Product getProductByIdFromDistributedCache(Long id) { // ... } }
-
缓存预热:可以在应用启动时,通过调用特定的方法来预先加载缓存数据,以提高系统的性能和响应速度。
@Component public class CachePreloader { @Autowired private ProductRepository productRepository; @PostConstruct public void preloadCache() { List<Product> products = productRepository.getAllProducts(); for (Product product : products) { productRepository.getProductById(product.getId()); } } }
-
异步缓存操作:使用异步方式执行缓存操作,以减少对主线程的影响,提高系统的并发性能。
@CachePut(value = "products", key = "#product.id") @Async public CompletableFuture<Product> updateProductAsync(Product product) { // ... return CompletableFuture.completedFuture(product); }
以上是一些进阶用法和注意事项,它们可以帮助你更好地使用 @Cacheable
、@CachePut
和 @CacheEvict
注解来管理缓存,并根据具体的业务需求和场景进行优化和配置。请根据实际情况选择合适的用法,并结合缓存框架和缓存管理器的文档进行深入研究和调整。
三、总结
-
@Cacheable
注解用于指示方法的结果应该被缓存,以提高后续对相同输入参数调用的性能。它会首先检查缓存中是否存在结果,如果存在,则直接返回缓存中的值;如果不存在,则执行方法并将结果存入缓存。可以通过设置缓存的键(key
)来区分不同的缓存项。 -
@CachePut
注解用于指示方法的结果应该被缓存,但它每次都会执行方法并将结果存入缓存,不会像@Cacheable
那样检查缓存中是否已存在结果。它常用于更新缓存中的数据,确保缓存的数据与数据库或其他数据源保持同步。 -
@CacheEvict
注解用于指示方法执行后应清除缓存中的某些项。可以通过设置缓存的键(key
)来指定要清除的特定缓存项。它还提供了一些属性,如allEntries
和beforeInvocation
,用于清除所有缓存项或在方法执行前清除缓存。
这些注解可以与不同的缓存提供程序集成,如 Spring Boot 默认的缓存提供程序 Caffeine、Ehcache、Redis 等。通过合理地使用这些注解,可以显著提高应用程序的性能和响应速度。
转载自:https://juejin.cn/post/7246279539985711162