当需要在一个方法中清除多个缓存时,@CacheEvict注解能否实现这个需求

想清除Redis中的多个缓存数据,如何实现?

@CacheEvict清除一个缓存,但现在想知道如何处理多个缓存的情况。场景:可能有一个更新用户信息的方法,这个方法执行后,不仅需要清除某个特定的用户缓存,还可能影响到其他相关的缓存,比如用户列表缓存或者某种组合查询的缓存。例如,更新用户信息后,用户详细信息的缓存需要清除,同时所有用户的列表缓存也需要更新,这时候就需要同时清除多个缓存条目。

Spring的@CacheEvict注解的功能。这个注解默认每次只能指定一个缓存名称(value或cacheNames)和一个键(key)。因此,如果直接使用单个@CacheEvict注解,是无法同时清除多个不同缓存名称或不同键的缓存的

解决方式:

  1. 使用多个@CacheEvict注解​:Spring允许在同一个方法上使用多个缓存相关的注解,比如同时使用多个@CacheEvict,每个注解指定不同的缓存名称和键。例如,一个注解清除用户缓存,另一个清除用户列表缓存。

  2. 使用@Caching组合注解​:@Caching注解可以组合多个缓存操作,包括Cacheable、CachePut和CacheEvict。用户可以在@Caching的evict属性中包含多个@CacheEvict,每个指定不同的缓存名称和键。

  3. 清除整个缓存区域​:如果多个缓存条目都属于同一个缓存名称(比如同一个value),可以通过设置allEntries=true来清除该缓存名称下的所有条目。但如果需要跨不同的缓存名称,这种方法就不适用了。

具体实现方式如下

1.@Caching组合注解

通过 @Caching 注解将多个 @CacheEvict 组合在一起,支持 ​跨不同缓存名称(value)和键(key)的批量清除

@Caching(evict = {
    @CacheEvict(value = "userList", key = "'allUsers'"),      // 清除用户列表缓存
    @CacheEvict(value = "userStats", key = "'activeCount'"),  // 清除统计信息缓存
    @CacheEvict(value = "userDetail", key = "#userId")       // 清除用户详情缓存
})
public void updateUser(Long userId) {
    // 1. 更新数据库
    userRepository.updateUser(userId);
    
    // 2. 其他业务逻辑...
}

2.使用多个@CacheEvict注解

直接在方法上叠加多个 @CacheEvict 注解,适用于简单场景。

@CacheEvict(value = "userList", key = "'allUsers'")
@CacheEvict(value = "userDetail", key = "#userId")
public void updateUser(Long userId) {
    // 数据库更新操作
}

3.清理整个缓存区域

如果某个缓存名称(value)下的所有键都需要清除,可以使用 allEntries = true

// 清除 userList 缓存下的所有键
@CacheEvict(value = "userList", allEntries = true)
public void refreshAllUserRelatedCache() {
    // 方法体可为空
}
适用场景
  • 当缓存键难以枚举(如分页缓存)
  • 需要批量清除关联缓存

4.动态生成多缓存键

通过 SpEL 表达式动态生成多个键值,结合 @CacheEvict 实现批量清除。

// 清除用户详情缓存及其关联缓存
@CacheEvict(value = "userDetail", key = "#userId")
@CacheEvict(value = "userRelations", key = "'relations_' + #userId")
public void updateUserWithRelations(Long userId) {
    // 更新用户及其关联数据
}

5.事件驱动缓存清除(高级)

通过 Spring 事件机制监听数据库变更,自动触发多缓存清除。

实现步骤

(1)定义自定义事件

public class UserUpdateEvent {
    private Long userId;
    // 构造函数、Getter/Setter...
}

(2)发布事件

@Service
public class UserService {
    @Autowired
    private ApplicationEventPublisher eventPublisher;
    
    public void updateUser(Long userId) {
        userRepository.updateUser(userId);
        eventPublisher.publishEvent(new UserUpdateEvent(userId));
    }
}

