如何告诉Spring缓存不要在@Cacheable批注中缓存空值


69

有没有一种方法可以指定如果该方法返回null值,那么不要将结果缓存在@Cacheable注解中,例如这样的方法?

@Cacheable(value="defaultCache", key="#pk")
public Person findPerson(int pk) {
   return getSession.getPerson(pk);
}

更新:这是去年11月提交的有关缓存空值的JIRA问题,该问题尚未解决: [#SPR-8871] @Cachable条件应允许引用返回值-Spring Projects Issue Tracker


嗨,我认为您应该接受Tech Trip的答案,因为它与Spring的当前版本更相关。
鲍里斯·特鲁霍夫

Answers:


130

Hooray,从Spring 3.2开始,该框架允许使用Spring SPEL和unless。来自Cacheable的Java文档中的注释:

http://static.springsource.org/spring/docs/3.2.x/javadoc-api/org/springframework/cache/annotation/Cacheable.html

公共抽象字符串,除非

Spring Expression Language(SpEL)属性用于否决方法缓存。

与condition()不同,此表达式是在调用方法后求值的,因此可以引用结果。默认值为“”,表示永远不会否决缓存。

重要的方面是unless在调用方法之后进行评估。这非常有道理,因为如果密钥已经在缓存中,则该方法将永远不会执行。

因此,在上面的示例中,您只需进行如下注释(#result可用于测试方法的返回值):

@Cacheable(value="defaultCache", key="#pk", unless="#result == null")
public Person findPerson(int pk) {
   return getSession.getPerson(pk);
}

我可以想象这种情况是由于使用可插拔缓存实现(例如Ehcache)而实现的,该实现允许缓存null。根据您的用例场景,可能不希望这样做。


1
嗨,我有一个场景,其中Redis缓存服务器缓存异常。我必须避免将异常缓存到服务器。我可以为此使用“除非”参数吗?除非有可能提到例外?请分享您的建议。在此先感谢
nijogeorgep

1
JSR107提供了一种机制,该机制始终调用带注释的方法,并且仍然通过cacheResult#skipGet缓存结果。根据skipGet上的API,如果为true,则也将跳过对引发的异常的预调用检查。但是,如果在调用过程中引发了异常,则将按照标准异常缓存规则对其进行缓存,因此可能无法满足您的需求。但是,使用JSR 107可以显式排除config上的某些异常,也许这将使您有余地。(请参阅nonCachedExceptions)Spring的抽象提供了JSR107批注支持。
TechTrip

1
仅供其他人参考-还有一个condition论点,也可以使用。就我而言,我需要condition="#pk != null"这样,以便当参数为null缓存时不会抛出hv000028: unexpected exception during isvalid call:)
tftd18年

我复制粘贴您的代码,并得到了一个意外错误(类型=内部服务器错误,状态= 500)。返回用于缓存操作的空键(也许您在没有调试信息的类上使用了命名的参数?)Builder [public java.util.Map
Kamil Nekanowicz,

在@tftd上方建议使用condition =“#pk!= null”。如果您需要使用“同步”,因为“除非”在使用“同步”时不起作用,这很有用。
markthegrea19年

6

如果是Spring注解

@Cacheable(value="defaultCache", key="#pk",unless="#result!=null")

不起作用,您可以尝试:

@CachePut(value="defaultCache", key="#pk",unless="#result==null")

这个对我有用。


10
你的意思是unless="#result == null"
cahen

11
缓存,除非结果!= null_means _Cache如果结果== null。所以,正确的表达unless="#result==null",这意味着高速缓存,除非结果== NULL这意味着如果结果缓存!= NULL
mmdemirbas

5

更新此答案已过时,对于Spring 3.2及更高版本,请参阅Tech Trip的答案,OP:随时将其标记为已接受。

我认为这是不可能的(即使在Spring中有条件的Cache驱逐可以在方法调用之前将@CacheEvict参数beforeInvocation设置为false,这是默认值也可以执行),检查CacheAspectSupport类表明返回的值未存储在之前的inspectAfterCacheEvicts(ops.get(EVICT));通话。

protected Object execute(Invoker invoker, Object target, Method method, Object[] args) {
    // check whether aspect is enabled
    // to cope with cases where the AJ is pulled in automatically
    if (!this.initialized) {
        return invoker.invoke();
    }

    // get backing class
    Class<?> targetClass = AopProxyUtils.ultimateTargetClass(target);
    if (targetClass == null && target != null) {
        targetClass = target.getClass();
    }
    final Collection<CacheOperation> cacheOp = getCacheOperationSource().getCacheOperations(method, targetClass);

    // analyze caching information
    if (!CollectionUtils.isEmpty(cacheOp)) {
        Map<String, Collection<CacheOperationContext>> ops = createOperationContext(cacheOp, method, args, target, targetClass);

        // start with evictions
        inspectBeforeCacheEvicts(ops.get(EVICT));

        // follow up with cacheable
        CacheStatus status = inspectCacheables(ops.get(CACHEABLE));

        Object retVal = null;
        Map<CacheOperationContext, Object> updates = inspectCacheUpdates(ops.get(UPDATE));

        if (status != null) {
            if (status.updateRequired) {
                updates.putAll(status.cUpdates);
            }
            // return cached object
            else {
                return status.retVal;
            }
        }

        retVal = invoker.invoke();

        inspectAfterCacheEvicts(ops.get(EVICT));

        if (!updates.isEmpty()) {
            update(updates, retVal);
        }

        return retVal;
    }

    return invoker.invoke();
}
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.