Spring Cache @Cacheable - not working while calling from another method of the same bean
Asked Answered
T

15

189

Spring cache is not working when calling cached method from another method of the same bean.

Here is an example to explain my problem in clear way.

Configuration:

<cache:annotation-driven cache-manager="myCacheManager" />

<bean id="myCacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
    <property name="cacheManager" ref="myCache" />
</bean>

<!-- Ehcache library setup -->
<bean id="myCache"
    class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:shared="true">
    <property name="configLocation" value="classpath:ehcache.xml"></property>
</bean>

<cache name="employeeData" maxElementsInMemory="100"/>  

Cached service :

@Named("aService")
public class AService {

    @Cacheable("employeeData")
    public List<EmployeeData> getEmployeeData(Date date){
    ..println("Cache is not being used");
    ...
    }

    public List<EmployeeEnrichedData> getEmployeeEnrichedData(Date date){
        List<EmployeeData> employeeData = getEmployeeData(date);
        ...
    }

}

Result :

aService.getEmployeeData(someDate);
output: Cache is not being used
aService.getEmployeeData(someDate); 
output: 
aService.getEmployeeEnrichedData(someDate); 
output: Cache is not being used

The getEmployeeData method call uses cache employeeData in the second call as expected. But when the getEmployeeData method is called within the AService class (in getEmployeeEnrichedData), Cache is not being used.

Is this how spring cache works or am i missing something ?

Terchie answered 3/6, 2013 at 14:51 Comment(3)
are you use same value for someDate param?Firecrest
@Firecrest Yes, it is sameTerchie
Possible duplicate of Spring cache @Cacheable method ignored when called from within the same classHexagram
M
260

I believe this is how it works. From what I remember reading, there is a proxy class generated that intercepts all requests and responds with the cached value, but 'internal' calls within the same class will not get the cached value.

From https://code.google.com/p/ehcache-spring-annotations/wiki/UsingCacheable

Only external method calls coming in through the proxy are intercepted. This means that self-invocation, in effect, a method within the target object calling another method of the target object, will not lead to an actual cache interception at runtime even if the invoked method is marked with @Cacheable.

Manlike answered 3/6, 2013 at 14:58 Comment(6)
Well, if you make the second call Cacheable as well, it'll only have one cache miss. That is, only the first call to getEmployeeEnrichedData will bypass the cache. The second call to it would used the previously-cached return from the first call to getEmployeeEnrichedData.Manlike
@Terchie I have same issue, my solution is move @Cacheable to DAO :( If you have better solution please let me know, thanks.Romanov
you also can write a Service e.g. CacheService and put all your to cache methods into the service. Autowire the Service where you need and call the methods. Helped in my case.Diminuendo
Since Spring 4.3 this could be solved using @Resource self-autowiring, see example https://mcmap.net/q/134922/-spring-cache-cacheable-not-working-while-calling-from-another-method-of-the-same-beanWrongful
Also the external @Cacheable method should be public, it doesn't work on package-private methods. Found it the hard way.Grimy
most if not all of the annotations in spring are done through aop which is by proxy class, thus this calls don't work, because of course calling a method within means you don't go through the proxy anymoreFaefaeces
W
59

Since Spring 4.3 the problem could be solved using self-autowiring over @Resource annotation:

@Component
@CacheConfig(cacheNames = "SphereClientFactoryCache")
public class CacheableSphereClientFactoryImpl implements SphereClientFactory {

    /**
     * 1. Self-autowired reference to proxified bean of this class.
     */
    @Resource
    private SphereClientFactory self;

    @Override
    @Cacheable(sync = true)
    public SphereClient createSphereClient(@Nonnull TenantConfig tenantConfig) {
        // 2. call cached method using self-bean
        return self.createSphereClient(tenantConfig.getSphereClientConfig());
    }

    @Override
    @Cacheable(sync = true)
    public SphereClient createSphereClient(@Nonnull SphereClientConfig clientConfig) {
        return CtpClientConfigurationUtils.createSphereClient(clientConfig);
    }
}
Wrongful answered 19/2, 2018 at 13:16 Comment(7)
Tried this under 4.3.17 and it didn't work, calls to self don't go through a proxy and the cache is (still) bypassed.Trinatrinal
Worked for me. Cache hits. I use latest spring dependencies as of this date.Blubbery
am I the only one thinking this breaks patterns, looks like a singleton mix, etc etc?Trowel
i used spring boot starter version - 2.1.0.RELEASE, and i had the same issue. This particular solution worked like a charm.Sitton
Will, it does not create a cyclic dependency?Rhomb
Spring 5 crashes: "Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans."Messeigneurs
It would fit much better if you move cached method to another helper / wrapper component bean.Demoss
T
23

The example below is what I use to hit the proxy from within the same bean, it is similar to @mario-eis' solution, but I find it a bit more readable (maybe it's not:-). Anyway, I like to keep the @Cacheable annotations at the service level:

