In any application or system there may be some data such type which is used frequently in the processing the request for any client. If such data we will be fetch from the database then it should be impact the performance of the system. Spring Framework provides support for caching means we can cache the frequently used data in the application at startup time.
Spring Cache
- The Spring Framework provides support for transparently adding caching to an application. At its core, the abstraction applies caching to ecutions based on the information available in the cache.
- This support is available from Spring Framework 3.1 and there is significant improvement provided in the Spring 4.1.
- This is an abstract framework where Spring only provides the layer where other third party caching implementations can be easily plugged for storing data means cache storage is not implemented by Spring, where as enabling and caching is supported by spring out of the box.
- Caching is supported for the methods and it works well if the methods return the same result for the given input for the multiple invocations.
Supported cache providers in Spring
As we know that the actual implementations of the cache is by third party library and Spring provides only the abstract layer for enabling that specific cache implementation to store the cache data. So there are following cache supported by the Spring.
- Generic
- JCache (JSR-107)
- EhCache 2.x
- Hazelcast
- Infinispan
- Couchbase
- Redis
- Caffeine
- Guava
- Simple
Generic:
Generic caching is used if the context defines at least one org.springframework.cache.Cache bean, a CacheManager wrapping them is configured.
JCache (JSR-107):
JCache is bootstrapped via the presence of a javax.cache.spi.CachingProvider on the classpath (i.e. a JSR-107 compliant caching library).
EhCache 2.x:
EhCache 2.x is used if a file named ehcache.xml can be found at the root of the classpath. If EhCache 2.x and such file is present it is used to bootstrap the cache manager. An alternate configuration file can be providing a well using:
spring.cache.ehcache.config=classpath:config/another-config.xml
Hazelcast:
Both com.hazelcast:hazelcast and com.hazelcast:hazelcast-spring should be added to the project to enable support for Hazelcast. Since there is a default hazelcast.xml configuration file at the root of the classpath, it is used to automatically configure the underlying HazelcastInstance.
Infinispan:
Add the org.infinispan:infinispan-spring4-embedded dependency to enable support for Infinispan. There is no default location that Infinispan uses to look for a config file so if you don’t specify anything it will bootstrap on a hardcoded default. You can set the spring.cache.infinispan.config property to use the provided infinispan.xml configuration instead.
Couchbase:
Add the java-client and couchbase-spring-cache dependencies and make sure that you have setup at least aspring.couchbase.bootstrap-hosts property.
Redis:
Add the spring-boot-starter-data-redis and make sure it is configured properly (by default, a redis instance with the default settings is expected on your local box).
Caffeine:
Simply add the com.github.ben-manes.caffeine:caffeine dependency to enable support for Caffeine. You can customize how caches are created in different ways, see application.properties for an example and the documentation for more details.
Guava:
Spring Boot does not provide any dependency management for Guava so you’ll have to add the com.google.guava:guavadependency with a version. You can customize how caches are created in different ways; see application.properties for an example and the documentation for more details.
Enable Caching
1. @EnableCaching to enable caching because caching in spring is not enabled by default. The caching feature can be declaratively enabled by simply adding the @EnableCaching annotation to any of the configuration classes.
@SpringBootApplication
@EnableCaching
public class SpringBootCacheApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootCacheApplication.class, args);
}
}
In the XML we can you use following tag for enable caching.
<cache:annotation-driven />
2. Cache Configurations – Configure the cache manager where the backing data is stored and retrieved for the quick response.
3. Caching declaration – Identify the methods that have to be cached and define the caching policy.
Use Caching With Annotations
After enabling the cache we can use following list of declarative annotations.
- @Cacheable
- @CacheEvict
- @CachePut
- @Caching
- @CacheConfig
@Cacheable: It is one of the most important and common annotation for caching the requests. If you annotate a method with @Cacheable, if multiple requests are received by the application, then this annotation will not execute the method multiple times, instead it will send the result from the cached storage.
The simplest way to enable caching behavior for a method is to demarcate it with @Cacheable and parameterize it with the name of the cache where the results would be stored:
@Cacheable(value="cities")
public List<City> findAllCity(){
return (List<City>) cityRepository.findAll();
}
The findAllCity() call will first checks the cache cities before actually invoking the method and then caching the result.
Spring framework also supports multiple caches to be passed as parameters:
@Cacheable(value={"cities","city-list"})
public List<City> findAllCity(){
return (List<City>) cityRepository.findAll();
}
@CacheEvict:
If we annotate all methods with @Cacheable then the size of cache may be some problem. We don’t want to populate the cache with values that we don’t need often. Caches can grow quite large, quite fast, and we could be holding on to a lot of stale or unused data. @CacheEvict annotation is used for removing a single cache or clearing the entire cache from the cache storage so that fresh values can be loaded into the cache again:
@CacheEvict(value="cities", allEntries=true)
public List<City> findAllCity(){
return (List<City>) cityRepository.findAll();
}
Here allEntries indicated whether all the data in the cache has to be removed.
@CachePut:
@CachePut annotation helps for updating the cache with the latest execution without stopping the method execution. The difference between @Cacheable and @CachePut is that @Cacheable will skip running the method, whereas @CachePut will actually run the method and then put its results in the cache.
@CachePut(value="cities")
public List<City> findAllCity(){
return (List<City>) cityRepository.findAll();
}
@Caching:
What if you want to use multiple annotations of the same type for caching a method? @Caching annotation used for grouping multiple annotations of the same type together when one annotation is not sufficient for the specifying the suitable condition. For example, you can put multiple @CacheEvict or @CachePut annotation inside @Caching to narrow down your conditions as you need.
@Caching(evict = {
@CacheEvict("cities"),
@CacheEvict(value="city-list", key="#city.name") })
public List<City> findAllCity(){
return (List<City>) cityRepository.findAll();
}
@CacheConfig:
You can annotate @CacheConfig at the class level to avoid repeated mentioning in each method. For example, in the class level you can provide the cache name and in the method you just annotate with @Cacheable annotation.
@CacheConfig(cacheNames={"cities"})
public class CityMasterService {
@Cacheable
public List<City>) findAllCity() {
return (List<City>)) cityRepository.findAll();
}
}
Conditional Caching
Sometimes we don’t want to cache some result there are no need to cache. For example – reusing our example from the @CachePut annotation – this will both execute the method as well as cache the results each and every time:
@CachePut(value="cities")
public List<City>) findAllCity(State state){
return (List<City>)) cityRepository.findAll(state.getStateCode());
}
Condition Parameter:
@CachePut(value="cities", condition="#state.stateName=='UP'")
public ListList<City>) findAllCity(State state){
return (List<City>)) cityRepository.findAll(state.getStateCode());
}
@CachePut can be parameterized with a condition parameter that takes a SpEL expression to ensure that the results are cached based on evaluating that expression.
Unless Parameter:
We can also control the caching based on the output of the method rather than the input – via the unless parameter
@CachePut(value="cities", unless="#result.length()>25")
public List<City>) findAllCity(String state){
return (List<City>)) cityRepository.findAll(state.getStateCode());
}
The above annotation would cache only those cities of state that have minimum 25 cities in the state.