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作为单个缓存条目的标识,无法直接基于返回列表的元素生成多个缓存键:
#result.id错误:方法返回List<Pokemon>,List本身没有id属性,直接访问会抛出空指针类错误#result?.each?.id错误:SpEL语法中不存在each关键字,且该写法无法生成有效的缓存键,导致返回null键触发异常
解决方案
要实现基于每个Pokemon ID的单独缓存,有两种标准Spring方案:
方案1:拆分方法(推荐)
将单个Pokemon的查询与列表查询拆分,利用@Cacheable缓存单个实例:
- 定义单个Pokemon的缓存查询方法
public interface PokemonRepository extends JpaRepository<Pokemon, Long> { @Cacheable(value = "pokemons", key = "#id") Optional<Pokemon> findById(Long id); }
- 在列表查询逻辑中,先从缓存加载已有数据,缺失部分再从数据库查询并缓存
@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
相关产品推荐
相关产品推荐

