Spring Caching provides an easy approach to add caching into an existing Spring application. In this article, we will look at an example of using Ehcache with Spring Boot.
Introduction
Spring offers support for two sets of annotations for caching. The original one are available with Spring 3.1+, while the JSR-107 introduced with Spring 4.1+. It has significantly improved the cache abstraction with the support of JSR-107 annotations and more customization options. In this article we will learn how to use Ehcache with Spring application. We will use Ehcache version 3 for our examples.
[pullquote align=”normal”]Read our article Spring caching for a further knowledge of Spring caching layer. [/pullquote]
1. Project Setup
Spring Boot provides auto-configuration support for the Cache providers. If we have not to defined a bean of type CacheManager
or a CacheResolver
named cacheResolver
, Spring Boot tries to detect the caching API based on the jars in the classpath. We will use Spring Boot for this article, but steps are similar for simple Spring application.
1.1 Maven dependencies
This is how our pom.xml look like:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.javadevjournal</groupId>
<artifactId>spring-boot-ehcache</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Spring Boot With Ehcache</name>
<description>Spring Boot With Ehcache</description>
<properties>
<java.version>1.8</java.version>
<ehcache-version>3.6.1</ehcache-version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>${ehcache-version}</version>
</dependency>
<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Let’s quickly look at some significant points:
- Added caching support with Spring Boot using the spring-boot-starter-cache.
- Add Ehcache 3.
- Add the jar for the JSR-107 API.
2. Ehcache Configuration
Spring’s auto-configuration finds Ehcache’s implementation of JSR-107. However, no caches created by default. Set the spring.cache.jcache.config
property to include the classpath and ehcache.xml
file to tell Spring where to find it.
spring.cache.jcache.config=classpath:ehcache.xml
Next step is to set up the caching for our Spring application. The best and most flexible approach is to use @EnableCaching
annotation:
@SpringBootApplication
@EnableCaching
public class SpringBootWithEhcacheApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootWithEhcacheApplication.class, args);
}
}
To enable caching based on the XML configuration, use the <cache:annotation-driven />
tag:
<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"
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 />
</beans>
2.1 Defining ehcache.xml file
Let’s create an ehcache.xml file with a cache called customer:
<config
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xmlns='http://www.ehcache.org/v3'
xmlns:jsr107='http://www.ehcache.org/v3/jsr107'>
<service>
<jsr107:defaults enable-statistics="true"/>
</service>
<cache alias="customer">
<key-type>java.lang.Long</key-type>
<value-type>com.javadevjournal.data.Customer</value-type>
<expiry>
<ttl unit="seconds">10</ttl>
</expiry>
<listeners>
<listener>
<class>com.javadevjournal.config.CustomCacheEventLogger</class>
<event-firing-mode>ASYNCHRONOUS</event-firing-mode>
<event-ordering-mode>UNORDERED</event-ordering-mode>
<events-to-fire-on>CREATED</events-to-fire-on>
<events-to-fire-on>UPDATED</events-to-fire-on>
<events-to-fire-on>EXPIRED</events-to-fire-on>
<events-to-fire-on>REMOVED</events-to-fire-on>
<events-to-fire-on>EVICTED</events-to-fire-on>
</listener>
</listeners>
<resources>
<heap unit="entries">2000</heap>
<offheap unit="MB">100</offheap>
</resources>
</cache>
</config>
2.2 Custom Listener
Let’s add a custom cache event listener to log the information. This is how our custom listener look like:
public class CustomCacheEventLogger implements CacheEventListener<Object, Object> {
private static final Logger LOG= LoggerFactory.getLogger(CustomCacheEventLogger.class);
@Override
public void onEvent(CacheEvent<!--?,?--> cacheEvent) {
LOG.info("custom Caching event {} {} {} {} ", cacheEvent.getType(),cacheEvent.getKey(),cacheEvent.getOldValue(),cacheEvent.getNewValue());
}
}
You can still use the EventLogger(org.terracotta.ehcache.EventLogger)
available with Ehcache
2.3 @EnableCaching
This annotation enable the proxy interceptors when @Cacheable
annotation methods invoked.Spring Boot provides an easy and flexible option to enable this support by using the @EnableCaching
annotation on the configuration class.
@SpringBootApplication
@EnableCaching
public class SpringBootWithEhcacheApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootWithEhcacheApplication.class, args);
}
}
If you don’t want to annotate your main Spring class with this annotation, we can also create a separate configuration class and add this annotation:
@Configuration
@EnableCaching
public class CacheConfig {
// custom caching config
}
3. Example Application
To test our application, let’s create a simple REST controller which will call the customer services to return a customer object.
@RestController
@RequestMapping("/customers")
public class CustomerController {
@Autowired
private CustomerService customerService;
@GetMapping("/customer/{id}")
public Customer getCustomer(@PathVariable Long id){
return customerService.getCustomer(id);
}
}
This is how our CustomerService
class look like:
@Cacheable(cacheNames = "customer",key="#id")
public Customer getCustomer(final Long id){
LOG.info("Returning customer information for customer id {} ",id);
Customer customer = new Customer();
customer.setCustomerId(id);
customer.setFirstName("Test");
customer.setLastName("User");
customer.setEmail("[email protected]");
return customer;
}
We annotated the method will @Cacheable annotation. This annotation let Spring handle caching for our application.
3.1 Returning List Using Spring Caching
In case you want to cache the List using Spring and Ehcache, you need to do the following steps
- Define a new cache property (optional, you can use the same one).
- Use a static key for the @Cacheable annotation.
@Cacheable(cacheNames = "customerList", key = "'customerList'")
public List < Customer > getCustomers() {
List < Customer > customers = new ArrayList < > ();
LOG.info("Returning customer list");
for (int i = 0; i < 4; i++) {
Customer customer = new Customer();
customer.setCustomerId(Long.valueOf(i));
customer.setFirstName("FirstName" + i);
customer.setLastName("LastName" + i);
customer.setEmail("[email protected]" + i);
customers.add(customer);
}
return customers;
}
Here, we are using a static key as “customerList” and every time we call the method, we will get the same key.
[pullquote align=”normal”]Pay close attention to the "'customerList'"
. I am escaping it using single quotes or you will get an org.springframework.expression.spel.SpelEvaluationException
[/pullquote]
4. Running the Application
Let’s build and run our application to see Spring with Ehcache 3 in action. Once your application start, go to http://localhost:8080/customers/customer/1 , you will have the following output from the controller:
{
"customerId": 1,
"firstName": "Test",
"lastName": "User",
"email": "[email protected]"
}
Check the server console, you will have the following output in the console:
2019-02-26 20:48:22.267 INFO 88901 --- [nio-8080-exec-5] c.j.service.CustomerService : Returning customer information for customer id 1
2019-02-26 20:48:22.267 INFO 88901 --- [e [_default_]-2] c.j.config.CustomCacheEventLogger : custom Caching event CREATED 1 null com.javadevjournal.data.Customer@74606dbe
Let’s try to see few critical points here:
- This was the first call to the API and there was no data with Ehcache.
- The second line shows that Spring caching API created cache entry with Ehcache.
- If you refresh the browser, there will be no new log output as Spring will serve the data from the cache (avoid method call).
We have set the cache ttl (time to live) to 10 seconds, refresh the browser after 10 seconds, you will have following output on the console.
2019-02-26 20:53:51.785 INFO 88901 --- [nio-8080-exec-8] c.j.service.CustomerService : Returning customer information for customer id 1
2019-02-26 20:53:51.785 INFO 88901 --- [e [_default_]-3] c.j.config.CustomCacheEventLogger : custom Caching event EXPIRED 1 com.javadevjournal.data.Customer@17f030bc null
2019-02-26 20:53:51.786 INFO 88901 --- [e [_default_]-3] c.j.config.CustomCacheEventLogger : custom Caching event CREATED 1 null com.javadevjournal.data.Customer@18fee071
This happens because after 10 seconds, the cache entry expired, our cache API performed 2 calls:
- Expired Event to remove invalid entry from cache.
- New/update data added to the cache through a new-created event.
Summary
In this article, we show to set up Ehcache with Spring Boot. We saw the different steps to integrate Ehcache 3 with your Spring application. The source code for this article is available on GitHub.
Hi, Getting below error when running applicaton:
Caused by: java.lang.UnsupportedOperationException: This parser does not support specification “null” version “null”
at javax.xml.parsers.DocumentBuilderFactory.setSchema(DocumentBuilderFactory.java:556) ~[na:1.8.0_191]
at org.ehcache.xml.ConfigurationParser.documentBuilder(ConfigurationParser.java:443) ~[ehcache-3.8.1.jar:3.8.1 a19322e8d4b3f7157e878112c2afc0b6e3090fdd]
at org.ehcache.xml.ConfigurationParser.(ConfigurationParser.java:174) ~[ehcache-3.8.1.jar:3.8.1 a19322e8d4b3f7157e878112c2afc0b6e3090fdd]
at org.ehcache.xml.XmlConfiguration.(XmlConfiguration.java:114) ~[ehcache-3.8.1.jar:3.8.1 a19322e8d4b3f7157e878112c2afc0b6e3090fdd]
… 162 common frames omitted
Are you getting it while running sample application or with your own code?
Hi,
Can you return list of Customer. If possible, give example
Thanks,
Shashikanth
Hi Shashikanth, you can pass a fixed key to the @Cacheable annotation, Spring will internally return as SimpleKey.EMPTY.In this case it will return the cached list of the second invocation of the method (First will be used to cache the result.).
I will update the post shortly.