多个方法参数上的@Cacheable键


77

spring文档中

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

如何指定@Cachable使用isbncheckWarehouse作为密钥?

Answers:


93

更新:如果没有另外指定,当前的Spring缓存实现将所有方法参数用作缓存键。如果要使用选定的键,请参考Arjan的答案,该答案使用SpEL列表{#isbn, #includeUsed} ,这是创建唯一键的最简单方法。

Spring文档

随着Spring 4.0的发布,默认的密钥生成策略发生了变化。Spring的早期版本使用密钥生成策略,该策略对于多个密钥参数仅考虑参数的hashCode(),而不考虑equals()。这可能会导致意外的键冲突(有关背景,请参阅SPR-10237)。对于这种情况,新的“ SimpleKeyGenerator”使用复合键。

在Spring 4.0之前

我建议您将Spel表达式中的参数值与类似的东西连接key="#checkWarehouse.toString() + #isbn.toString()"),我相信这应该可以作为org.springframework.cache.interceptor.ExpressionEvaluator返回Object,之后将其用作键,因此您不必提供一个int在你的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这绝对有可能的任务,但是每次都进行计算可能会太昂贵,例如,请查看Dan Boneh密码学课程以了解更多信息。


嗯,我认为spr-9036会忽略参数是数组的情况,因为默认情况下数组不会做深度等于
jasonk

4
匿名下注非常有用!抱歉,目前尚无心灵感应。
鲍里斯·特鲁霍霍夫

这是否与春季版的实际关键周有关3.1.1SPR-9377是固定的3.1.1,不是吗?有人可以为此问题添加UPDATED部分吗?
樱桃,

也不会T(someType).hash(#isbn)在春季工作3.1.1
2016年

@Cherry尝试使用阿尔扬的回答({ #root.methodName, #param1, #param2 },反正3.1.1是旧版本了。
鲍里斯Treukhov

80

与Spring 3.2的一些有限的测试后,似乎可以使用一个规划环境地政司名单:{..., ..., ...}。这也可以包括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配置中获取自定义KeyGenerator?
罗勒

感谢您指出{..., ..., ...}。在当前文档中,它说默认密钥生成将考虑所有参数。因此无需创建CacheKeyGenerator
linqu

如果这对您不起作用,则可能您的代码不知道参数名称,因此请使用#a0代替#asin,使用a1代替#checkWarehouse等(还包括#p0,#p1)。这发生在spring-data-jpa
Cipous,2016年

2
一个有点晚了,@linqu,但默认密钥生成不包括方法名。如果要对多个方法使用一个缓存,则如果两个方法可以具有相同的参数,则需要包括方法名称。
Arjan

5

您可以使用Spring-EL表达式,例如在JDK 1.7上:

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

那碰撞呢?PS:是的,这是他们在参考文献中告诉我们要做的事情,但是整个想法非常奇怪
Boris Treukhov 2012年

PPS其实他们知道在参考的事-用散列唯一的例子是@Cacheable(value="books", key="T(someType).hash(#isbn)") public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)-但它似乎是一个错误的和误导性的例子
鲍里斯Treukhov

实际上,我在Google上找到了一个反例(请参阅我的回答)
Boris Treukhov

@BorisTreukhov,关键是要说明如何使用Spring-EL来使用参数来构建键,而不是可以认为是健壮的解决方案,我同意,最简单的解决方案是将参数简单地连接在一起,再次可以使用春季EL。
Biju Kunjummen 2012年

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
该组合始终是唯一的,因为您只是将字符串值组合添加到键中
。。– Veswanth
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.