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

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;
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 11:44:05