Spring Caching and Ehcache example
In this tutorial, we will show you how to enable data caching in a Spring application, and integrate with the popular Ehcache framework.
Tools used
- Ehcache 2.9
- Spring 4.1.4.RELEASE
- Logback 1.0.13
- Maven 3 / Gradle 2
- JDK 1.7
- Eclipse 4.4
Spring supports caching since version 3.1
Spring cache has been significantly improved since version 4.1
1. Project Directory Structure
2. Project Dependencies
The Spring caching is in the spring-context.jar
, to support Ehcache caching, you need to include the spring-context-support.jar
as well.
For Maven project :
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.9.0</version>
</dependency>
<!-- Optional, to log stuff -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.0.13</version>
</dependency>
<!-- Spring caching framework inside this -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.1.4.RELEASE</version>
</dependency>
<!-- Support for Ehcache and others -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>4.1.4.RELEASE</version>
</dependency>
</project>
For Gradle project :
apply plugin: 'java'
apply plugin: 'eclipse-wtp'
version = '1.0'
// Uses JDK 7
sourceCompatibility = 1.7
targetCompatibility = 1.7
// Get dependencies from Maven central repository
repositories {
mavenCentral()
}
//Project dependencies
dependencies {
compile 'org.springframework:spring-context:4.1.4.RELEASE'
compile 'org.springframework:spring-context-support:4.1.4.RELEASE'
compile 'net.sf.ehcache:ehcache:2.9.0'
compile 'ch.qos.logback:logback-classic:1.0.13'
}
3. Spring Non-Cache Example
A simple DAO to find a movie by director name.
package com.mkyong.movie;
import java.io.Serializable;
public class Movie implements Serializable {
int id;
String name;
String directory;
//getters and setters
//constructor with fields
//toString()
}
package com.mkyong.movie;
public interface MovieDao{
Movie findByDirector(String name);
}
package com.mkyong.movie;
import org.springframework.stereotype.Repository;
@Repository("movieDao")
public class MovieDaoImpl implements MovieDao{
//each call will delay 2 seconds, simulate the slow query call
public Movie findByDirector(String name) {
slowQuery(2000L);
System.out.println("findByDirector is running...");
return new Movie(1,"Forrest Gump","Robert Zemeckis");
}
private void slowQuery(long seconds){
try {
Thread.sleep(seconds);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
}
}
package com.mkyong.test;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan({ "com.mkyong.*" })
public class AppConfig {
}
package com.mkyong.test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.mkyong.movie.MovieDao;
public class App {
private static final Logger log = LoggerFactory.getLogger(App.class);
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
MovieDao obj = (MovieDao) context.getBean("movieDao");
log.debug("Result : {}", obj.findByDirector("dummy"));
log.debug("Result : {}", obj.findByDirector("dummy"));
log.debug("Result : {}", obj.findByDirector("dummy"));
}
}
Output
findByDirector is running...
2015-01-22 10:39:04 [main] DEBUG com.mkyong.test.App - Result : Movie [id=1, name=Forrest Gump, directory=Robert Zemeckis]
findByDirector is running...
2015-01-22 10:39:06 [main] DEBUG com.mkyong.test.App - Result : Movie [id=1, name=Forrest Gump, directory=Robert Zemeckis]
findByDirector is running...
2015-01-22 10:39:08 [main] DEBUG com.mkyong.test.App - Result : Movie [id=1, name=Forrest Gump, directory=Robert Zemeckis]
Each call to findByDirector
will take 2 seconds delay.
4. Spring Caching Example + EhCache
Now, we will enable data caching on method findByDirector
.
4.1 Create a ehcache.xml
file, to tell Ehcache how and where to cache the data.
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="ehcache.xsd"
updateCheck="true"
monitoring="autodetect"
dynamicConfig="true">
<diskStore path="java.io.tmpdir" />
<cache name="movieFindCache"
maxEntriesLocalHeap="10000"
maxEntriesLocalDisk="1000"
eternal="false"
diskSpoolBufferSizeMB="20"
timeToIdleSeconds="300" timeToLiveSeconds="600"
memoryStoreEvictionPolicy="LFU"
transactionalMode="off">
<persistence strategy="localTempSwap" />
</cache>
</ehcache>
To learn how to configure Ehcache, read this official ehcache.xml example.
4.2 Add @Cacheable
on the method you want to cache.
package com.mkyong.movie;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Repository;
@Repository("movieDao")
public class MovieDaoImpl implements MovieDao{
//This "movieFindCache" is delcares in ehcache.xml
@Cacheable(value="movieFindCache", key="#name")
public Movie findByDirector(String name) {
slowQuery(2000L);
System.out.println("findByDirector is running...");
return new Movie(1,"Forrest Gump","Robert Zemeckis");
}
private void slowQuery(long seconds){
try {
Thread.sleep(seconds);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
}
}
4.3 Enable Caching with @EnableCaching
and declared a EhCacheCacheManager
.
package com.mkyong.test;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.ehcache.EhCacheCacheManager;
import org.springframework.cache.ehcache.EhCacheManagerFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
@Configuration
@EnableCaching
@ComponentScan({ "com.mkyong.*" })
public class AppConfig {
@Bean
public CacheManager cacheManager() {
return new EhCacheCacheManager(ehCacheCacheManager().getObject());
}
@Bean
public EhCacheManagerFactoryBean ehCacheCacheManager() {
EhCacheManagerFactoryBean cmfb = new EhCacheManagerFactoryBean();
cmfb.setConfigLocation(new ClassPathResource("ehcache.xml"));
cmfb.setShared(true);
return cmfb;
}
}
4.4 In non-web application, you need to shut down the Spring context manually, so that Ehcache got chance to shut down as well, otherwise Ehcache manager will hang there.
package com.mkyong.test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.mkyong.movie.MovieDao;
public class App {
private static final Logger log = LoggerFactory.getLogger(App.class);
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
MovieDao obj = (MovieDao) context.getBean("movieDao");
log.debug("Result : {}", obj.findByDirector("dummy"));
log.debug("Result : {}", obj.findByDirector("dummy"));
log.debug("Result : {}", obj.findByDirector("dummy"));
//shut down the Spring context.
((ConfigurableApplicationContext)context).close();
}
}
Output
INFO: Initializing EhCache CacheManager
findByDirector is running...
2015-01-22 10:53:28 [main] DEBUG com.mkyong.test.App - Result : Movie [id=1, name=Forrest Gump, directory=Robert Zemeckis]
2015-01-22 10:53:28 [main] DEBUG com.mkyong.test.App - Result : Movie [id=1, name=Forrest Gump, directory=Robert Zemeckis]
2015-01-22 10:53:28 [main] DEBUG com.mkyong.test.App - Result : Movie [id=1, name=Forrest Gump, directory=Robert Zemeckis]
INFO: Shutting down EhCache CacheManager
Review the executed time, there is no delay. In addition, only one “findByDirector is running…” is printed, because this method only executed once, subsequent call will get the object from cache.
Done.
This article is to help you get started with Spring data caching, to learn more about other caching annotations like
@CacheEvict
, @CachePut
, @CacheConfig
and etc, please refer to this official Spring Cache Abstraction documentation, quite detail over there.
public CacheManager cacheManager() {
return new EhCacheCacheManager(ehCacheCacheManager().getObject());
}
Error:
Multiple markers at this line
– The type net.sf.ehcache.CacheManager cannot be resolved. It is indirectly referenced from
required .class files
– The method getObject() from the type EhCacheManagerFactoryBean refers to the missing type
CacheManager
if I want to refresh cache and I don’t want to restart the app, how I can do that?
Use this: @CacheEvict(cacheNames = “user”, key = “#userId”)
Caused by: java.lang.ClassNotFoundException: net.sf.ehcache.CacheManager
me too : (
you might be using latest version of eh-cache. above class is available in eh-cache 2.x.x. if you use eh-cache 3.x you will get above issue
I don’t want to use EhCache.xml as I am using spring boot, is there any way so that I can use caching without going into Xml’s
You can write your own Caching class. Maybe a class with a map as the cache and a synchronized getter for it and if the cache does not contain a value you are looking for it will get added like
public class Cache {
private static Map cache;
if (!cache.containsKey(key)) {
// make a call to fetch whatever you want to store in the cache
cache.put(key, fetched_value)
}
// else return value from the cache map
}
Hope it made some sense.
I always love reading myyong’s examples, very simple and straight to the point. There were two issues I had. First, my spring configuration is in XML. I did:
Second, when I copied the ehcache configuration the maxEntriesLocalHeap was larger then maxEntriesLocalDisk which gives this error:
WARN CacheConfiguration:1865 – Configuration problem for cache employeesById: MaxEntriesLocalHeap equal or greater than MaxEntriesLocalDisk. This will result in useless disk storage.
The amount in memory should always be smaller then what’s on disk.
@alanbdee:disqus you da real mvp
Thanks! I was just about to Google how to use an XML configuration and decided to look down to the comments first.
Hello, thank you for another helpful article.
I have a question – we use the EhCacheManagerFactoryBean by declaring it in xml config. Spring version is 4.1.6.RELEASE. We recently upgraded to 4.3.27.RELEASE and we get BeanCreationException for EhCacheManagerFactoryBean. This is the bean declaration config,
Any help is appreciated. thank you.
If my application is deployed on multiple servers then how can we ensure that data in cache remains consistent on both servers.
For e.g. delete operation which has got cacehEvict annotation should also clear cache not only on server1 but also on server 2.
Whats the best way to accomplish this?
I tried the above example on my existing website. I am also very new to EHCache and Spring and from the logs I can see that the method gets called everytime I refresh the page. I enabled logging on ehcache and I can see this “MemoryStore – Initialized net.sf.ehcache.store.MemoryStore for customBrandListCache”, “Cache – Initialised cache: customBrandListCache “, “ConfigurationHelper – CacheDecoratorFactory not configured. Skipping for ‘customBrandListCache’.”and then “CacheManager – Attempting to create an existing singleton. Existing singleton returned.” I read up on this link https://code.google.com/archive/p/ehcache-spring-annotations/wikis/UsingCacheable.wiki to see if any of this could be the problem. As far as I know, It doesn’t fall in this. Please help.
Nice Example.
I am trying the Junit for above example getting error. Please provide the an example for Junit test of AppConfig .class
much appreciated.
How can we turn off the ehcache using spring configuration
Hi, I’m new to Ehcache.
I’ve used this example for caching mechanism to get a list of objects form DB.
It works fine. However, when a record is updated in DB, the cache is not updated.
Kindly help with an example.
@Kamal, you’ve to use the @CachePut annotation to update the cache.
Sal
goooood