@Cacheable Schlüssel für mehrere Methodenargumente

75

Aus der Frühjahrsdokumentation :

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

Wie kann ich angeben @Cachable, ob isbnund checkWarehouseals Schlüssel verwendet werden soll?

Phury
quelle

Antworten:

88

Update : Die aktuelle Spring-Cache-Implementierung verwendet alle Methodenparameter als Cache-Schlüssel, sofern nicht anders angegeben. Wenn Sie ausgewählte Schlüsselverwenden möchten, lesen Sie die Antwort von Arjan, in der die SpEL-Liste verwendet wird. Dies{#isbn, #includeUsed}ist der einfachste Weg, um eindeutige Schlüssel zu erstellen.

Aus der Frühlingsdokumentation

Die Standardstrategie zur Schlüsselgenerierung wurde mit der Veröffentlichung von Spring 4.0 geändert. Frühere Versionen von Spring verwendeten eine Schlüsselgenerierungsstrategie, bei der für mehrere Schlüsselparameter nur der hashCode () der Parameter und nicht gleich () berücksichtigt wurde. Dies kann zu unerwarteten Schlüsselkollisionen führen (Hintergrund siehe SPR-10237). Der neue 'SimpleKeyGenerator' verwendet für solche Szenarien einen zusammengesetzten Schlüssel.

Vor dem Frühjahr 4.0

Ich empfehle Ihnen, die Werte der Parameter im Spel-Ausdruck mit etwas zu verknüpfen key="#checkWarehouse.toString() + #isbn.toString()"), das meiner Meinung nach funktionieren sollte, da org.springframework.cache.interceptor.ExpressionEvaluator Object zurückgibt, das später als Schlüssel verwendet wird, damit Sie es nicht angeben müssen ein intin Ihrem SPEL-Ausdruck.

Der Hash-Code mit einer hohen Kollisionswahrscheinlichkeit kann nicht als Schlüssel verwendet werden.

