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

124,442

Solution 1

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.

Solution 2

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

Solution 3

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

Solution 4

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

Solution 5

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

Share:
124,442

Related videos on Youtube

Bala
Author by

Bala

Happy to help.

Updated on January 22, 2022

Comments

  • Bala
    Bala over 2 years

    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 ?

  • Shawn D.
    Shawn D. almost 11 years
    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.
  • Bala
    Bala almost 11 years
    What is "static weaving"? google doesn't help much. Any pointers to understand this concepts ?
  • Dewfy
    Dewfy almost 11 years
    @Bala - just for example on our project we use <iajc compiler (from ant) that resolves all necessity aspects for cache-able classes.
  • Coder
    Coder over 8 years
    could you give an example with AspectJ ?
  • VAdaihiep
    VAdaihiep over 7 years
    @Bala I have same issue, my solution is move @Cacheable to DAO :( If you have better solution please let me know, thanks.
  • DOUBL3P
    DOUBL3P over 6 years
    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.
  • SingleShot
    SingleShot over 6 years
    Accessing the application context, e.g. applicationContext.getBean(SettingService.class);, is the opposite of dependency injection. I suggest avoiding that style.
  • molholm
    molholm over 6 years
    Yes it would be better to avoid it, but I do not see a better solution for this problem.
  • radistao
    radistao about 6 years
    Since Spring 4.3 this could be solved using @Resource self-autowiring, see example stackoverflow.com/a/48867068/907576
  • Madbreaks
    Madbreaks over 5 years
    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.
  • Tomas Bisciak
    Tomas Bisciak almost 5 years
    Worked for me. Cache hits. I use latest spring dependencies as of this date.
  • 2mia
    2mia almost 5 years
    am I the only one thinking this breaks patterns, looks like a singleton mix, etc etc?
  • Deepan Prabhu Babu
    Deepan Prabhu Babu over 4 years
    i used spring boot starter version - 2.1.0.RELEASE, and i had the same issue. This particular solution worked like a charm.
  • anand
    anand almost 4 years
    Also the external @Cacheable method should be public, it doesn't work on package-private methods. Found it the hard way.
  • jaco0646
    jaco0646 almost 4 years
    This answer is a duplicate of stackoverflow.com/a/34090850/1371329.
  • Chandresh Mishra
    Chandresh Mishra over 3 years
    Will, it does not create a cyclic dependency?
  • amstegraf
    amstegraf about 3 years
    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 anymore
  • Dexter Legaspi
    Dexter Legaspi about 3 years
    this is the way.
  • jnnnnn
    jnnnnn almost 2 years
    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."