Spring整合Redis:CacheManager、CacheResolver与KeyGenerator的使用方法及缓存查询问题咨询
Hey there! Let's break down your two main pain points with Spring Cache and Redis, and walk through practical solutions using the components you mentioned (CacheManager, CacheResolver, KeyGenerator).
1. Caching POJO Collections Using a POJO Property as the Key
The default @Cacheable annotation caches the entire method return value (like a List<Person>) under a single key, but you want to cache each individual Person using its id as the key. Here are two workable approaches:
Option 1: Manual Cache Population with CacheManager
If you still want to leverage Spring Cache infrastructure but need to split the collection into individual cache entries, you can fetch the cache instance directly via CacheManager and populate entries one by one:
@Service public class PersonService { private final CacheManager cacheManager; private final PersonRepository personRepository; public PersonService(CacheManager cacheManager, PersonRepository personRepository) { this.cacheManager = cacheManager; this.personRepository = personRepository; } public List<Person> getAllPersons() { List<Person> persons = personRepository.findAll(); Cache personCache = cacheManager.getCache("personCache"); // Cache each Person with its id as the key for (Person person : persons) { personCache.put(person.getId(), person); } return persons; } }
Option 2: Use @CachePut for Individual Entries
If your workflow involves saving/updating single Person objects, you can use @CachePut to automatically cache each entry with its id:
@CachePut(value = "personCache", key = "#person.id") public Person savePerson(Person person) { return personRepository.save(person); }
Then you can retrieve individual cached entries easily with @Cacheable:
@Cacheable(value = "personCache", key = "#id") public Person getPersonById(Long id) { return personRepository.findById(id).orElse(null); }
If you also need to cache the full list of persons, you can add an extra cache entry for the list of ids, so you can quickly fetch all cached entries later:
public List<Person> getAllPersons() { Cache personCache = cacheManager.getCache("personCache"); Cache personIdsCache = cacheManager.getCache("personIdsCache"); // Check if the list of ids exists in cache List<Long> personIds = personIdsCache.get("allIds", List.class); if (personIds != null) { return personIds.stream() .map(id -> personCache.get(id, Person.class)) .collect(Collectors.toList()); } // Fetch from DB if not cached List<Person> persons = personRepository.findAll(); List<Long> ids = persons.stream().map(Person::getId).collect(Collectors.toList()); // Cache individual persons and the id list persons.forEach(person -> personCache.put(person.getId(), person)); personIdsCache.put("allIds", ids); return persons; }
2. Querying and Searching Data in the Cache Provider
Spring Cache's abstraction is built around method-level caching, so it doesn't offer out-of-the-box query APIs. To search cached data, you need to interact directly with Redis (your underlying cache provider):
Approach 1: Use RedisTemplate for Direct Queries
Inject RedisTemplate to leverage Redis's native APIs for search operations:
@Service public class PersonCacheSearchService { private final RedisTemplate<String, Person> redisTemplate; public PersonCacheSearchService(RedisTemplate<String, Person> redisTemplate) { this.redisTemplate = redisTemplate; } // Search by key pattern (e.g., all entries in personCache) public List<Person> searchPersonsByKeyPattern(String pattern) { Set<String> keys = redisTemplate.keys("personCache::" + pattern); if (keys == null || keys.isEmpty()) { return Collections.emptyList(); } return redisTemplate.opsForValue().multiGet(keys); } // Search by Person's name (using Redis Hash structure for better field-based queries) public List<Person> searchPersonsByName(String name) { // Assume Persons are stored in a Redis Hash named "personHash" (key: person id, value: Person object) Set<String> personIds = redisTemplate.opsForHash().keys("personHash"); List<Person> result = new ArrayList<>(); for (String id : personIds) { Person person = (Person) redisTemplate.opsForHash().get("personHash", id); if (person != null && person.getName().contains(name)) { result.add(person); } } return result; } }
Approach 2: Extend RedisCacheManager for Custom Search
If you want search logic aligned with Spring Cache's style, you can extend RedisCacheManager to encapsulate query logic:
@Component public class CustomRedisCacheManager extends RedisCacheManager { private final RedisConnectionFactory connectionFactory; public CustomRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfig, RedisConnectionFactory connectionFactory) { super(cacheWriter, defaultCacheConfig); this.connectionFactory = connectionFactory; } public List<Person> searchInCache(String cacheName, String namePattern) { RedisCache cache = (RedisCache) this.getCache(cacheName); if (cache == null) { return Collections.emptyList(); } // Use Redis connection to execute search try (RedisConnection conn = connectionFactory.getConnection()) { Set<byte[]> keys = conn.keys((cache.getName() + "::*").getBytes()); List<Person> persons = new ArrayList<>(); for (byte[] key : keys) { byte[] value = conn.get(key); if (value != null) { Person person = cache.getCacheConfiguration().getConversionService() .convert(value, Person.class); if (person.getName().contains(namePattern)) { persons.add(person); } } } return persons; } } }
A Quick Note on CacheResolver
CacheResolver is used to dynamically select which cache(s) to use based on business logic or method parameters. For example, if you have versioned caches (personCacheV1, personCacheV2), you can use it to pick the right one:
@Component public class PersonCacheResolver implements CacheResolver { private final CacheManager cacheManager; public PersonCacheResolver(CacheManager cacheManager) { this.cacheManager = cacheManager; } @Override public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context) { // Select cache based on the version parameter String version = (String) context.getArgs()[0]; Cache targetCache = "v1".equals(version) ? cacheManager.getCache("personCacheV1") : cacheManager.getCache("personCacheV2"); return Collections.singletonList(targetCache); } }
Then use it in your cache annotation:
@Cacheable(cacheResolver = "personCacheResolver") public Person getPersonByVersion(String version, Long id) { return personRepository.findById(id).orElse(null); }
内容的提问来源于stack exchange,提问作者user2354302