Jemand in diesem Thread hat vorgeschlagen, zu verwenden, T(java.util.Objects).hash(#p0,#p1, #p2)aber es wird nicht funktionieren und dieser Ansatz ist leicht zu brechen, zum Beispiel habe ich die Daten von SPR-9377 verwendet :

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

Beide Zeilen drucken -636517714 in meiner Umgebung.

PS Eigentlich in der Referenzdokumentation haben wir

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

Ich denke, dass dieses Beispiel FALSCH und irreführend ist und aus der Dokumentation entfernt werden sollte, da die Schlüssel eindeutig sein sollten.

PPS finden Sie auch unter https://jira.springsource.org/browse/SPR-9036 für einige interessante Ideen zur Standardschlüsselgenerierung.

Ich möchte aus Gründen der Korrektheit und als unterhaltsame Tatsache hinzufügen , dass eine sichere Verwendung von verschlüsselter Hash - Funktion wie SHA256, aufgrund der Eigenschaften solchen Funktion IST für diese Aufgabe möglich, aber es zu berechnen , jedes Mal kann zu teuer sein.

Boris Treukhov
quelle
hmm ich denke spr-9036 ignoriert die Situation, in der ein Parameter ein Array ist, da Arrays standardmäßig nicht tief gleich sind
jasonk
4
Anonyme Abstimmungen sind äußerst nützlich! Entschuldigung, aber Telepathie ist derzeit nicht verfügbar.
Boris Treukhov
Entspricht diese Antwort der aktuellen Schlüsselwoche für die Frühlingsversion 3.1.1? SPR-9377 wurde behoben für 3.1.1, nein? Könnte jemand einen UPDATED- Abschnitt für diese Antwort hinzufügen ?
Cherry
Funktioniert auch T(someType).hash(#isbn)im Frühling 3.1.1?
Cherry
@Cherry Versuchen Sie, Arjans Antwort zu verwenden ( { #root.methodName, #param1, #param2 }jedenfalls ist 3.1.1 jetzt eine alte Version.
Boris Treukhov
76

Nach einigen begrenzten Tests mit Spring 3.2 scheint es möglich zu sein, eine Rechtschreibliste zu verwenden : {..., ..., ...}. Dies kann auch nullWerte enthalten. Spring übergibt die Liste als Schlüssel für die eigentliche Cache-Implementierung. Wenn Sie Ehcache verwenden, wird dies irgendwann List # hashCode () aufrufen , das alle seine Elemente berücksichtigt. (Ich bin nicht sicher, ob Ehcache nur auf dem Hash-Code basiert .)

Ich verwende dies für einen gemeinsam genutzten Cache, in den ich auch den Methodennamen in den Schlüssel einbinde, den der Spring-Standardschlüsselgenerator nicht enthält . Auf diese Weise kann ich den (einzelnen) Cache leicht löschen, ohne (zu viel ...) das Risiko einzugehen, dass Schlüssel für verschiedene Methoden übereinstimmen. Mögen:

@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)
...

Wenn dies von vielen Methoden benötigt wird und Sie immer alle Parameter für Ihren Schlüssel verwenden, können Sie natürlich auch einen benutzerdefinierten Schlüsselgenerator definieren, der den Klassen- und Methodennamen enthält:

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

...mit:

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;
    }
}
Arjan
quelle
1
Wie kann ich den benutzerdefinierten KeyGenerator in einer XML-freien Konfiguration abholen lassen?
Basil
Vielen Dank für den Hinweis {..., ..., ...}. In der aktuellen Dokumentation heißt es, dass die Standardschlüsselgenerierung alle Parameter berücksichtigt. Also keine Notwendigkeit zu erstellen CacheKeyGenerator.
Linqu
Wenn dies bei Ihnen nicht funktioniert, kennt Ihr Code möglicherweise keine Argumentnamen. Verwenden Sie daher # a0 anstelle von #asin, a1 anstelle von #checkWarehouse usw. (auch # p0, # p1). Dies geschieht für mich in spring-data-jpa
Cipous
2
Ein bisschen spät, @linqu, aber der Standardschlüsselgenerator enthält nicht den Methodennamen. Wenn ein einzelner Cache für mehrere Methoden verwendet werden soll, muss der Methodenname angegeben werden, wenn zwei Methoden dieselben Parameter haben können.
Arjan
5

Sie können einen Spring-EL-Ausdruck verwenden, z. B. für JDK 1.7:

@Cacheable(value="bookCache", key="T(java.util.Objects).hash(#p0,#p1, #p2)")
Biju Kunjummen
quelle
Und was ist mit den Kollisionen? PS Ja, das ist es, was sie in der Referenz sagen, aber die ganze Idee ist sehr seltsam
Boris Treukhov
PPS sagen sie eigentlich nicht in der Referenz zu tun - das einzige Beispiel mit Hashing ist @Cacheable(value="books", key="T(someType).hash(#isbn)") public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)- aber es scheint ein falsches und irreführendes Beispiel zu sein
Boris Treukhov
Eigentlich habe ich ein Gegenbeispiel mit Google gefunden (siehe meine Antwort)
Boris Treukhov
@BorisTreukhov, es ging darum zu zeigen, wie Argumente verwendet werden können, um einen Schlüssel mit Spring-EL zu erstellen, keine Lösung, die als robust angesehen werden kann. Ich stimme zu, dass die wahrscheinlich einfachste Lösung darin besteht, die Argumente einfach miteinander zu verketten, was wiederum mit verwendet werden kann Spring-EL.
Biju Kunjummen
1
Ich verstehe, aber das Problem besteht nicht darin, welche Lösung einfacher ist - das Problem ist, dass die Verwendung von Hashcode einfach gefährlich ist - wenn ein Client zufällig ein Schlüsselpaar 109/434 und ein anderer - 110/403 - erhält, können sie sich beispielsweise gegenseitig sehen Nachrichten im Forum oder Kontobetrieb in der Internetbank. Ich bin mir sicher, dass viel mehr Kollisionen möglich sind - gute Hash-Funktionen sind schwer zu implementieren (überlegen Sie sich die Quelle der MD5-Implementierungen - es ist klar, dass sie sich nicht mit einer magischen Zahl multiplizieren), und dennoch werden sie niemals eindeutig zurückgeben können Werte.
Boris Treukhov
1

Sie können die Spring SimpleKey-Klasse verwenden

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

Das wird funktionieren

@Cacheable(value="bookCache", key="#checkwarehouse.toString().append(#isbn.toString())")
Niraj Singh
quelle
Ich habe spring-data-jpa verwendet und es muss a0 als Argument für die erste Position verwendet werden, anstatt den Namen zu verwenden ...
Cipous
-2

Benutze das

@Cacheable(value="bookCache", key="#isbn + '_' + #checkWarehouse + '_' + #includeUsed")
Gavy
quelle
1
Diese Kombination ist immer eindeutig, da Sie nur die Zeichenfolgenwertkombinationen zum Schlüssel hinzufügen.
Veswanth