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

4

97

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

Ela answered 24/8, 2012 at 17:2 Comment(1)
Hi, I think you should Tech Trip's answer as accepted, because it is more relevant for the current version of Spring.Rauscher
L
183

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

Spring Expression Language (SpEL) expression used to veto method caching. Veto caching the result if the condition evaluates to true.

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.

Lourielouse answered 2/4, 2013 at 22:12 Comment(6)
Hi, I have a scenario,in which redis caching server caches exceptions. I have to avoid caching of exceptions to server. Can i use "unless" parameter for this? Is it possible to mention exceptions in unless? Please share your suggestions. Thanks in advancePelagi
JSR107 provides a mechanism to always invoke the annotated method and still cache the result via cacheResult#skipGet. According to the API on skipGet, If true the pre-invocation check for a thrown exception is also skipped. However, if an exception is thrown during invocation it will be cached following the standard exception caching rules so that may not suffice for your needs. Yet, using JSR 107 you can explicitly exclude certain exceptions on config and perhaps that will allow you the leeway to proceed. (see nonCachedExceptions) Spring’s abstraction provides JSR107 annotations support.Lourielouse
Just an FYI for anybody else - there is also a condition argument, which can be used as well. In my case I needed condition="#pk != null" so that when the argument is null the caching would not throw hv000028: unexpected exception during isvalid call :)Kano
I copy paste your code and got There was an unexpected error (type=Internal Server Error, status=500). Null key returned for cache operation (maybe you are using named params on classes without debug info?) Builder[public java.util.MapMargretmargreta
Above @Kano suggested using condition="#pk != null". This is useful if you need to use "sync" as "unless" does not work when using "sync".Analeptic
How about if my method return Optional<T> value? Will this work? unless="#result .equals(Optional.empty())"Titillate
R
7

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();
}
Rauscher answered 24/8, 2012 at 17:52 Comment(0)
D
5

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.

Dahlia answered 2/3, 2017 at 2:47 Comment(2)
did you mean unless="#result == null" ?Manheim
Cache unless result != null_means _Cache if result == null. So, the correct expression is unless="#result==null" which means Cache unless result == null which means Cache if result != null.Superpower
H
0

If you're using Redis simply use disableCachingNullValues() in your configuration.

@Bean
public RedisCacheConfiguration cacheConfiguration() {
   return RedisCacheConfiguration
     .defaultCacheConfig()
     .entryTtl(Duration.ofMinutes(60))
     .disableCachingNullValues()
     .serializeValuesWith(SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
}
Herniorrhaphy answered 16/4 at 10:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.