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.
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.
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.
@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 CachingIn this post, we will learn the basics of caching and commonly used annotations including @EnableCaching
, @Cacheable
& @CacheEvict
using EhCache
as caching provider.
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.
Spring provides Caching abstractions and annotations to seamlessly adding caching support in any spring application. Main caching annotations being used are @EnableCaching
, @Cacheable
& @CacheEvict
.
@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); } } }
@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.
@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.
<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>
That’s it. Next post discusses all Spring Cache annotations in detail with help of examples.
References
If you like tutorials on this site, why not take a step further and connect me on Facebook , Google Plus & Twitter as well? I would love to hear your thoughts on these articles, it will help improve further our learning process.
In this post we will be developing a full-blown CRUD application using Spring Boot, AngularJS, Spring Data, JPA/Hibernate and MySQL,…
Spring Boot complements Spring REST support by providing default dependencies/converters out of the box. Writing RESTful services in Spring Boot…
Being able to start the application as standalone jar is great, but sometimes it might not be possible to run…
Spring framework has taken the software development industry by storm. Dependency Injection, rock solid MVC framework, Transaction management, messaging support,…
Let's secure our Spring REST API using OAuth2 this time, a simple guide showing what is required to secure a…
This post shows how an AngularJS application can consume a REST API which is secured with Basic authentication using Spring…
View Comments
I have been surfing online more than 3 hours today, yet I never found any interesting article like yours. It is pretty worth enough for me. In my opinion, if all web owners and bloggers made good content as you did, the web will be much more useful than ever before.
Hello websystique,
How can we turn off the ehcache using spring configuration
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].