分页场景下如何为@Cacheable动态传键及构造可复用缓存键
分页场景缓存键构造及无@Cacheable的缓存实现
一、构造适配所有分页场景的缓存键
当前的缓存键只包含country、页码和页大小,但分页查询结果还会受**排序规则(Sort)**影响——不同的排序字段、排序方向会返回完全不同的结果,必须把排序信息加入缓存键,才能保证所有分页场景的缓存都能正确复用。
方式1:通过SpEL直接扩展缓存键
直接在@Cacheable的key表达式中拼接排序信息,Spring的Sort类toString()方法会输出清晰的排序规则,适合快速实现:
@Cacheable(value = "states", key = "#country + '_' + #pageable.pageNumber + '_' + #pageable.pageSize + '_' + #pageable.sort") public Page<List<String>> findStatesByName(String country, Pageable pageable) { // 注意:原代码中创建的Sort和PageRequest未实际传入Repository,建议修正为使用pr查询 Sort sort = Sort.by(Sort.Order.asc("state")); PageRequest pr = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), sort); Page<List<String>> states = countryRepository.findStatesByName(country, pr); return states; }
方式2:自定义键生成器(更可控)
如果担心Sort.toString()的格式不稳定,可以自定义键生成器,精准控制缓存键的格式:
@Component public class PageableKeyGenerator implements KeyGenerator { @Override public Object generate(Object target, Method method, Object... params) { String country = (String) params[0]; Pageable pageable = (Pageable) params[1]; StringBuilder keyBuilder = new StringBuilder(); keyBuilder.append(country) .append("_page_").append(pageable.getPageNumber()) .append("_size_").append(pageable.getPageSize()); // 遍历排序规则,拼接字段和方向 if (pageable.getSort().isSorted()) { pageable.getSort().forEach(order -> { keyBuilder.append("_sort_").append(order.getProperty()) .append("_").append(order.getDirection().name()); }); } return keyBuilder.toString(); } }
然后在方法上指定该生成器:
@Cacheable(value = "states", keyGenerator = "pageableKeyGenerator") public Page<List<String>> findStatesByName(String country, Pageable pageable) { // 业务逻辑 }
二、不使用@Cacheable实现动态键缓存
完全可以手动通过缓存管理器实现,核心逻辑是手动生成缓存键→查询缓存→命中则返回→未命中则查库→存入缓存,灵活性更高。
示例:基于Spring CacheManager实现
@Autowired private CacheManager cacheManager; @Autowired private CountryRepository countryRepository; public Page<List<String>> findStatesByName(String country, Pageable pageable) { // 1. 动态生成缓存键 String cacheKey = generateCacheKey(country, pageable); // 2. 查询缓存 Cache cache = cacheManager.getCache("states"); if (cache != null) { Page<List<String>> cachedResult = cache.get(cacheKey, Page.class); if (cachedResult != null) { return cachedResult; } } // 3. 缓存未命中,执行数据库查询 Sort sort = Sort.by(Sort.Order.asc("state")); PageRequest pr = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), sort); Page<List<String>> states = countryRepository.findStatesByName(country, pr); // 4. 将结果存入缓存 if (cache != null) { cache.put(cacheKey, states); } return states; } // 提取缓存键生成逻辑,方便复用 private String generateCacheKey(String country, Pageable pageable) { StringBuilder keyBuilder = new StringBuilder(); keyBuilder.append(country) .append("_page_").append(pageable.getPageNumber()) .append("_size_").append(pageable.getPageSize()); if (pageable.getSort().isSorted()) { pageable.getSort().forEach(order -> { keyBuilder.append("_sort_").append(order.getProperty()) .append("_").append(order.getDirection().name()); }); } return keyBuilder.toString(); }
示例:基于RedisTemplate实现(更灵活)
如果用Redis作为缓存,可以直接操作RedisTemplate,还能自定义过期时间:
@Autowired private RedisTemplate<String, Object> redisTemplate; @Autowired private CountryRepository countryRepository; public Page<List<String>> findStatesByName(String country, Pageable pageable) { String cacheKey = generateCacheKey(country, pageable); // 查询Redis缓存 Page<List<String>> cachedResult = (Page<List<String>>) redisTemplate.opsForValue().get(cacheKey); if (cachedResult != null) { return cachedResult; } // 数据库查询逻辑 Page<List<String>> states = countryRepository.findStatesByName(country, pageable); // 存入Redis并设置1小时过期 redisTemplate.opsForValue().set(cacheKey, states, 1, TimeUnit.HOURS); return states; } private String generateCacheKey(String country, Pageable pageable) { StringBuilder keyBuilder = new StringBuilder("states_"); keyBuilder.append(country) .append("_page_").append(pageable.getPageNumber()) .append("_size_").append(pageable.getPageSize()); if (pageable.getSort().isSorted()) { pageable.getSort().forEach(order -> { keyBuilder.append("_sort_").append(order.getProperty()) .append("_").append(order.getDirection().name()); }); } return keyBuilder.toString(); }
内容的提问来源于stack exchange,提问作者user352290
相关产品推荐
相关产品推荐

