Spring @Cacheable для метода без параметра

#spring #spring-cache

#spring #spring-кэш

Вопрос:

Я хочу кэшировать некоторые данные БД. например, кэшировать Customer и использовать customer.id в качестве ключа.

Как я могу установить ключ, если я хочу загрузить всех клиентов ( allCustomer() в коде)?

 @Cacheable(value = "customer", key = "#customerID")
public Customer getCustomer(Long customerID) {
    return getCustomerData(customerID);
}

// How to setup this key?
@Cacheable(value = "customer", key = "?")
public List<Customer> allCustomer(){
    return db.values().stream().collect(Collectors.toList());
}
    
 @CachePut(value = "customer", key = "#customer.id")
public void updateCustomer(Customer customer){
    db.put(customer.getId(), customer);
}

@CacheEvict(value = "customer", key = "#customerID")
public void deleteCustomer(Long customerID){
    db.remove(customerID);
}
 

Ответ №1:

Я бы рекомендовал использовать @CachePut вместо @Cacheable . В случае, если новая запись добавляется в базу данных извне этого экземпляра приложения, кэш не будет содержать это новое значение.

Вы можете использовать #result.id , чтобы указать Spring, какое значение использовать в качестве ключа, и я включил условие, чтобы вы не получали странных ошибок в случае нулевого значения.

 @CachePut(value = "customer", key = "#result.id", condition = "#result != null")
 

Ответ №2:

Это невозможно сделать для коллекций с аннотациями Spring — у @Cacheable вас будет только один элемент в кэше с вычисляемым ключом и значением со всем списком внутри.

Если производительность в вашем приложении не так важна, используйте getCustomer(...) in a loop .

В противном случае вам потребуется обновить кэш вручную. К сожалению, интерфейс кэша не предоставляет метода для извлечения всех ключей / значений / пар ключ-значение из кэша, поэтому требуется небольшое приведение.

Пример для кэша в памяти по умолчанию ( spring.cache.type=simple ):

 @Autowired
private org.springframework.CacheManager cacheManager;

public List<Customer> allCustomers() {
    ConcurrentMap<Long, Customer> customerCache = (ConcurrentMap<Long, Customer>) 
        cacheManager.getCache("customer").getNativeCache();

    if (!customerCache.isEmpty()) {
        return new ArrayList<>(customerCache.values());
    }

    List<Customer> customers = db.values().stream().collect(Collectors.toList());

    customers.forEach(customer -> customerCache.put(customer.getId(), customer));

    return customers;
}
 

Или для spring.cache.type=jcache с резервным EhCache 3:

 @Autowired
private org.springframework.CacheManager cacheManager;

public List<Customer> allCustomers() {
    javax.cache.Cache<Long, Customer> customerCache = (javax.cache.Cache<Long, Customer>) 
        cacheManager.getCache("customer").getNativeCache();
    Iterator<Cache.Entry<Long, Customer>> iterator = customerCache.iterator();

    List<Customer> cachedCustomers = new ArrayList<>();
    while (iterator.hasNext()) {
        Cache.Entry<Long, Customer> entry = iterator.next();
        cachedCustomers.add(entry.getValue());
    }
    if (!cachedCustomers.isEmpty()) {
        return cachedCustomers;
    }

    List<Customer> customers = db.values().stream().collect(Collectors.toList());

    customers.forEach(customer -> customerCache.put(customer.getId(), customer));

    return customers;
}
 

То же самое можно сделать аналогичным образом для любого другого типа кэша (redis, hazelcast, caffeine и т. Д.).

Соответствующий метод удаления может быть написан намного проще:

 @CacheEvict(value = "customer", allEntries = true)
public void deleteAllCustomers(){
    db.removeAll(); //pseudocode
}