Categories: spring

Spring 4 Cache Tutorial with EhCache

Caching is crucial for today’s high performance hungry applications. Adding Caching in your Spring based application is rather trivial. There are many caching solutions available in market today namely EhCache, Guava Cache, Caffeine Cache, OScache, JBoss Cache,etc. Spring provides seamless integration with these caching solution using minimal configuration.

Post Spring 4 Caching Annotations Tutorial contains detailed examples with explanations of all spring Caching Annotataions.

Cache Abstraction

Spring Cache Abstraction is a set of interfaces and annotations, used for applying caching to Java methods. The idea is to cache the returned value of a method for a given input[method argument e.g.], and for any further request with same input, return the result from cache without even executing the method, thus reducing the number of executions. Spring’s org.springframework.cache.Cache and org.springframework.cache.CacheManager are main interfaces used for cache management. Do note that Spring Caching is still an abstraction [not a cache implementation] and requires an actual implementation in order to store the cache data.

Caching Providers/Implementations

Spring provides few out of the box abstraction implementations: JDK java.util.concurrent.ConcurrentMap based caches, EhCache, Caffeine, Gemfire cache, Guava caches and JSR-107 compliant caches. Other cache providers can be integrated with minimal configuration. For this post, we will use EhCache as a provider.

Caching Annotations

  • @Cacheable : triggers cache population
  • @CacheEvict : triggers cache eviction
  • @CachePut : updates the cache without interfering with the method execution
  • @Caching : regroups multiple cache operations to be applied on a method
  • @CacheConfig : shares some common cache-related settings at class-level
  • @EnableCaching : Configuration level annotation which enables Caching

In this post, we will learn the basics of caching and commonly used annotations including @EnableCaching, @Cacheable & @CacheEvict using EhCache as caching provider.


Without Caching

In order to understand the need of caching and appreciate caching advantages, lets first see an example where no caching is used.

Below example demonstrates a trivial service , returning specific products. The method which actually lookup products is deliberately made slow to understand the concept.

package com.websystique.spring.model;

public class Product {

 private String name;
 private double price;

 public Product(String name, double price){
  this.name = name;
  this.price = price;
 }
 
 public String getName() {
  return name;
 }
 public void setName(String name) {
  this.name = name;
 }
 public double getPrice() {
  return price;
 }
 public void setPrice(double price) {
  this.price = price;
 }

 @Override
 public String toString() {
  return "Product [name=" + name + ", price=" + price + "]";
 }
}
package com.websystique.spring.service;

import com.websystique.spring.model.Product;

public interface ProductService {

 Product getByName(String name);
}
package com.websystique.spring.service;

import org.springframework.stereotype.Service;

import com.websystique.spring.model.Product;

@Service("productService")
public class ProductServiceImpl implements ProductService{

 @Override
 public Product getByName(String name) {
  slowLookupOperation();
  return new Product(name,100);
 }

 public void slowLookupOperation(){
   try {
             long time = 5000L;
             Thread.sleep(time);
         } catch (InterruptedException e) {
             throw new IllegalStateException(e);
        }
 }
}

And the main to run the application:

package com.websystique.spring.configuration;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;

import com.websystique.spring.service.ProductService;

public class SampleApplication {

 private static final Logger logger = LoggerFactory.getLogger(SampleApplication.class);
 
 public static void main(String[] args){
  AbstractApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

  ProductService service = (ProductService) context.getBean("productService");
  logger.info("IPhone ->" + service.getByName("IPhone"));
  logger.info("IPhone ->" + service.getByName("IPhone"));
  logger.info("IPhone ->" + service.getByName("IPhone"));
  ((AbstractApplicationContext) context).close();
 }
}

If you run above example, following will be the output. Note that each lookup takes 5 seconds, even if the product we are looking for is exactly same.

18:10:05.653 [main] INFO com.websystique.spring.configuration.SampleApplication - IPhone ->Product [name=IPhone, price=100.0]
18:10:10.654 [main] INFO com.websystique.spring.configuration.SampleApplication - IPhone ->Product [name=IPhone, price=100.0]
18:10:15.655 [main] INFO com.websystique.spring.configuration.SampleApplication - IPhone ->Product [name=IPhone, price=100.0]

Now let’s enable caching and see the difference.


With Caching, using EHCache

Spring provides Caching abstractions and annotations to seamlessly adding caching support in any spring application. Main caching annotations being used are @EnableCaching, @Cacheable & @CacheEvict.

1. @Cacheable [ Used for populating and accessing Cache]