(3)监听事件并清除缓存

@Component
public class UserCacheListener {
    @Autowired
    private CacheManager cacheManager;
    
    @EventListener
    public void handleUserUpdate(UserUpdateEvent event) {
        // 清除 userDetail 缓存
        cacheManager.getCache("userDetail").evict(event.getUserId());
        
        // 清除 userList 缓存
        cacheManager.getCache("userList").clear();
    }
}

性能与一致性建议

场景推荐方案注意事项
精确清除少量缓存@Caching + 多个 @CacheEvict确保键值正确
批量清除关联缓存allEntries = true避免对大型缓存使用
高频更新场景TTL 过期 + 延迟双删结合 Redis 的 EXPIRE 命令
分布式环境事件驱动 + 消息队列确保缓存清除操作传播到所有节点

完整示例:用户更新操作清除多个缓存

@Service
public class UserService {

    // 更新用户并清除关联缓存
    @Caching(evict = {
        @CacheEvict(value = "userDetail", key = "#userId"),
        @CacheEvict(value = "userList", key = "'allUsers'"),
        @CacheEvict(value = "userSearch", keyGenerator = "searchKeyGenerator")
    })
    public User updateUser(Long userId, User newUser) {
        // 1. 更新数据库
        User updatedUser = userRepository.save(newUser);
        
        // 2. 记录操作日志(与缓存无关)
        log.info("User {} updated", userId);
        
        return updatedUser;
    }
    
    // 自定义键生成器
    @Bean
    public KeyGenerator searchKeyGenerator() {
        return (target, method, params) -> "search_" + params[0];
    }
}
### 如何使用 `@CacheEvict` 删除多个缓存 #### 配置缓存管理器 为了能够操作缓存,首先需要配置一个合适的缓存管理器。可以采用简单的内存缓存方案如 `ConcurrentMapCacheManager` 来快速启动项目并测试功能[^2]。 ```java @Configuration @EnableCaching public class AppConfig { @Bean public CacheManager cacheManager() { ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager("employee-cache", "another-cache"); return cacheManager; } } ``` 上述代码展示了如何创建两个不同名称的缓存实例:"employee-cache" 和 "another-cache"。 #### 使用 `@CacheEvict` 注解清除单个或多个缓存条目 当希望一次清空多个命名空间下的所有数据或者特定条件的数据,则可以通过设置 `cacheNames` 属性来指定要清理的目标缓存集合,并通过其他参数控制具体行为: - **allEntries**: 设置为 true 表示该方法调用后将会移除所有的缓存项而不仅仅是匹配的方法参数对应的键值对。 - **beforeInvocation**: 如果设为 true,在实际执行业务逻辑之前就会触发缓存清除动作,默认是在成功完成之后才进行处理。 下面是一个例子说明怎样利用这些特性达到目的: ```java @Service public class EmployeeService { @CacheEvict(value={"employee-cache","another-cache"}, allEntries=true) public void clearAllEmployeeData(){ System.out.println("Clearing employee data..."); } @CacheEvict(value="employee-cache", key="#id") public void deleteById(Long id){ System.out.println("Deleting employee with ID: "+id); } // More service methods... } ``` 在这个服务类里有两个被标记了 `@CacheEvict` 的方法: - 方法 `clearAllEmployeeData()` 将会一次性清除掉名为 `"employee-cache"` 及 `"another-cache"` 下面的所有记录; - 方法 `deleteById(Long id)` 则只会针对传入的具体员工编号去删除对应于 `"employee-cache"` 中的相关信息[^1]。 #### 处理复杂场景中的事务与缓存交互 如果在一个方法上同声明了 `@Transactional` 和 `@CacheEvict` ,需要注意的是默认情况下即使抛出了未被捕获的运行期异常也不会影响到已经发生的缓存更新/删除操作。这意味着除非特别指定了传播机制或者其他策略,否则一旦发生错误,虽然数据库层面可能会回滚更改但是已经被修改过的缓存状态却不会恢复原样[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值