13

I'm having a problem with @org.springframework.cache.annotation.Cachable annotation:

@Bean
public ConcurrentMapCache cache() {
    return new ConcurrentMapCache(CACHE);
}

@Cacheable(CACHE)
public String getApi() {
    return "api";
}

@Cacheable(CACHE)
public String getUrl() {
    return "url";
}

usage:

assertEquals("api", service.getApi()); //OK
assertEquals("url", service.getUrl()); //FAILURE. this returns also "api"

So, why does @Cachable not create a cache result by method name if method signature does not contain any input parameters?

membersound
  • 81,582
  • 193
  • 585
  • 1,120

4 Answers4

17

Try this

@Cacheable(value = CACHE, key = "#root.method.name")

Or even

@Cacheable(value = CACHE, key = "#root.methodName")

You need to tell spring to use method name as key. You can use SPEL for this. Here is the doc with various other options.

Rany Albeg Wein
  • 3,304
  • 3
  • 16
  • 26
pvpkiran
  • 25,582
  • 8
  • 87
  • 134
  • 3
    While that works - isn't it counter intuitive? I mean, why did the spring guys ever thought adding @Cachable on a method without parameters should work that way? – membersound Feb 20 '18 at 19:00
  • @membersound it's counter-intuitive indeed, even their example doesn't take that into account. I lost some time today until I read the doc, finding that special line: `This approach works only for methods that are guaranteed to return the same output (result) for a given input (or arguments) no matter how many times it is executed. ` – Dardan Iljazi Aug 28 '20 at 14:40
  • Good idea, but two methods have two different names, so the keys are different as well. – vahid kh May 14 '23 at 14:26
2

While the accepted answer describes how to solve the problem, it fails to answer the actual question which was why Spring behaves like this. So here's the answer for anyone interested.

From the documentation of org.springframework.cache.annotation.EnableCaching:

Normally, @EnableCaching will configure Spring's SimpleKeyGenerator

And SimpleKeyGenerator is defined like so:

public class SimpleKeyGenerator implements KeyGenerator {

    @Override
    public Object generate(Object target, Method method, Object... params) {
        return generateKey(params);
    }

    /**
     * Generate a key based on the specified parameters.
     */
    public static Object generateKey(Object... params) {
        if (params.length == 0) {
            return SimpleKey.EMPTY;
        }
        if (params.length == 1) {
            Object param = params[0];
            if (param != null && !param.getClass().isArray()) {
                return param;
            }
        }
        return new SimpleKey(params);
    }
}

As you can see, it only looks at the parameters to generate the cache key and completely ignores the method. Which I agree, is total nuts.

But Stéphane Nicoll said:

SimpleKeyGenerator does this on purpose. We had many requests to add more context to the key such as the method signature for instance. We feel this is wrong as the key should be natural and usable outside of how we interact with the cache.

Michel Jung
  • 2,966
  • 6
  • 31
  • 51
1

You can use two cache with different name:

@Bean
public ConcurrentMapCache cache() {
    return new ConcurrentMapCache(CACHE_API, CACHE_URL);
}
@Cacheable(CACHE_API)
public String getApi() {
    return "api";
}

@Cacheable(CACHE_URL)
public String getUrl() {
    return "url";
}
akasha
  • 494
  • 6
  • 4
0

You can refer to the same key by SpEL:

@Bean
public ConcurrentMapCache cache() {
    return new ConcurrentMapCache(CACHE);
}

@Cacheable(CACHE, key="'theKey'")
public String getApi() {
    return "api";
}

@Cacheable(CACHE, key="'theKey'")
public String getUrl() {
    return "url";
}
vahid kh
  • 1,874
  • 2
  • 12
  • 16