@Cacheable annotation indicates that the result of invoking a method (or all methods in a class) can be cached. A cache itself can be imagined as a key-value based Map. First time a method annotated with @Cacheable gets called, it gets executed and it’s return value is stored in Cache using a key[method parameter for instance, ]. Next time, if the method gets called using same key[same parameter for instance], the result is returned directly from Cache, method invocation does not take place.

By default it uses the method parameters [product name in this case] to compute the key , but a SpEL expression can be provided via the key() attribute, or a custom KeyGenerator implementation can replace the default one. Check out the Official reference for details of all possible attributes.

package com.websystique.spring.service;

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import com.websystique.spring.model.Product;

@Service("productService")
public class ProductServiceImpl implements ProductService{

 @Override
 @Cacheable("products")
 public Product getByName(String name) {
  slowLookupOperation();
  return new Product(name,100);
 }

 @CacheEvict(value = "products", allEntries = true)
 public void refreshAllProducts() {
    //This method will remove all 'products' from cache, say as a result of flush API call.
 } 

 public void slowLookupOperation(){
   try {
             long time = 5000L;
             Thread.sleep(time);
         } catch (InterruptedException e) {
             throw new IllegalStateException(e);
        }
 }
}

2. @CacheEvict [ Used for removing items from Cache]

@CacheEvict annotation indicates that a method (or all methods on a class) triggers a cache evict operation, removing specific[or all] items from cache. Various attributes provides complete control to enforce the required behavior for cache-eviction.

3. @EnableCaching [ Used for Enabling Caching support in Spring Applicaion]

@EnableCaching annotation triggers a post processor that inspects every Spring bean for the presence of caching annotations[@Cacheable, @CacheEvict, @CachePut] on public methods. If such an annotation is found, a proxy is automatically created to intercept the method call and handle the caching behavior accordingly.

package com.websystique.spring.configuration;

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;

@EnableCaching
@Configuration
@ComponentScan(basePackages = "com.websystique.spring")
public class AppConfig {

 /* 
 //Suitable for basic use cases, no persistence capabilities or eviction contracts.
 @Bean
 public CacheManager cacheManager() {
         // configure and return an implementation of Spring's CacheManager SPI
         SimpleCacheManager cacheManager = new SimpleCacheManager();
         cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache("products")));
         return cacheManager;
     }
   */ 
 //EhCache based CacheManager, most commonly used in Enterprise applications.
 @Bean
 public CacheManager cacheManager() {
  return new EhCacheCacheManager(ehCacheCacheManager().getObject());
 }

 @Bean
 public EhCacheManagerFactoryBean ehCacheCacheManager() {
  EhCacheManagerFactoryBean factory = new EhCacheManagerFactoryBean();
  factory.setConfigLocation(new ClassPathResource("ehcache.xml"));
  factory.setShared(true);
  return factory;
 }
 
}

org.springframework.cache.Cache & org.springframework.cache.CacheManager are the main abstractions provided by Spring. Please do note that these are the abstraction providing the caching logic, but does not provide the actual storage to store the cache data. Fortunately, there are few implementations of that abstraction available out of the box: JDK java.util.concurrent.ConcurrentMap based caches, EhCache, Gemfire cache, Caffeine, Guava caches and JSR-107 compliant caches.

The first CacheManager[commented] is based on JDK ConcurrentMap which is enough for simple use cases but does not support the persistence or eviction policy. For an enterprise solution, Ehcache is the preferred choice, providing advanced features. We will be focusing on EhCache in this post. We will need following dependency to be added in our pom.xml [available at the end of post] for EhCache support.

  <dependency>
      <groupId>net.sf.ehcache</groupId>
      <artifactId>ehcache</artifactId>
      <version>2.10.2.2.21</version>
  </dependency>

Above EhCacheManagerFactoryBean accepts an XML file[ehcache.xml] providing the caching configuration for individual cache items.

<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="products" 
  maxEntriesLocalHeap="100"
  maxEntriesLocalDisk="1000" 
  eternal="false" 
  timeToIdleSeconds="300" 
  timeToLiveSeconds="600"
  memoryStoreEvictionPolicy="LFU" 
  transactionalMode="off">
  <persistence strategy="localTempSwap" />
 </cache>

</ehcache>

Here we are setting up a cache with name ‘products’. Maximum 100 products will be kept in in-memory [on-heap] store, while maximum 1000 products will be maintained in the DiskStore, on the path specified ‘java.io.tmpdir’ which refers to default temp file path. A product will be expired if it is idle for more than 5 minutes and lives for more than 10 minutes. A detailed description of individual properties can be found at Ehcache Official Reference.

Let’s adapt the main to see the things in action.

package com.websystique.spring.configuration;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;

import com.websystique.spring.service.ProductService;

public class SampleApplication {

 private static final Logger logger = LoggerFactory.getLogger(SampleApplication.class);
 
