Printemps @CacheEvict à l’aide de caractères génériques

Est-il possible d’utiliser des caractères génériques dans @CacheEvict?

J’ai une application avec plusieurs locataires qui nécessite parfois d’expulser toutes les données du cache du locataire, mais pas de tous les locataires du système.

Considérez la méthode suivante:

@Cacheable(value="users", key="T(Security).getTenant() + #user.key") public List getUsers(User user) { ... } 

Donc, j’aimerais faire quelque chose comme:

 @CacheEvict(value="users", key="T(Security).getTenant() + *") public void deleteOrganization(Organization organization) { ... } 

Y a-t-il un moyen de le faire?

Comme pour 99% des questions de l’univers, la réponse est: cela dépend. Si votre gestionnaire de cache implémente quelque chose qui traite de cela, tant mieux. Mais cela ne semble pas être le cas.

Si vous utilisez SimpleCacheManager , un gestionnaire de cache en mémoire de base fourni par Spring, vous utilisez probablement ConcurrentMapCache fourni avec Spring. Bien qu’il ne soit pas possible d’étendre ConcurrentMapCache pour traiter les caractères génériques dans les clés (car le magasin de cache est privé et vous ne pouvez pas y accéder), vous pouvez simplement l’utiliser comme source d’inspiration pour votre propre implémentation.

Ci-dessous, une implémentation possible (je ne l’ai pas vraiment testée à part pour vérifier si elle fonctionnait). Ceci est une copie ConcurrentMapCache de ConcurrentMapCache avec une modification de la méthode evict() . La différence est que cette version de evict() traite la clé pour voir si c’est une regex. Dans ce cas, il parcourt toutes les clés du magasin et supprime celles qui correspondent à la regex.

 package com.sigraweb.cache; import java.io.Serializable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.springframework.cache.Cache; import org.springframework.cache.support.SimpleValueWrapper; import org.springframework.util.Assert; public class RegexKeyCache implements Cache { private static final Object NULL_HOLDER = new NullHolder(); private final Ssortingng name; private final ConcurrentMap store; private final boolean allowNullValues; public RegexKeyCache(Ssortingng name) { this(name, new ConcurrentHashMap(256), true); } public RegexKeyCache(Ssortingng name, boolean allowNullValues) { this(name, new ConcurrentHashMap(256), allowNullValues); } public RegexKeyCache(Ssortingng name, ConcurrentMap store, boolean allowNullValues) { Assert.notNull(name, "Name must not be null"); Assert.notNull(store, "Store must not be null"); this.name = name; this.store = store; this.allowNullValues = allowNullValues; } @Override public final Ssortingng getName() { return this.name; } @Override public final ConcurrentMap getNativeCache() { return this.store; } public final boolean isAllowNullValues() { return this.allowNullValues; } @Override public ValueWrapper get(Object key) { Object value = this.store.get(key); return toWrapper(value); } @Override @SuppressWarnings("unchecked") public  T get(Object key, Class type) { Object value = fromStoreValue(this.store.get(key)); if (value != null && type != null && !type.isInstance(value)) { throw new IllegalStateException("Cached value is not of required type [" + type.getName() + "]: " + value); } return (T) value; } @Override public void put(Object key, Object value) { this.store.put(key, toStoreValue(value)); } @Override public ValueWrapper putIfAbsent(Object key, Object value) { Object existing = this.store.putIfAbsent(key, value); return toWrapper(existing); } @Override public void evict(Object key) { this.store.remove(key); if (key.toSsortingng().startsWith("regex:")) { Ssortingng r = key.toSsortingng().replace("regex:", ""); for (Object k : this.store.keySet()) { if (k.toSsortingng().matches(r)) { this.store.remove(k); } } } } @Override public void clear() { this.store.clear(); } protected Object fromStoreValue(Object storeValue) { if (this.allowNullValues && storeValue == NULL_HOLDER) { return null; } return storeValue; } protected Object toStoreValue(Object userValue) { if (this.allowNullValues && userValue == null) { return NULL_HOLDER; } return userValue; } private ValueWrapper toWrapper(Object value) { return (value != null ? new SimpleValueWrapper(fromStoreValue(value)) : null); } @SuppressWarnings("serial") private static class NullHolder implements Serializable { } } 

J’espère que les lecteurs sauront initialiser le gestionnaire de cache avec une implémentation de cache personnalisée. Il existe de nombreux documents qui vous montrent comment faire cela. Une fois votre projet correctement configuré, vous pouvez utiliser l’annotation normalement comme suit:

 @CacheEvict(value = { "cacheName" }, key = "'regex:#tenant'+'.*'") public myMethod(Ssortingng tenant){ ... } 

Encore une fois, ceci est loin d’être correctement testé, mais cela vous donne le moyen de faire ce que vous voulez. Si vous utilisez un autre gestionnaire de cache, vous pouvez également étendre son implémentation de cache.

La réponse est: non.

Et ce n’est pas un moyen facile d’atteindre ce que vous voulez.

  1. Les annotations Spring Cache doivent être simples pour pouvoir être facilement implémentées par le fournisseur de cache.
  2. La mise en cache efficace doit être simple. Il y a une clé et une valeur. Si key est trouvé dans le cache, utilisez la valeur, sinon calculez la valeur et mettez-la en cache. La clé efficace doit avoir des équals rapides et honnêtes () et hashcode () . Supposons que vous ayez mis en cache plusieurs paires (clé, valeur) d’un client hébergé. Pour des raisons d’ efficacité, les différentes clés doivent avoir un hashcode () différent . Et vous décidez d’expulser tout le locataire. Il n’est pas facile de trouver des éléments locataires dans le cache. Vous devez itérer toutes les paires mises en cache et ignorer les paires appartenant au locataire. Ce n’est pas efficace. Ce n’est plutôt pas atomique, donc c’est compliqué et a besoin d’une certaine synchronisation. La synchronisation n’est pas efficace.

Donc non

Mais si vous trouvez une solution, dites-moi, car la fonctionnalité que vous souhaitez est vraiment utile.

Ci-dessous a fonctionné pour moi sur Redis Cache. Supposons que vous souhaitiez supprimer toutes les entrées du cache avec le préfixe de clé: ‘nom-cache: nom-object: clé-parent’. Méthode d’appel avec valeur de clé cache-name:object-name:parentKey* .

 import org.springframework.data.redis.core.RedisOperations; ... private final RedisOperations redisTemplate; ... public void evict(Object key) { redisTemplate.delete(redisTemplate.keys(key)); } 

De RedisOperations.java

 /** * Delete given {@code keys}. * * @param keys must not be {@literal null}. * @return The number of keys that were removed. * @see Redis Documentation: DEL */ void delete(Collection keys); /** * Find all keys matching the given {@code pattern}. * * @param pattern must not be {@literal null}. * @return * @see Redis Documentation: KEYS */ Set keys(K pattern);