@Service
@Transactional(readOnly=true)
public class SettingServiceImpl implements SettingService {

@Inject
private SettingRepository settingRepository;

@Inject
private ApplicationContext applicationContext;

@Override
@Cacheable("settingsCache")
public String findValue(String name) {
    Setting setting = settingRepository.findOne(name);
    if(setting == null){
        return null;
    }
    return setting.getValue();
}

@Override
public Boolean findBoolean(String name) {
    String value = getSpringProxy().findValue(name);
    if (value == null) {
        return null;
    }
    return Boolean.valueOf(value);
}

/**
 * Use proxy to hit cache 
 */
private SettingService getSpringProxy() {
    return applicationContext.getBean(SettingService.class);
}
...

See also Starting new transaction in Spring bean

Tillford answered 31/10, 2016 at 11:8 Comment(2)
Accessing the application context, e.g. applicationContext.getBean(SettingService.class);, is the opposite of dependency injection. I suggest avoiding that style.Aboveboard
Yes it would be better to avoid it, but I do not see a better solution for this problem.Tillford
M
14

If you call a cached method from same bean it will be treated as a private method and annotations will be ignored

Manzanares answered 22/9, 2020 at 6:38 Comment(0)
W
13

Here is what I do for small projects with only marginal usage of method calls within the same class. In-code documentation is strongly advidsed, as it may look strage to colleagues. But its easy to test, simple, quick to achieve and spares me the full blown AspectJ instrumentation. However, for more heavy usage I'd advice the AspectJ solution.

@Service
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
class AService {

    private final AService _aService;

    @Autowired
    public AService(AService aService) {
        _aService = aService;
    }

    @Cacheable("employeeData")
    public List<EmployeeData> getEmployeeData(Date date){
        ..println("Cache is not being used");
        ...
    }

    public List<EmployeeEnrichedData> getEmployeeEnrichedData(Date date){
        List<EmployeeData> employeeData = _aService.getEmployeeData(date);
        ...
    }
}
Webby answered 4/12, 2015 at 15:3 Comment(2)
could you give an example with AspectJ ?Hostile
This answer is a duplicate of https://mcmap.net/q/137018/-spring-cache-cacheable-method-ignored-when-called-from-within-the-same-class.Aquamarine
L
9

Yes, the caching will not happen because of the reasons that were already mentioned in the other posts. However I would solve the problem by putting that method to its own class (service in this case). With that your code will be easier to maintain/test and understand.

@Service // or @Named("aService")
public class AService {

    @Autowired //or how you inject your dependencies
    private EmployeeService employeeService;
 
    public List<EmployeeData> getEmployeeData(Date date){
          employeeService.getEmployeeData(date);
    }

    public List<EmployeeEnrichedData> getEmployeeEnrichedData(Date date){
        List<EmployeeData> employeeData = getEmployeeData(date);
        ...
    }

}
@Service // or @Named("employeeService")
public class EmployeeService {

