Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I tell Spring cache not to cache null value in @Cacheable annotation

Is there a way to specify that if the method returns null value, then don't cache the result in @Cacheable annotation for a method like this?

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

Update: here is the JIRA issue submitted regarding caching null value last November, which hasn't resolved yet: [#SPR-8871] @Cachable condition should allow referencing return value - Spring Projects Issue Tracker

like image 691
David Zhao Avatar asked Aug 24 '12 17:08

David Zhao


People also ask

Which annotation is used for caching in spring boot application?

To enable the Spring Boot caching feature, you need to add the @EnableCaching annotation to any of your classes annotated with @Configuration or to the boot application class annotated with @SpringBootApplication .

How do you achieve caching in your Spring application?

4.1. The simplest way to enable caching behavior for a method is to demarcate it with @Cacheable, and parameterize it with the name of the cache where the results would be stored: @Cacheable("addresses") public String getAddress(Customer customer) {...}

What is the default cache in spring boot?

Spring provides one concurrent hashmap as default cache, but we can override CacheManager to register external cache providers as well easily.


2 Answers

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.

like image 176
TechTrip Avatar answered Sep 24 '22 15:09

TechTrip


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(); } 
like image 27
Boris Treukhov Avatar answered Sep 22 '22 15:09

Boris Treukhov