 public static void main(String[] args){
  AbstractApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

  ProductService service = (ProductService) context.getBean("productService");
  
  logger.info("IPhone ->" + service.getByName("IPhone"));
  logger.info("IPhone ->" + service.getByName("IPhone"));
  logger.info("IPhone ->" + service.getByName("IPhone"));
  logger.info("Refreshing all products");
  service.refreshAllProducts();
  logger.info("IPhone [after refresh]->" + service.getByName("IPhone"));
  logger.info("IPhone [after refresh]->" + service.getByName("IPhone"));
  logger.info("IPhone [after refresh]->" + service.getByName("IPhone"));
  
  ((AbstractApplicationContext) context).close();
 }
}

The output speaks for itself. For the 1st request, method was executed, while for 2nd, 3rd, we got them from cache. Then we fired a remove-all-products-from-cache. 5th line shows that method was re-executed and took 5 seconds, while 6th,7th were again from cache.

19:18:48.439 [main] INFO com.websystique.spring.configuration.SampleApplication - IPhone ->Product [name=IPhone, price=100.0]
19:18:48.439 [main] INFO com.websystique.spring.configuration.SampleApplication - IPhone ->Product [name=IPhone, price=100.0]
19:18:48.439 [main] INFO com.websystique.spring.configuration.SampleApplication - IPhone ->Product [name=IPhone, price=100.0]
19:18:48.439 [main] INFO com.websystique.spring.configuration.SampleApplication - Refreshing all products
19:18:53.457 [main] INFO com.websystique.spring.configuration.SampleApplication - IPhone [after refresh]->Product [name=IPhone, price=100.0]
19:18:53.457 [main] INFO com.websystique.spring.configuration.SampleApplication - IPhone [after refresh]->Product [name=IPhone, price=100.0]
19:18:53.457 [main] INFO com.websystique.spring.configuration.SampleApplication - IPhone [after refresh]->Product [name=IPhone, price=100.0]

In the next post, we will develop an interesting use case involving @CahcePut along with other goodies we have seen here.Stay tune.

pom.xml

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

 <groupId>com.websystique.spring</groupId>
 <artifactId>Spring4CachingExample</artifactId>
 <version>1.0.0</version>
 <packaging>jar</packaging>

 <name>Spring4CachingExample</name>

 <properties>
  <springframework.version>4.3.0.RELEASE</springframework.version>
 </properties>

 <dependencies>
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-core</artifactId>
   <version>${springframework.version}</version>
  </dependency>
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-context</artifactId>
   <version>${springframework.version}</version>
  </dependency>
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-context-support</artifactId>
   <version>${springframework.version}</version>
  </dependency>
  <!-- EHCache -->
  <dependency>
      <groupId>net.sf.ehcache</groupId>
      <artifactId>ehcache</artifactId>
      <version>2.10.2.2.21</version>
  </dependency>
  <!-- SLF4J/Logback -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.1.7</version>
        </dependency>  
 </dependencies>

 <build>
  <plugins>
   <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.2</version>
    <configuration>
     <source>1.7</source>
     <target>1.7</target>
    </configuration>
   </plugin>
  </plugins>
 </build>
</project>

Project Structure

That’s it. Next post discusses all Spring Cache annotations in detail with help of examples.

Download Source Code


References

View Comments

  • Hello websystique,

    After updating the selected entity, it seems the ehcache is not updating the cache value. It's still showing the same data before updating. Even refreshing many times. Hoping to find for solution for this, thanks websystique! Big fan of this tutorial page.

    • Hi Purinsu,The Cache needs to be invalidated/ccleaned in the use-cases where you know the data is not valid anymore [it has just been changed by some flow].

Share
Published by

Recent Posts

Spring Boot + AngularJS + Spring Data + JPA CRUD App Example

In this post we will be developing a full-blown CRUD application using Spring Boot, AngularJS, Spring Data, JPA/Hibernate and MySQL,…

7 years ago

Spring Boot Rest API Example

Spring Boot complements Spring REST support by providing default dependencies/converters out of the box. Writing RESTful services in Spring Boot…

7 years ago

Spring Boot WAR deployment example

Being able to start the application as standalone jar is great, but sometimes it might not be possible to run…

7 years ago

Spring Boot Introduction + hello world example

Spring framework has taken the software development industry by storm. Dependency Injection, rock solid MVC framework, Transaction management, messaging support,…

7 years ago

Secure Spring REST API using OAuth2

Let's secure our Spring REST API using OAuth2 this time, a simple guide showing what is required to secure a…

8 years ago

AngularJS+Spring Security using Basic Authentication

This post shows how an AngularJS application can consume a REST API which is secured with Basic authentication using Spring…

8 years ago