Ключ @Cacheable для декількох аргументів методу


Відповіді:


93

Оновлення : Поточна реалізація кешу Spring використовує всі параметри методу як ключ кешу, якщо не вказано інше. Якщо ви хочете використовувати вибрані клавіші, зверніться до відповіді Arjan, де використовується список SpEL{#isbn, #includeUsed} який є найпростішим способом створення унікальних ключів.

З весняної документації

Стратегія генерації ключів за замовчуванням змінилася з виходом Spring 4.0. У попередніх версіях Spring використовувалася стратегія генерації ключів, яка для кількох ключових параметрів враховувала лише hashCode () параметрів, а не дорівнює (); це може спричинити несподівані зіткнення ключів (див. SPR-10237 для фону). Новий 'SimpleKeyGenerator' використовує складений ключ для таких сценаріїв.

До весни 4.0

Я пропоную вам поєднати значення параметрів у виразі Spel з чимось на зразок key="#checkWarehouse.toString() + #isbn.toString()"), я вважаю, це повинно працювати як org.springframework.cache.interceptor.ExpressionEvaluator повертає Object, який згодом використовується як ключ, тому вам не потрібно надавати anint в вашому вираженні SPEL.

Що стосується хеш-коду з високою ймовірністю зіткнення - ви не можете використовувати його як ключ.

Хтось у цій темі запропонував використовувати, T(java.util.Objects).hash(#p0,#p1, #p2)але він НЕ ПРАЦЮЄ, і цей підхід легко зламати, наприклад, я використовував дані з 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)));

Обидва рядки друкують -636517714 на моєму середовищі.

PS Насправді в довідковій документації, яку ми маємо

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

Я вважаю, що цей приклад НЕПРАВИЛЬНИЙ та оманливий, і його слід вилучити з документації, оскільки ключі мають бути унікальними.

PPS також див. Https://jira.springsource.org/browse/SPR-9036 для деяких цікавих ідей щодо генерації ключів за замовчуванням.

Я хотів би додати заради справедливості і в розважальній математичних / комп'ютерній науці тим , що в відміну від вбудованої в хеше, використовуючи захищену криптографічний хеш - функцію , як MD5 або SHA256, з - за властивості такої функції IS абсолютно можливо для цього завдання, але обчислювати його щоразу може бути надто дорого, перевіряйте, наприклад, курс криптографії Ден Боне, щоб дізнатися більше.


хм, я думаю, spr-9036 ігнорує ситуацію, коли параметром є масив, оскільки масиви за замовчуванням не мають рівних
— рівнів

4
Анонімні голоси проти надзвичайно корисні! Вибачте, але телепатія наразі недоступна.
— Борис Треухов

Чи відповідає ця відповідь про ключову тижневість для весняної версії 3.1.1? SPR-9377 було виправлено 3.1.1, ні? Хтось може додати ОНОВЛЕНИЙ розділ для цієї відповіді?
— Вишня

Також T(someType).hash(#isbn)працює навесні 3.1.1?
— Вишня

@Cherry Спробуйте скористатися відповіддю Ар'яна (у { #root.methodName, #param1, #param2 }будь-якому випадку 3.1.1 є старою версією.
— Борис Треухов,

80

Після деякого обмеженого тестування з Spring 3.2, здається , можна використовувати список SPEL: {..., ..., ...}. Це також може включати nullзначення. Spring передає список як ключ до фактичної реалізації кешу. При використанні Ehcache такий в якийсь момент викличе List # hashCode () , який враховує всі його елементи. (Я не впевнений, що Ehcache покладається лише на хеш-код.)

Я використовую це для спільного кешу, в який я також включаю ім'я методу в ключі, яке генератор ключів за замовчуванням Spring не включає . Таким чином я можу легко стерти (єдиний) кеш-пам'ять, не ризикуючи (занадто багато ...), клавіші відповідності для різних методів. Подібно до:

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

Звичайно, якщо це потрібно багатьом методам, і ви завжди використовуєте всі параметри для свого ключа, тоді можна також визначити власний генератор ключів, який включає назву класу та методу:

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

... з:

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

1
Як я можу отримати користувацький генератор ключів у конфігурації без xml?
— Василь

Дякуємо, що вказали на {..., ..., ...}. У поточній документації сказано, що генерація ключів за замовчуванням враховуватиме всі параметри. Тож не потрібно творити CacheKeyGenerator.
— linqu

Якщо це не працює для вас, можливо, ваш код не знає імен аргументів, тому використовуйте # a0 замість #asin, a1 замість #checkWarehouse тощо (також # p0, # p1). Це трапляється для мене у spring-data-jpa
— Cipous

2
Трохи пізно, @linqu, але генератор ключа за замовчуванням не включає ім'я методи. Якщо ви хочете використовувати один кеш для декількох методів, тоді потрібно включити ім'я методу, якщо два методи можуть мати однакові параметри.
— Arjan

5

Ви можете використовувати вираз Spring-EL, наприклад, на JDK 1.7:

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

А як щодо зіткнень? PS Так, це те, що вони кажуть робити у
— довідці

PPS насправді вони не кажуть робити у посиланні - єдиним прикладом хешування є @Cacheable(value="books", key="T(someType).hash(#isbn)") public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)- але це, здається, неправильний та оманливий приклад
— Борис Треухов

Насправді я знайшов контраприклад із Google (див. Мою відповідь)
— Борис Треухов,

@BorisTreukhov, суть полягала в тому, щоб показати, як аргументи можна використовувати для побудови ключа за допомогою Spring-EL, а не рішення, яке можна вважати надійним, я згоден, мабуть, найпростішим рішенням є просто об'єднання аргументів разом, що знову можна зробити, використовуючи Весна-ЕЛ.
— Biju Kunjummen

1
Я бачу, але проблема полягає не в тому, яке рішення простіше - проблема полягає в тому, що використання хеш-коду просто небезпечно - якщо один клієнт отримав пару ключів 109/434, а інший - 110/403, що може дозволити їм, наприклад, бачити повідомлення на форумі або операції з рахунками в Інтернет-банку. Я впевнений, що можливо набагато більше зіткнень - хороші хеш-функції важко реалізувати (розгляньте джерело реалізацій md5 - це просто не множиться на якесь магічне число), і все одно вони ніколи не зможуть повернути унікальні значення.
— Борис Треухов


0

Це спрацює

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

Я використав spring-data-jpa, і там повинен бути використаний a0 як аргумент на першій позиції, інакше як використовувати його ім'я ...
— Cipous

-2

Використовуй це

@Cacheable(value="bookCache", key="#isbn + '_' + #checkWarehouse + '_' + #includeUsed")

1
Ця комбінація завжди є унікальною, оскільки ви просто додаєте комбінації рядкових значень до ключа ..
— Весвант
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.