90 votes

@Cacheable key on multiple method arguments

De la documentation sur le printemps :

@Cacheable(value="bookCache", key="isbn")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

Comment puis-je spécifier @Cachable à utiliser isbn y checkWarehouse comme clé ?

107voto

Boris Treukhov Points 5564

Mise à jour : L'implémentation actuelle du cache de Spring utilise tous les paramètres de la méthode comme clé de cache si ce n'est pas spécifié autrement. Si vous souhaitez utiliser des clés sélectionnées, reportez-vous à la rubrique La réponse d'Arjan qui utilise la liste SpEL {#isbn, #includeUsed} ce qui est la façon la plus simple de créer des clés uniques.

De Documentation sur le printemps

La stratégie de génération de clés par défaut a changé avec la sortie de Spring 4.0. Les versions antérieures de Spring utilisaient une stratégie de génération de clés qui, pour les paramètres de clés multiples, ne prenait en compte que le hashCode() des paramètres paramètres et non equals(), ce qui pouvait provoquer des collisions de clés inattendues (voir SPR-10237 pour le contexte). La nouvelle stratégie SimpleKeyGenerator" utilise une clé composée pour de tels scénarios.

Avant Spring 4.0

Je vous suggère de concaténer les valeurs des paramètres dans l'expression Spel avec quelque chose comme key="#checkWarehouse.toString() + #isbn.toString()") Je pense que cela devrait fonctionner car org.springframework.cache.interceptor.ExpressionEvaluator renvoie un objet, qui est ensuite utilisé comme clé, de sorte que vous n'avez pas à fournir un nom de domaine. int dans votre expression SPEL.

Quant au code de hachage avec une forte probabilité de collision, vous ne peut pas l'utiliser comme clé.

Quelqu'un dans ce fil a suggéré d'utiliser T(java.util.Objects).hash(#p0,#p1, #p2) mais cela ne fonctionnera pas et cette approche est facile à casser, par exemple j'ai utilisé les données de SPR-9377 :

    System.out.println( Objects.hash("someisbn", new Integer(109), new Integer(434)));
    System.out.println( Objects.hash("someisbn", new Integer(110), new Integer(403)));

Les deux lignes affichent -636517714 dans mon environnement.

P.S. En fait, dans la documentation de référence, nous avons

@Cacheable(value="books", key="T(someType).hash(#isbn)") 
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

Je pense que cet exemple est erroné et trompeur et devrait être retiré de la documentation, car les clés doivent être uniques.

P.P.S. voir aussi https://jira.springsource.org/browse/SPR-9036 pour des idées intéressantes concernant la génération de clés par défaut.

J'aimerais ajouter, par souci d'exactitude et pour le plaisir des mathématiques et de l'informatique, qu'à la différence du hachage intégré, l'utilisation d'un fichier sécurisé fonction de hachage cryptographique comme MD5 ou SHA256, en raison des propriétés de ces fonctions. IS absolument possible pour cette tâche, mais le calculer à chaque fois peut être trop coûteux, consultez par exemple le cours de cryptographie de Dan Boneh pour en savoir plus.

93voto

Arjan Points 7154

Après quelques tests limités avec Spring 3.2, il semble que l'on puisse utiliser une liste SpEL : {..., ..., ...} . Cela peut également inclure null valeurs. Spring transmet la liste comme clé à l'implémentation réelle du cache. Lorsque vous utilisez Ehcache, vous invoquerez à un moment donné Liste#hashCode() qui tient compte de tous ses éléments. (Je ne suis pas sûr que Ehcache seulement s'appuie sur le code de hachage).

Je l'utilise pour un cache partagé, dans lequel je inclure le nom de la méthode dans la clé également, ce que le générateur de clés par défaut de Spring ne comprend pas . De cette façon, je peux facilement effacer le cache (unique), sans risquer (trop...) de faire correspondre les clés pour différentes méthodes. Par exemple :

@Cacheable(value="bookCache", 
  key="{ #root.methodName, #isbn?.id, #checkWarehouse }")
public Book findBook(ISBN isbn, boolean checkWarehouse) 
...

@Cacheable(value="bookCache", 
  key="{ #root.methodName, #asin, #checkWarehouse }")
public Book findBookByAmazonId(String asin, boolean checkWarehouse)
...

Bien sûr, si de nombreuses méthodes en ont besoin et que vous utilisez toujours tous les paramètres pour votre clé, on peut aussi définir un générateur de clé personnalisé qui inclut le nom de la classe et de la méthode :

<cache:annotation-driven mode="..." key-generator="cacheKeyGenerator" />
<bean id="cacheKeyGenerator" class="net.example.cache.CacheKeyGenerator" />

...avec :

public class CacheKeyGenerator 
  implements org.springframework.cache.interceptor.KeyGenerator {

    @Override
    public Object generate(final Object target, final Method method, 
      final Object... params) {

        final List<Object> key = new ArrayList<>();
        key.add(method.getDeclaringClass().getName());
        key.add(method.getName());

        for (final Object o : params) {
            key.add(o);
        }
        return key;
    }
}

5voto

Biju Kunjummen Points 20258

Vous pouvez utiliser une expression Spring-EL, par exemple sur le JDK 1.7 :

@Cacheable(value="bookCache", key="T(java.util.Objects).hash(#p0,#p1, #p2)")

3voto

Vous pouvez utiliser la classe SimpleKey de Spring

@Cacheable(value = "bookCache", key = "new org.springframework.cache.interceptor.SimpleKey(#isbn, #checkWarehouse)")

1voto

Niraj Singh Points 69

Cela va fonctionner

@Cacheable(value="bookCache", key="#checkwarehouse.toString().append(#isbn.toString())")

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X