如何告诉 Spring 缓存不要在@Cacheable 注释中缓存 null 值

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

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

更新: 以下是去年11月提交的关于缓存空值的 JIRA 问题,尚未解决: [ # SPR-8871]@Cached 条件应该允许引用返回值-Spring 项目问题跟踪器

101496 次浏览

update this answer is outdated now, for Spring 3.2 and later see Tech Trip's answer, OP: feel free to mark it as accepted.

I don't think that it's possible(even though there's conditional Cache eviction in Spring that can be executed after the method invocation with @CacheEvict parameter beforeInvocation set to false, which is default value) examining the CacheAspectSupport class shows that the returned value is not stored anywhere before the inspectAfterCacheEvicts(ops.get(EVICT)); call.

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

Hooray, as of Spring 3.2 the framework allows for this using Spring SPEL and unless. Note from the java doc surrounding Cacheable:

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

public abstract String unless

Spring Expression Language (SpEL) attribute used to veto method caching.

Unlike condition(), this expression is evaluated after the method has been called and can therefore refer to the result. Default is "", meaning that caching is never vetoed.

The important aspect is that unless is evaluated after the method has been called. This makes perfect sense because the method will never get executed if the key is already in the cache.

So in the above example you would simply annotate as follows (#result is available to test the return value of a method):

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

I would imagine this condition arises from the use of pluggable cache implementations such as Ehcache which allows caching of nulls. Depending on your use case scenario this may or may not be desirable.

If Spring annotation

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

does not work ,you can try:

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

It works for me.