Spring Caching Example with Java and XML Configuration
Spring Caching Annotations
In this tutorial we show you how you can leverage the spring framework caching abstraction to improve the performance of your system. At its core, the abstraction applies caching to Java methods, reducing the number of invocations based on the information available in the cache. You can decorate your java methods with Java annotations or XML Configuration thus minimizing the impact on the code.
How the caching works
Each time a targeted method is invoked, the abstraction will apply a caching behavior checking whether the method has been already executed for the given arguments. If it has, then the cached result is returned without having to execute the actual method; if it has not, then method is executed, the result cached and returned to the user so that, the next time the method is invoked the cached result is returned. This way, expensive methods (whether CPU or IO bound) can be executed only once for a given set of parameters and the result reused without having to actually execute the method again. The caching logic is applied transparently without any interference to the invoker.
Supported Caching Providers
The cache abstraction of the Spring Framework does not provide an actual store and relies on one of the following supported caching providers.
If you haven’t defined a bean of type CacheManager
or a CacheResolver
, Spring Boot tries to detect the following providers (in this order).
- Generic
- JCache (JSR-107) (EhCache 3, Hazelcast, Infinispan, etc)
- EhCache 2
- Hazelcast
- Infinispan
- Couchbase
- Redis
- Caffeine
- Simple
Enable caching annotations
You can easily enable caching by adding the @EnableCaching
annotation to one of your @Configuration
classes. You can also completely disable caching by removing this annotation. Meaning, whenever you’re debugging an issue and you want to make sure it’s not a caching issue.. just remove this @EnableCaching
annotation.
@Configuration
@EnableCaching
public class CacheConfig {
}
Alternatively for XML configuration use the cache:annotation-driven
element:
<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>
@Cacheable annotation
Caching a method in Spring is as simple as annotating a method with the @Cacheable
annotation.
@Cacheable("instruments")
public List findAll() { ... }
In the snippet above, the method findAll
is associated with the cache named instruments
. When the method is executed for the first time, the result is stored into the cache so on subsequent invocations with the same arguments, the value in the cache is returned without invoking the method.
While in most cases one cache is enough, the spring framework supports multiple caches.
@Cacheable(cacheNames = {"instruments", "directory"})
public List findAll() { ... }
In this case, each of the caches will be checked before executing the method – if at least one cache is hit, then the associated value will be returned. All the other caches that do not contain the value will be updated as well.
Conditional Caching
You can optionally use a condition
when a method is not suitable for caching all the time. This condition
takes a SpEL
expression that is evaluated to a boolean
condition. If true
the result is called.
@Cacheable(condition = "#instrument.equals('trombone')")
public String play(String instrument) { .. }
In addition the condition
parameter, the unless
parameter can be used to veto the adding of a value to the cache. Unlike condition
, unless
expressions are evaluated after the method has been called.
@Cacheable(unless = "#result.size() > 25")
public List findAll() { .. }
@CachePut annotation
You can use the @CachePut
annotation to populate the cache. The method will always be executed and its result placed into the cache (according to the @CachePut
options).
@CachePut(cacheNames="instruments", allEntries=true)
public void save(String instrument) { .. }
ImportantBe carefully when mixing
@CachePut
and@Cacheable
annotations on the same method. While the@Cacheable
annotation causes the method execution to be skipped by using the cache, the@CachePut
annotation forces the execution in order to execute a cache update. This can lead to unexpected behavior.
@CacheEvict annotation
The @CacheEvict
is useful for removing stale or unused data from the cache. This method act as a trigger for removing data from the cache.
@CacheEvict
public void delete(String instrument) { .. }
You can optionally pass an extra parameter allEntries=true
to wipe alle the entries from the cache.
@CacheEvict(allEntries=true)
public void delete(String instrument) { .. }
You can also pass in a beforeInvocation=true
parameter. When true
the eviction always occurs before the method is executed.
@CacheEvict(beforeInvocation=true)
public void delete(String instrument) { .. }
@Caching annotation
The @Caching
annotation allows multiple nested @Cacheable
, @CachePut
and @CacheEvict
annotations to be used on the same method.
@Caching(cacheable = {
@Cacheable(condition = "#index == 1"),
@Cacheable(condition = "#index == 2"),
})
public String findByIndex(int index) { .. }
@CacheConfig annotation
You can annotate your class with the @CacheConfig
annotation to specify cache configurations for the entire class.
@CacheConfig(cacheNames = {"directory", "instruments"})
public class MusicServiceIml implements MusicService {..}
Spring Caching Example Java and XML Configuration
In the previous section we saw different caching annotations of the spring framework. In the following section we demonstrate how to use them with a simple example configuration.
Maven Dependencies
We use Apache Maven to manage our project dependencies. Start by adding the following dependencies to your project.
<?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>
<groupId>com.memorynotfound.springboot.caching</groupId>
<artifactId>caching-annotations</artifactId>
<version>1.0.0-SNAPSHOT</version>
<url>https://memorynotfound.com</url>
<name>Spring Boot - ${project.artifactId}</name>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.4.RELEASE</version>
</parent>
<dependencies>
<!-- Spring Framework Caching Support -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Cashing Service Methods
To demonstrate the spring framework caching abstraction, we created a simple service.
package com.memorynotfound.springboot;
import java.util.List;
public interface MusicService {
List<String> findAll();
String findByIndex(int index);
void save(String instrument);
void delete(String instrument);
void deleteAll();
String play( final String instrument );
}
package com.memorynotfound.springboot;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.annotation.*;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Service
@CacheConfig(cacheNames = {"directory", "instruments"})
public class MusicServiceIml implements MusicService {
private static Logger log = LoggerFactory.getLogger(Application.class);
private static List<String> instruments = new ArrayList<String>(Arrays.asList("Guitar", "Bass", "Keyboard"));
@Cacheable(unless = "#result.size() > 25")
@Override
public List<String> findAll() {
log.info("Executing: " + this.getClass().getSimpleName() + ".findAll();");
return instruments;
}
@Cacheable
@Override
public String findByIndex(int index) {
log.info("Executing: " + this.getClass().getSimpleName() + ".findByIndex(\"" + index + "\");");
return instruments.get(index);
}
@CachePut
@Override
public void save(String instrument) {
log.info("Executing: " + this.getClass().getSimpleName() + ".save(\"" + instrument + "\");");
instruments.add(instrument);
}
@CacheEvict
@Override
public void delete(String instrument) {
log.info("Executing: " + this.getClass().getSimpleName() + ".delete(\"" + instrument + "\");");
instruments.remove(instrument);
}
@CacheEvict(allEntries = true)
@Override
public void deleteAll() {
log.info("Executing: " + this.getClass().getSimpleName() + ".deleteAll();");
instruments.removeAll(instruments);
}
@Cacheable(condition = "#instrument.equals('trombone')")
public String play(String instrument) {
log.info("Executing: " + this.getClass().getSimpleName() + ".play(\"" + instrument + "\");");
return "paying " + instrument + "!";
}
}
Spring Cache Configuration
This example uses a simple ConcurrentMapCache
to demonstrate the caching mechanism. The following is the java configuration.
Spring Cache Java Configurationpackage com.memorynotfound.springboot;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Arrays;
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Arrays.asList(
new ConcurrentMapCache("directory"),
new ConcurrentMapCache("instruments")));
return cacheManager;
}
}
package com.memorynotfound.springboot;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Arrays;
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Arrays.asList(
new ConcurrentMapCache("directory"),
new ConcurrentMapCache("instruments")));
return cacheManager;
}
}
And here is the equivalent Spring XML Caching Configuration:
@ImportResource("classpath:cache-config.xml")
<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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-4.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<cache:annotation-driven/>
<context:annotation-config/>
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" name="directory"/>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" name="addresses"/>
</set>
</property>
</bean>
<!-- define caching behavior -->
<cache:advice cache-manager="cacheManager">
<cache:caching cache="instruments, directory">
<cache:cacheable method="findAll" unless="#result.size() > 25"/>
<cache:cacheable method="findByIndex"/>
<cache:cache-put method="save"/>
<cache:cache-evict method="delete"/>
<cache:cache-evict method="deleteAll" all-entries="true"/>
<cache:cacheable method="play" condition="#instrument.equals('trombone')"/>
</cache:caching>
</cache:advice>
</beans>
Bootstrap Spring Application
The following application demonstrates the spring caching by executing a couple of methods annotated with various caching annotations.
package com.memorynotfound.springboot;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application implements CommandLineRunner {
private static Logger log = LoggerFactory.getLogger(Application.class);
@Autowired
private MusicService musicService;
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
@Override
public void run(String... args) throws Exception {
log.info("Spring Boot Conditional Caching and Other Caching Annotations Example");
String service = MusicServiceIml.class.getSimpleName();
log.info("Calling: " + service + ".findAll();");
log.info("Occurrences: " + musicService.findAll());
log.info("Calling: " + service + ".findAll();");
log.info("Occurrences: " + musicService.findAll());
log.info("Calling: " + service + ".findByIndex();");
musicService.findByIndex(1);
log.info("Calling: " + service + ".findByIndex();");
musicService.findByIndex(1);
log.info("Calling: " + service + ".findByIndex();");
musicService.findByIndex(0);
log.info("Calling: " + service + ".findByIndex();");
musicService.findByIndex(0);
log.info("Calling: " + service + ".save();");
musicService.save("Saxophone");
log.info("Calling: " + service + ".findAll();");
log.info("Occurrences: " + musicService.findAll());
log.info("Calling: " + service + ".delete();");
musicService.delete("Bass");
log.info("Calling: " + service + ".findAll();");
log.info("Occurrences: " + musicService.findAll());
log.info("Calling: " + service + ".deleteAll();");
musicService.deleteAll();
log.info("Calling: " + service + ".findAll();");
log.info("Occurrences: " + musicService.findAll());
play("trombone");
play("guitar");
play("trombone");
play("bass");
play("trombone");
}
private void play(String instrument){
log.info("Calling: " + MusicServiceIml.class.getSimpleName() + ".play(\"" + instrument + "\");");
musicService.play(instrument);
}
}
Running Spring Application
We can run our application using the following command.
mvn spring-boot:run
Output
The previous application prints the following output to the console.
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.5.4.RELEASE)
DEBUG - Running with Spring Boot v1.5.4.RELEASE, Spring v4.3.9.RELEASE
INFO - Spring Boot Conditional Caching and Other Caching Annotations Example
INFO - Calling: MusicServiceIml.findAll();
INFO - Executing: MusicServiceIml.findAll();
INFO - Occurrences: [Guitar, Bass, Keyboard]
INFO - Calling: MusicServiceIml.findAll();
INFO - Occurrences: [Guitar, Bass, Keyboard]
INFO - Calling: MusicServiceIml.findByIndex();
INFO - Executing: MusicServiceIml.findByIndex("1");
INFO - Calling: MusicServiceIml.findByIndex();
INFO - Calling: MusicServiceIml.findByIndex();
INFO - Executing: MusicServiceIml.findByIndex("0");
INFO - Calling: MusicServiceIml.findByIndex();
INFO - Executing: MusicServiceIml.findByIndex("0");
INFO - Calling: MusicServiceIml.save();
INFO - Executing: MusicServiceIml.save("Saxophone");
INFO - Calling: MusicServiceIml.findAll();
INFO - Occurrences: [Guitar, Bass, Keyboard, Saxophone]
INFO - Calling: MusicServiceIml.delete();
INFO - Executing: MusicServiceIml.delete("Bass");
INFO - Calling: MusicServiceIml.findAll();
INFO - Occurrences: [Guitar, Keyboard, Saxophone]
INFO - Calling: MusicServiceIml.deleteAll();
INFO - Executing: MusicServiceIml.deleteAll();
INFO - Calling: MusicServiceIml.findAll();
INFO - Executing: MusicServiceIml.findAll();
INFO - Occurrences: []
INFO - Calling: MusicServiceIml.play("trombone");
INFO - Executing: MusicServiceIml.play("trombone");
INFO - Calling: MusicServiceIml.play("guitar");
INFO - Executing: MusicServiceIml.play("guitar");
INFO - Calling: MusicServiceIml.play("trombone");
INFO - Calling: MusicServiceIml.play("bass");
INFO - Executing: MusicServiceIml.play("bass");
INFO - Calling: MusicServiceIml.play("trombone");
References
- @Cacheable Javadoc
- @CacheConfig Javadoc
- @CachePut Javadoc
- @CacheEvict Javadoc
- @Caching Javadoc
- @EnableCaching Javadoc
- @CacheManager Javadoc
- Spring Cache Documentation