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

Spring Boot能否基于响应DTO的ID缓存Pokemon数据?如何实现?

基于返回结果的Spring Cache实现问题解决

问题概述

你希望为获取Pokemon列表的方法添加缓存,要求基于返回列表中每个Pokemon的ID单独缓存,但使用@Cacheable的尝试均报错:

  • 首次尝试key = "#result.id",报错EL1012E: Cannot index into a null value
  • 二次尝试key = "#result?.each?.id",报错java.lang.IllegalArgumentException: Null key returned

错误原因分析

Spring Cache的@Cacheable注解默认是缓存整个方法的返回值,用指定的key作为单个缓存条目的标识,无法直接基于返回列表的元素生成多个缓存键:

  1. #result.id错误:方法返回List<Pokemon>,List本身没有id属性,直接访问会抛出空指针类错误
  2. #result?.each?.id错误:SpEL语法中不存在each关键字,且该写法无法生成有效的缓存键,导致返回null键触发异常

解决方案

要实现基于每个Pokemon ID的单独缓存,有两种标准Spring方案:

方案1:拆分方法(推荐)

将单个Pokemon的查询与列表查询拆分,利用@Cacheable缓存单个实例:

  1. 定义单个Pokemon的缓存查询方法
public interface PokemonRepository extends JpaRepository<Pokemon, Long> {
    @Cacheable(value = "pokemons", key = "#id")
    Optional<Pokemon> findById(Long id);
}
  1. 在列表查询逻辑中,先从缓存加载已有数据,缺失部分再从数据库查询并缓存
@Service
public class PokemonService {
    @Autowired
    private PokemonRepository pokemonRepository;
    @Autowired
    private PokemonCustomRepository pokemonCustomRepository;
    @Autowired
    private CacheManager cacheManager;

    public List<Pokemon> getPokemonByNameAndType(String name, String types, int limit, Long skip, String generation, String sort) {
        // 1. 先查询符合条件的Pokemon ID列表
        List<Long> pokemonIds = pokemonCustomRepository.getPokemonIdsByNameAndType(name, types, limit, skip, generation, sort);
        
        Cache pokemonsCache = cacheManager.getCache("pokemons");
        List<Pokemon> cachedPokemons = new ArrayList<>();
        List<Long> missingIds = new ArrayList<>();

        // 2. 遍历ID,从缓存读取已有数据,记录未命中的ID
        for (Long id : pokemonIds) {
            Pokemon cachedPokemon = pokemonsCache.get(id, Pokemon.class);
            if (cachedPokemon != null) {
                cachedPokemons.add(cachedPokemon);
            } else {
                missingIds.add(id);
            }
        }

        // 3. 查询未命中的Pokemon并存入缓存
        if (!missingIds.isEmpty()) {
            List<Pokemon> dbPokemons = pokemonRepository.findAllById(missingIds);
            dbPokemons.forEach(pokemon -> pokemonsCache.put(pokemon.getId(), pokemon));
            cachedPokemons.addAll(dbPokemons);
        }

        // 4. 按原查询规则排序(如需)
        return cachedPokemons;
    }
}

方案2:手动操作缓存(在自定义Repository实现类中)

如果不想拆分方法,可在自定义Repository的实现类中手动使用CacheManager缓存每个返回的Pokemon:

@Component
public class PokemonCustomRepositoryImpl implements PokemonCustomRepository {
    @Autowired
    private EntityManager entityManager;
    @Autowired
    private CacheManager cacheManager;

    @Override
    public List<Pokemon> getPokemonByNameAndType(String name, String types, int limit, Long skip, String generation, String sort) {
        // 执行自定义查询获取Pokemon列表
        List<Pokemon> pokemons = executeCustomQuery(name, types, limit, skip, generation, sort);
        
        // 将每个Pokemon存入缓存
        Cache pokemonsCache = cacheManager.getCache("pokemons");
        if (pokemons != null && !pokemons.isEmpty()) {
            pokemons.forEach(pokemon -> pokemonsCache.put(pokemon.getId(), pokemon));
        }
        
        return pokemons;
    }

    private List<Pokemon> executeCustomQuery(String name, String types, int limit, Long skip, String generation, String sort) {
        // 此处编写你的JPQL/原生SQL查询逻辑
        String jpql = "SELECT p FROM Pokemon p WHERE 1=1";
        // 拼接条件、设置参数、分页排序
        TypedQuery<Pokemon> query = entityManager.createQuery(jpql, Pokemon.class);
        query.setMaxResults(limit);
        query.setFirstResult(skip.intValue());
        // ...其他参数设置
        return query.getResultList();
    }
}

补充:缓存整个列表的方案

如果你的需求是缓存整个查询结果而非单个Pokemon,可直接用方法参数作为缓存键:

@Cacheable(value = "pokemonLists", key = "{#name, #types, #limit, #skip, #generation, #sort}")
List<Pokemon> getPokemonByNameAndType(String name, String types, int limit, Long skip, String generation, String sort);

内容的提问来源于stack exchange,提问作者Prashant Aghara

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 15:00:00