Spring Boot With Ehcache

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:

  1. Added caching support with Spring Boot using the spring-boot-starter-cache.
  2. Add Ehcache 3.
  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

  1. Define a new cache property (optional, you can use the same one).
  2. 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:

  1. This was the first call to the API and there was no data with Ehcache.
  2. The second line shows that Spring caching API created cache entry with Ehcache.
  3. 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:

  1. Expired Event to remove invalid entry from cache.
  2. 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.

4 thoughts on “Spring Boot With Ehcache”

  1. 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

    • 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.

Comments are closed.