You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring分页请求缓存及数据清除问题咨询

这个问题我之前也碰到过,Spring Cache 默认的 @CacheEvict 确实没法直接按前缀清除一批缓存条目,不过有几种靠谱的解决办法,看你用的缓存实现和需求来选:

1. 如果你用的是支持模糊匹配的缓存(比如 Redis)

这是最省心的方案,直接在修改用户数据的方法上用 @CacheEvict,指定匹配该用户所有缓存条目的 key 模式就行:

@CacheEvict(value = "foo", key = "#userId + '*'")
public void updateUserData(String userId, UserUpdateRequest request) {
    // 你的用户数据修改逻辑
}

不过要注意,得确保你的缓存配置支持模糊删除。比如用 Redis 的话,需要在 CacheManager 配置里开启 key 的前缀(默认一般是开启的),Redis 本身支持 SCAN 命令匹配 key,Spring 的 RedisCache 会自动处理这种模糊删除逻辑。

2. 通用方案:手动通过 CacheManager 清除指定用户的缓存

如果你的缓存实现不支持模糊匹配(比如默认的 ConcurrentMapCache),可以手动注入 CacheManager,然后遍历缓存条目删除符合条件的:

@Autowired
private CacheManager cacheManager;

public void updateUserData(String userId, UserUpdateRequest request) {
    // 先执行用户数据修改逻辑
    updateUserInDb(userId, request);

    // 清除该用户的所有分页缓存
    Cache cache = cacheManager.getCache("foo");
    if (cache != null) {
        if (cache instanceof ConcurrentMapCache) {
            // 针对内存缓存的处理
            ConcurrentMap<Object, Object> nativeCache = ((ConcurrentMapCache) cache).getNativeCache();
            Iterator<Map.Entry<Object, Object>> iterator = nativeCache.entrySet().iterator();
            while (iterator.hasNext()) {
                Map.Entry<Object, Object> entry = iterator.next();
                Object cacheKey = entry.getKey();
                // 你的缓存 key 是 {userId, page, size} 的数组,取第一个元素判断
                if (cacheKey instanceof Object[] && userId.equals(((Object[]) cacheKey)[0])) {
                    iterator.remove();
                }
            }
        } else {
            // 如果是其他缓存(比如 Caffeine),可以用对应的 API 处理
            // 比如 Caffeine 可以通过 asMap() 获取缓存的 Map 视图,再遍历删除
        }
    }
}

这种方法的好处是适配所有缓存实现,缺点是需要自己写遍历逻辑,缓存条目多时可能有轻微性能开销。

3. 优化缓存 key 设计,简化清除逻辑

如果你能接受调整缓存粒度,可以把同一个用户的所有分页缓存放到一个条目里:用 userId 作为缓存 key,缓存值是一个 Map,key 是 page_size 的组合,value 是对应的 Bar 对象:

@Autowired
private CacheManager cacheManager;

@Cacheable(value = "foo", key = "#userId")
public Map<String, Bar> getPaginatedResponse(String userId, int page, int size) {
    String pageSizeKey = page + "_" + size;
    Cache cache = cacheManager.getCache("foo");
    Map<String, Bar> userPageCache = cache.get(userId, Map.class);

    // 缓存存在且包含目标分页数据,直接返回
    if (userPageCache != null && userPageCache.containsKey(pageSizeKey)) {
        return userPageCache;
    }

    // 查询数据库获取目标分页数据
    Bar bar = fetchBarFromDb(userId, page, size);
    // 初始化或更新缓存的 Map
    if (userPageCache == null) {
        userPageCache = new HashMap<>();
    }
    userPageCache.put(pageSizeKey, bar);
    cache.put(userId, userPageCache);
    return userPageCache;
}

然后修改用户数据时,只需要清除该 userId 对应的单个缓存条目:

@CacheEvict(value = "foo", key = "#userId")
public void updateUserData(String userId, UserUpdateRequest request) {
    // 修改逻辑
}

这种方法的优点是清除逻辑超级简单,缺点是缓存粒度变大——只要用户任意一页数据更新,整个用户的分页缓存都会失效,适合分页数据更新不频繁的场景。

内容的提问来源于stack exchange,提问作者o.m.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 07:51:46