    @Cacheable("employeeData")
    public List<EmployeeData> getEmployeeData(Date date){
        println("This will be called only once for same date");
        ...
    }

}
Laughry answered 22/3, 2021 at 15:19 Comment(2)
this is the way.Tomcat
Interesting approach. To expand on what "easier to maintain" might mean: this way all Cache related methods would be placed in the same class what makes a developer to make a choice where to put a new method? In a class that caches (and evicts) or in other one? As time passes by, it is easy to miss the point that when you add a new method, you might also need to evict the related data from the cache.Sherman
M
4

In my Case I add variable :

@Autowired
private AService  aService;

So I call the getEmployeeData method by using the aService

@Named("aService")
public class AService {

@Cacheable("employeeData")
public List<EmployeeData> getEmployeeData(Date date){
..println("Cache is not being used");
...
}

public List<EmployeeEnrichedData> getEmployeeEnrichedData(Date date){
    List<EmployeeData> employeeData = aService.getEmployeeData(date);
    ...
}

}

It will use the cache in this case.

Miniskirt answered 29/10, 2018 at 15:41 Comment(0)
I
3

Better approach should be creating another service like ACachingService and call ACachingService.cachingMethod() instead of self Autowiring ( or any other approach trying to self inject). This way you do not fall into Circular dependency, which may be resulted in warning/error when upgrade to newer Spring ( Spring 2.6.6 in my case ) :

ERROR o.s.boot.SpringApplication - Application run failed
org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name 'webSecurityConfig': 
Requested bean is currently in creation: Is there an unresolvable circular reference?
Irk answered 25/5, 2022 at 8:41 Comment(0)
M
3

We looked at all the solutions here and decided to use a separate class for the cached methods because Spring 5 doesn't like circular dependencies.

Messeigneurs answered 1/6, 2022 at 0:27 Comment(0)
F
1

Use static weaving to create proxy around your bean. In this case even 'internal' methods would work correctly

Firecrest answered 3/6, 2013 at 15:15 Comment(2)
What is "static weaving"? google doesn't help much. Any pointers to understand this concepts ?Terchie
@Terchie - just for example on our project we use <iajc compiler (from ant) that resolves all necessity aspects for cache-able classes.Firecrest
W
1

I use internal inner bean (FactoryInternalCache) with real cache for this purpose:

@Component
public class CacheableClientFactoryImpl implements ClientFactory {

private final FactoryInternalCache factoryInternalCache;

@Autowired
public CacheableClientFactoryImpl(@Nonnull FactoryInternalCache factoryInternalCache) {
    this.factoryInternalCache = factoryInternalCache;
}

/**
 * Returns cached client instance from cache.
 */
@Override
public Client createClient(@Nonnull AggregatedConfig aggregateConfig) {
    return factoryInternalCache.createClient(aggregateConfig.getClientConfig());
}

/**
 * Returns cached client instance from cache.
 */
@Override
public Client createClient(@Nonnull ClientConfig clientConfig) {
    return factoryInternalCache.createClient(clientConfig);
}

/**
 * Spring caching feature works over AOP proxies, thus internal calls to cached methods don't work. That's why
 * this internal bean is created: it "proxifies" overloaded {@code #createClient(...)} methods
 * to real AOP proxified cacheable bean method {@link #createClient}.
 *
 * @see <a href="https://mcmap.net/q/134922/-spring-cache-cacheable-not-working-while-calling-from-another-method-of-the-same-bean">Spring Cache @Cacheable - not working while calling from another method of the same bean</a>
 * @see <a href="https://mcmap.net/q/137018/-spring-cache-cacheable-method-ignored-when-called-from-within-the-same-class">Spring cache @Cacheable method ignored when called from within the same class</a>
 */
@EnableCaching
@CacheConfig(cacheNames = "ClientFactoryCache")
static class FactoryInternalCache {

    @Cacheable(sync = true)
    public Client createClient(@Nonnull ClientConfig clientConfig) {
        return ClientCreationUtils.createClient(clientConfig);
    }
}
}
Wrongful answered 9/1, 2018 at 13:0 Comment(0)
P
0

I would like to share what I think is the easiest approach:

  • Autowire the controller and use to call the method it instead of using the class context this.

The updated code would look like:

@Controller
public class TestController {


    @Autowired TestController self;

    @RequestMapping("/test")
    public String testView(){
        self.expensiveMethod();
        return "test";
    }


    @Cacheable("ones")
    public void expensiveMethod(){
       System.out.println("Cache is not being used");
    }

}
Pless answered 21/1, 2022 at 11:3 Comment(0)
B
0

The default advice mode for processing caching annotation is “proxy”. At the startup of an application, all the caching annotations like @Caching, @Cacheable, @CacheEvict etc. are scanned and a target proxy class is generated for all of these classes. The proxy allows for intercepting the calls to these cacheable methods, which adds the caching advice/behavior.

So when we invoke the cacheable methods from the same class, as shown below, calls from the clients don’t get intercepted in a way that allows for caching advice to be added to them. Hence, every single time there is an unexpected cache miss.

Solution: Invoke the Cacheable methods from a different bean to use proxy class with caching advice.

Bert answered 2/1, 2023 at 12:44 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Bavaria
U
0

Implementation based on Single Responsibility Principle; Cacheable classes support just cache not using cache ;-).

Untrimmed answered 24/3, 2023 at 12:12 Comment(0)
D
0

This is how it works. Move your cached method to helper / wrapper component bean.

Demoss answered 30/3, 2023 at 16:7 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.