Java – MongoDB Spring Data Repository – Cache methods

javamongodbspringspring-dataspring-data-mongodb

I need to have calls to MongoDB cached using spring @Cacheable annotation:

public interface SiteRepository extends PagingAndSortingRepository<Site, String>{
    @Cacheable
    List<Site> findByStatus(Site.Status status);
}

Unfortunately, annotating any method in the interface with @Cacheable causes the following exception:

Nov 22, 2014 7:11:06 PM org.apache.catalina.core.ApplicationContext
log SEVERE: StandardWrapper.Throwable
org.springframework.beans.factory.BeanCreationException: Error
creating bean with name 'example': Injection of autowired dependencies
failed; nested exception is
org.springframework.beans.factory.BeanCreationException: Could not
autowire field: com.example.repositories.mongodb.SiteRepository
com.example.siteRepo; nested exception is
org.springframework.beans.factory.BeanCreationException: Error
creating bean with name 'siteRepository': Post-processing of
FactoryBean's singleton object failed; nested exception is
org.springframework.aop.framework.AopConfigException: Could not
generate CGLIB subclass of class [class com.sun.proxy.$Proxy92]:
Common causes of this problem include using a final class or a
non-visible class; nested exception is
java.lang.IllegalArgumentException: Cannot subclass final class class
com.sun.proxy.$Proxy92 at
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:289)
at
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1147)
at
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:519)
at
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:458)
at
org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:293)
at
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:223)
at
org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:290)
at
org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:191)
at
org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:633)
at
org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:932)
at
org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:479)
at
org.springframework.web.servlet.FrameworkServlet.configureAndRefreshWebApplicationContext(FrameworkServlet.java:651)
at
org.springframework.web.servlet.FrameworkServlet.createWebApplicationContext(FrameworkServlet.java:602)
at
org.springframework.web.servlet.FrameworkServlet.createWebApplicationContext(FrameworkServlet.java:665)
at
org.springframework.web.servlet.FrameworkServlet.initWebApplicationContext(FrameworkServlet.java:521)
at
org.springframework.web.servlet.FrameworkServlet.initServletBean(FrameworkServlet.java:462)
at
org.springframework.web.servlet.HttpServletBean.init(HttpServletBean.java:136)
at javax.servlet.GenericServlet.init(GenericServlet.java:160) at
org.apache.catalina.core.StandardWrapper.initServlet(StandardWrapper.java:1189)
at
org.apache.catalina.core.StandardWrapper.loadServlet(StandardWrapper.java:1103)
at
org.apache.catalina.core.StandardWrapper.load(StandardWrapper.java:1010)
at
org.apache.catalina.core.StandardContext.loadOnStartup(StandardContext.java:4935)
at
org.apache.catalina.core.StandardContext$3.call(StandardContext.java:5262)
at
org.apache.catalina.core.StandardContext$3.call(StandardContext.java:5257)
at java.util.concurrent.FutureTask.run(Unknown Source) at
java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) at
java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) at
java.lang.Thread.run(Unknown Source) Caused by:
org.springframework.beans.factory.BeanCreationException: Could not
autowire field: com.example.repositories.mongodb.SiteRepository
com.example.siteRepo; nested exception is
org.springframework.beans.factory.BeanCreationException: Error
creating bean with name 'siteRepository': Post-processing of
FactoryBean's singleton object failed; nested exception is
org.springframework.aop.framework.AopConfigException: Could not
generate CGLIB subclass of class [class com.sun.proxy.$Proxy92]:
Common causes of this problem include using a final class or a
non-visible class; nested exception is
java.lang.IllegalArgumentException: Cannot subclass final class class
com.sun.proxy.$Proxy92 at
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:517)
at
org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87)
at
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:286)
… 27 more Caused by:
org.springframework.beans.factory.BeanCreationException: Error
creating bean with name 'siteRepository': Post-processing of
FactoryBean's singleton object failed; nested exception is
org.springframework.aop.framework.AopConfigException: Could not
generate CGLIB subclass of class [class com.sun.proxy.$Proxy92]:
Common causes of this problem include using a final class or a
non-visible class; nested exception is
java.lang.IllegalArgumentException: Cannot subclass final class class
com.sun.proxy.$Proxy92 at
org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:115)
at
org.springframework.beans.factory.support.AbstractBeanFactory.getObjectForBeanInstance(AbstractBeanFactory.java:1465)
at
org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:304)
at
org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:191)
at
org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:917)
at
org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:860)
at
org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:775)
at
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:489)
… 29 more Caused by:
org.springframework.aop.framework.AopConfigException: Could not
generate CGLIB subclass of class [class com.sun.proxy.$Proxy92]:
Common causes of this problem include using a final class or a
non-visible class; nested exception is
java.lang.IllegalArgumentException: Cannot subclass final class class
com.sun.proxy.$Proxy92 at
org.springframework.aop.framework.CglibAopProxy.getProxy(CglibAopProxy.java:218)
at
org.springframework.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:109)
at
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.createProxy(AbstractAutoProxyCreator.java:477)
at
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.wrapIfNecessary(AbstractAutoProxyCreator.java:362)
at
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.postProcessAfterInitialization(AbstractAutoProxyCreator.java:322)
at
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:409)
at
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.postProcessObjectFromFactoryBean(AbstractAutowireCapableBeanFactory.java:1656)
at
org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:112)
… 36 more Caused by: java.lang.IllegalArgumentException: Cannot
subclass final class class com.sun.proxy.$Proxy92 at
org.springframework.cglib.proxy.Enhancer.generateClass(Enhancer.java:446)
at
org.springframework.cglib.transform.TransformingClassGenerator.generateClass(TransformingClassGenerator.java:33)
at
org.springframework.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25)
at
org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:216)
at
org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:377)
at org.springframework.cglib.proxy.Enhancer.create(Enhancer.java:285)
at
org.springframework.aop.framework.CglibAopProxy.getProxy(CglibAopProxy.java:206)
… 43 more

I am looking for a way to cache calls to DB (which are quite expensive). Any idea?

Best Answer

From the Spring documentation on caches:

Spring recommends that you only annotate concrete classes (and methods of concrete classes) with the @Cache* annotation, as opposed to annotating interfaces. You certainly can place the @Cache* annotation on an interface (or an interface method), but this works only as you would expect it to if you are using interface-based proxies. The fact that Java annotations are not inherited from interfaces means that if you are using class-based proxies ( proxy-target-class="true") or the weaving-based aspect ( mode="aspectj"), then the caching settings are not recognized by the proxying and weaving infrastructure, and the object will not be wrapped in a caching proxy, which would be decidedly bad.

Probably your cache configuration is colliding with the way that Spring creates implementations of your repository interfaces at runtime.

You could review your caching configuration to make it play nice with spring data (see here for different caching configuration options).

Or, you could do this:

public interface SiteRepository extends PagingAndSortingRepository<Site, String>{
    List<Site> findByStatus(Site.Status status);
}

@Service
public class CachedSiteService {

    @Autowired
    private SiteRepository siteRepository;

    @Cacheable("sites")
    List<Site> findByStatus(Site.Status status) {
        return siteRepository.findByStatus(status);
    }

}