Spring 3.1 @Cacheable - method still executed

31,636

Solution 1

From http://static.springsource.org/spring/docs/3.1.x/spring-framework-reference/html/cache.html

In proxy mode (which is the default), 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 caching at runtime even if the invoked method is marked with @Cacheable - considering using the aspectj mode in this case.

and

Method visibility and @Cacheable/@CachePut/@CacheEvict

When using proxies, you should apply the @Cache annotations only to methods with public visibility.

  1. You self-invoke someMethod in the same target object.
  2. Your @Cacheable method is not public.

Solution 2

You need to define a cache that matches the name you are referencing in you annotation ("testmethod"). Create an entry in your ehcache.xml for that cache as well.

Solution 3

In addition to Lee Chee Kiam: Here is my solution for small projects with only marginal usage of bypassing (not annotated) method calls. The DAO is simply injected into itself as a proxy and calls it's own methods using that proxy instead of a simple method call. So @Cacheable is considered without doing complicated insturmentation.

In-code documentation is strongly advidsed, as it may look strange 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 also advice the AspectJ solution as Lee Chee Kiam did.

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

    private final PersonDao _personDao;

    @Autowired
    public PersonDao(PersonDao personDao) {
        _personDao = personDao;
    }

    @Cacheable(value = "defaultCache", key = "#id")
    public Person findPerson(int id) {
        return getSession().getPerson(id);
    }

    public List<Person> findPersons(int[] ids) {
        List<Person> list = new ArrayList<Person>();
        for (int id : ids) {
            list.add(_personDao.findPerson(id));
        }
        return list;
    }
}
Share:
31,636
Andy Miller
Author by

Andy Miller

I'm a graduate of the University of Texas at Austin who currently lives in Austin. I mostly work on SPA for mobile and web.

Updated on July 29, 2020

Comments

  • Andy Miller
    Andy Miller almost 4 years

    I'm trying to implement Spring 3.1 caching as explained here and here, but it doesn't seem to be working: my method is run through every time even though it is marked @cacheable. What am I doing wrong?

    I've moved it into a junit test case with its own configuration file to isolate it from the rest of my application, but the problem still happens. Here are the relevant files:

    Spring-test-servlet.xml

    <beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:cache="http://www.springframework.org/schema/cache"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">
    <cache:annotation-driven />
    
    <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager" p:cache-manager-ref="ehcache"/>
    <bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"
          p:config-location="classpath:ehcache.xml"/>
    </beans>
    

    ehcache.xml

    <ehcache>
    <diskStore path="java.io.tmpdir"/>
    <cache name="cache"
           maxElementsInMemory="100"
           eternal="false"
           timeToIdleSeconds="120"
           timeToLiveSeconds="120"
           overflowToDisk="true"
           maxElementsOnDisk="10000000"
           diskPersistent="false"
           diskExpiryThreadIntervalSeconds="120"
           memoryStoreEvictionPolicy="LRU"/>
    
    </ehcache>
    

    MyTest.java

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration({"classpath:spring-test-servlet.xml"})
    @Component
    public class MyTest extends TestCase {
    
        @Test
        public void testCache1(){
            for(int i = 0; i < 5; i++){
                System.out.println("Calling someMethod...");
                System.out.println(someMethod(0));
            }
        }
    
        @Cacheable("testmethod")
        private int someMethod(int val){
            System.out.println("Not from cache");
            return 5;
        }
    }
    

    Relevant Pom entries: (spring-version = 3.1.1.RELEASE)

        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache-core</artifactId>
            <version>2.5.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
        </dependency>
    

    when I run the test, Spring puts out some debug messages that looks like my cache is initialized without errors

    DEBUG: config.ConfigurationHelper - No CacheManagerEventListenerFactory class specified. Skipping...
    DEBUG: ehcache.Cache - No BootstrapCacheLoaderFactory class specified. Skipping...
    DEBUG: ehcache.Cache - CacheWriter factory not configured. Skipping...
    DEBUG: config.ConfigurationHelper - No CacheExceptionHandlerFactory class specified. Skipping...
    DEBUG: store.MemoryStore - Initialized net.sf.ehcache.store.MemoryStore for cache
    DEBUG: disk.DiskStorageFactory - Failed to delete file cache.data
    DEBUG: disk.DiskStorageFactory - Failed to delete file cache.index
    DEBUG: disk.DiskStorageFactory - Matching data file missing (or empty) for index file. Deleting index file /var/folders/qg/xwdvsg6x3mx_z_rcfvq7lc0m0000gn/T/cache.index
    DEBUG: disk.DiskStorageFactory - Failed to delete file cache.index
    DEBUG: ehcache.Cache - Initialised cache: cache
    DEBUG: config.ConfigurationHelper - CacheDecoratorFactory not configured. Skipping for 'cache'.
    DEBUG: config.ConfigurationHelper - CacheDecoratorFactory not configured for defaultCache. Skipping for 'cache'.
    

    but the debug output shows no cache checks between method calls to someMethod and the print statement from inside someMethod prints every time.

    Is there something I'm missing?