Ключ @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.