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

分页场景下如何为@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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 07:50:25