Spring @CacheEvict с использованием подстановочных знаков

Есть ли способ использования подстановочных знаков в @CacheEvict?

У меня есть приложение с многопользовательской лицензией, которое иногда требует выселения всех данных из кэша арендатора, но не всех арендаторов в системе.

Рассмотрим следующий метод:

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

Итак, я хотел бы сделать что-то вроде:

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

Есть ли способ сделать это?

Ответ 1

Как и в случае с 99% каждого вопроса во Вселенной, ответ таков: это зависит. Если ваш менеджер кэша реализует что-то, что с этим связано, отлично. Но, похоже, это не так.

Если вы используете SimpleCacheManager, который является базовым менеджером кэшей в памяти, предоставляемым Spring, вы, вероятно, используете ConcurrentMapCache, который также поставляется с Spring. Хотя невозможно расширить ConcurrentMapCache для борьбы с подстановочными знаками в ключах (поскольку хранилище кеша является закрытым, и вы не можете получить к нему доступ), вы можете просто использовать его как вдохновение для своей собственной реализации.

Ниже приведена возможная реализация (я действительно не тестировал ее, кроме как проверить, работает ли она). Это простая копия ConcurrentMapCache с модификацией метода evict(). Разница в том, что эта версия evict() рассматривает ключ, чтобы увидеть, является ли это регулярным выражением. В этом случае он выполняет итерацию через все ключи в хранилище и вытесняет те, которые соответствуют регулярному выражению.

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 {
    }
}

Я верю, что читатели знают, как инициализировать диспетчер кэша с помощью реализации пользовательского кэша. Там много документации, которая показывает вам, как это сделать. После того, как ваш проект настроен правильно, вы можете обычно использовать аннотацию следующим образом:

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

Опять же, это далеко не правильно проверено, но оно дает вам способ делать то, что вы хотите. Если вы используете другой менеджер кэша, вы можете так же расширить реализацию кэша.

Ответ 2

Ответ: Нет.

И это не простой способ добиться того, чего вы хотите.

  • Spring Аннотации кэша должны быть простыми, чтобы их было легко реализовать поставщиком кешей.
  • Эффективное кэширование должно быть простым. Есть ключ и ценность. Если ключ находится в кеше, используйте значение, иначе вычислите значение и поместите в кеш. Эффективный ключ должен иметь быстрый и честный equals() и hashcode(). Предположим, вы кэшировали много пар (ключ, значение) от одного арендатора. Для эффективности разные ключи должны иметь разные hashcode(). И вы решаете выселить всего арендатора. Найти элементы арендатора в кеше непросто. Вы должны перебирать все кэшированные пары и отбрасывать пары, принадлежащие арендатору. Это неэффективно. Он скорее не атомарный, поэтому он сложный и нуждается в некоторой синхронизации. Синхронизация неэффективна.

Поэтому нет.

Но, если вы найдете решение сказать мне, потому что функция, которую вы хотите, действительно полезна.