Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Boot EnableCaching and Cacheable annotation not working

I want to cache master data to Redis.

So, I had written these codes.

@Configuration
@EnableCaching
public class AppConfig extends CachingConfigurerSupport {
    @Bean
    @Autowired
    public CacheManager cacheManager(RedisTemplate<Object, Object> redisTemplate) {
        RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
        Map<String, Long> expires = new HashMap<>();
        expires.put("cache.day", new Long(24 * 60 * 60));
        cacheManager.setExpires(expires);
        return cacheManager;
    }
}

And

package com.taisho.artifacts.repository.impl;

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Repository;

import java.util.ArrayList;
import java.util.List;

@Repository
public class TestRepository {

    @Cacheable(value = "cache.day", key = "'cache.test'")
    public List<String> getTest() {
        List<String> list = new ArrayList<>();
        list.add("test");
        list.add("sample");
        return list;
    }

    public void printTest() {
        System.out.println(getTest());
    }
}

And ymlfile

spring:
  redis:
    host: 127.0.0.1
    port: 26379

But, Cache not working...

Whenever I call printTest method, "getTest" method will execute. Redis has no data... What is a problem in my code?

Note

SpringBoot version is 1.4.0

dependencies are

compile("org.springframework.boot:spring-boot-starter-web:${springBootVersion}")
compile("org.springframework.boot:spring-boot-starter-data-redis:${springBootVersion}")
compile("org.springframework.boot:spring-boot-autoconfigure:${springBootVersion}")
like image 858
Hiroki Sakamoto Avatar asked Jan 08 '17 15:01

Hiroki Sakamoto


People also ask

How does @cachable work?

As the name implies, @Cacheable is used to demarcate methods that are cacheable - that is, methods for whom the result is stored into the cache so on subsequent invocations (with the same arguments), the value in the cache is returned without having to actually execute the method.


1 Answers

TL; DR

Spring AOP is proxy-based, so when you calling the getTest() method from the printTest() method, the getTest() method would be called on this reference not the proxied version which is capable of performing caching operations. Usually this is a Design Smell and you are better off reconsidering your current design. But as a workaround, you can use the AopContext:

public void printTest() {
    System.out.println(((TestRepository) AopContext.currentProxy()).getTest());
}

Detailed Answer

Suppose you have a client code that has access to the TestRepository through Dependency Injection:

@Component
class SomeUnfortunateClient {
    // I know field injection is evil!
    @Autowired TestRepository testRepository;

    void youAreGoingToBeSurprised() {
        testRepository.printTest();
    }
}

The TestRepository is a Spring managed repository and in order to add the additional functionalities to the TestRepository, e.g. Caching, Spring will create a Proxy for it. This means that method calls on the testRepository object reference will be calls on the proxy, and as such the proxy will be able to delegate to all of the interceptors (advice) that are relevant to that particular method call. In your case, those advises would check if the cache entry exists or not.

However, once the call has finally reached the target object, the TestRepository reference in this case, any method calls that it may make on itself, such as System.out.println(getTest());, are going to be invoked against the this reference, and not the proxy. It means that self-invocation is not going to result in the advice associated with a method invocation getting a chance to execute.

As the Spring Documentation states:

Okay, so what is to be done about this? The best approach (the term best is used loosely here) is to refactor your code such that the self-invocation does not happen. For sure, this does entail some work on your part, but it is the best, least-invasive approach. The next approach is absolutely horrendous, and I am almost reticent to point it out precisely because it is so horrendous. You can (choke!) totally tie the logic within your class to Spring AOP by doing this:

public class SimplePojo implements Pojo {

    public void foo() {
        // this works, but... gah!
        ((Pojo) AopContext.currentProxy()).bar();
    }

    public void bar() {
        // some logic...
    }
}

This totally couples your code to Spring AOP, and it makes the class itself aware of the fact that it is being used in an AOP context, which flies in the face of AOP. It also requires some additional configuration when the proxy is being created.

Further Reading

This answer is heavily based on Spring Documentation, so for (even!) more detailed discussion, you should definitely check out the Understanding AOP proxies section in Spring Documentation.

like image 157
Ali Dehghani Avatar answered Sep 20 '22 00:09

Ali Dehghani