2013-07-19 15 views
11

Gibt es eine Möglichkeit, Platzhalter in @CacheEvict zu verwenden?Spring @CacheEvict mit Platzhaltern verwenden

Ich habe eine Anwendung mit Mandantenfähigkeit, die manchmal alle Daten aus dem Cache des Mandanten, aber nicht aller Mandanten im System entfernen muss.

Betrachten Sie die folgende Methode:

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

So würde Ich mag, wie etwas zu tun ist:

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

Gibt es trotzdem, es zu tun?

Antwort

1

Wie bei 99% jeder Frage im Universum ist die Antwort: es kommt darauf an. Wenn Ihr Cache-Manager etwas implementiert, das damit umgeht, großartig. Aber das scheint nicht der Fall zu sein.

Wenn Sie SimpleCacheManager verwenden, einen grundlegenden In-Memory-Cache-Manager von Spring, verwenden Sie wahrscheinlich ConcurrentMapCache, das auch mit Spring geliefert wird. Obwohl es nicht möglich ist, ConcurrentMapCache zu erweitern, um Platzhalter in Schlüsseln zu behandeln (weil der Cache-Speicher privat ist und Sie nicht darauf zugreifen können), könnten Sie ihn einfach als Inspiration für Ihre eigene Implementierung verwenden.

Unten gibt es eine mögliche Implementierung (ich habe es wirklich nicht viel anders getestet als zu überprüfen, ob es funktioniert). Dies ist eine einfache Kopie von ConcurrentMapCache mit einer Modifikation der evict() Methode. Der Unterschied ist, dass diese Version von evict() den Schlüssel behandelt, um zu sehen, ob es eine Regex ist. In diesem Fall durchläuft es alle Schlüssel im Speicher und verwirft diejenigen, die mit der Regex übereinstimmen.

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 String name; 

    private final ConcurrentMap<Object, Object> store; 

    private final boolean allowNullValues; 

    public RegexKeyCache(String name) { 
     this(name, new ConcurrentHashMap<Object, Object>(256), true); 
    } 

    public RegexKeyCache(String name, boolean allowNullValues) { 
     this(name, new ConcurrentHashMap<Object, Object>(256), allowNullValues); 
    } 

    public RegexKeyCache(String name, ConcurrentMap<Object, Object> 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 String getName() { 
     return this.name; 
    } 

    @Override 
    public final ConcurrentMap<Object, Object> 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> T get(Object key, Class<T> 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.toString().startsWith("regex:")) { 
      String r = key.toString().replace("regex:", ""); 
      for (Object k : this.store.keySet()) { 
       if (k.toString().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 { 
    } 
} 

Ich vertraue darauf, dass die Leser wissen, wie der Cache-Manager mit einer benutzerdefinierten Cache-Implementierung zu initialisieren. Es gibt eine Menge Dokumentation, die zeigt, wie man das macht.Nachdem Ihr Projekt richtig konfiguriert ist, können Sie die Anmerkung normalerweise wie so verwenden:

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

Auch dies ist bei weitem noch nicht richtig getestet, aber es gibt Ihnen eine Möglichkeit zu tun, was Sie wollen. Wenn Sie einen anderen Cache-Manager verwenden, können Sie die Cache-Implementierung ähnlich erweitern.

3

Antwort ist: Nein.

Und es gibt keinen einfachen Weg zu erreichen, was Sie wollen.

  1. Spring Cache-Annotationen müssen einfach sein, um durch den Cache-Provider einfach zu implementieren.
  2. Effizientes Caching muss einfach sein. Es gibt einen Schlüssel und einen Wert. Wenn der Schlüssel im Cache gefunden wird, verwenden Sie den Wert, andernfalls berechnen Sie den Wert und legen ihn in den Cache. Effizienter Schlüssel muss schnell und ehrlich sein equals() und hashcode(). Angenommen, Sie haben viele Paare (Schlüssel, Wert) von einem Mandanten zwischengespeichert. Für die Effizienz sollten verschiedene Schlüssel unterschiedliche Hashcode() haben. Und Sie beschließen, ganze Mieter zu vertreiben. Es ist nicht einfach, Tenant-Elemente im Cache zu finden. Sie müssen alle zwischengespeicherten Paare durchlaufen und Paare löschen, die zum Mandanten gehören. Es ist nicht effizient. Es ist eher nicht atomar, also ist es kompliziert und benötigt eine gewisse Synchronisation. Die Synchronisation ist nicht effizient.

Daher keine.

Aber, wenn Sie eine Lösung finden, sagen Sie mir, weil Feature Sie wollen, ist wirklich nützlich.

Verwandte Themen