RefactorFirst

Integrate Caching with Spring Cache and Ehcache 3



In this article, we will look into some Spring Cache concepts and then integrate Ehcache 3.

Introduction

In an application, we can reduce the amount of processing or network calls to another service by introducing caching. Caching allows us to improve our application performance by avoiding executing either computationally heavy tasks or by reducing network latency when communicating with other services.

In this article, we will look at some Spring Cache concepts and then extend this further to use a cache provider called Ehcache.

Creating an Application

Let’s go to https://start.spring.io and create an application with the following dependencies.

Once you generate and open the project, we will add a simple controller as follows.

@RestController  
public class WebService {  
  
    @Autowired  
    private Service service;  
  
  
   @GetMapping("/{name}")  
    public Person generatePerson(@PathVariable(value = "name") String name) {  
    return service.generatePerson(name);  
}

Now here for an incoming request, we have called a service to generate a person with a name. Let’s look at the service class.

@org.springframework.stereotype.Service  
public class Service {  
  
    Logger logger = LoggerFactory.getLogger(Service.class);  
  
    @Cacheable(cacheNames = "cacheStore", key = "#name")  
    public Person generatePerson(String name) {  
        Person person = new Person(UUID.randomUUID().toString(), name, "Switzerland");  
        logger.info("Generated Person: {}", person);  
        return person;  
    }

As you can see here, the method generatePerson has a cachable annotation defining a cache called cacheStore. This way you can configure a cache that will store key-value pairs. Here, the value is the result of the function and the key is the name from the input parameter. To define your key, you can make use of Spring’s Expression Language (SpEL) format. If you don’t provide the key, it will use the input as the key itself.

Now with this let’s start the service and make a call to the service.

curl -i http://localhost:8080/jim

Once the first call is made, the logger from the function generatePerson generates an info log, as the function gets executed. But on a subsequent REST call, the generatePersonfunction is not executed, as it returns the result from the cache that was stored during the previous call.

2021-10-24 09:29:12.846  INFO 22022 --- [           main] c.a.p.S.SpringCachingServiceApplication  : Started SpringCachingServiceApplication in 1.614 seconds (JVM running for 1.963)

2021-10-24 09:29:18.646  INFO 22022 --- [nio-8080-exec-1] c.a.prabhu.Springcachingservice.Service  : Generated Person: Person{id='d04c8531-1054-4773-9410-8b351f4cd785', name='jim', address='Switzerland'}

CachePut

Now, let’s change the annotation from cacheable to cachePutand run the application.

Now, when I make the same REST call twice, it also calls the function twice. This is because in the case of cachePut, The function gets executed and the cache gets updated with the result from the function call. In this way, we can keep the most recent result of the execution.

So now you might wonder, how would this be beneficial? For this let’s extend the service to include another method.

@CachePut(cacheNames = "cacheStore", key = "#name")  
public Person generatePerson(String name) {  
    Person person = new Person(UUID.randomUUID().toString(), name, "Switzerland");  
    logger.info("Generated Person: {}", person);  
    return person;  
}

@Cacheable(cacheNames = "cacheStore", key = "#person.name")  
public Person fetchPerson(Person person) {  
    logger.info("Person request received = {}", person);  
    return person;  
}

Here I am using the same cache cacheStore using the cachable annotation.

Now let’s add an endpoint to call the fetchPerson method.

@GetMapping("/person/{name}")  
public String getPerson(@PathVariable(value = "name") String name) {  
    Person person = new Person("",  
            name,  
            "");  
  
    return service.fetchPerson(person)  
            .toString();  
}

Let’s make the following series of calls and see the output.

Checking cacheable annotation

As you can see, the person Id for the first two calls changes. This because cachePut allows the function to be executed and generates a new person Id.

Now, when we call the person endpoint to fetch the person, it fetches the last value which is stored in the cacheStore cache.

With this, let’s look at how we can evict a cache key-value pair. For this, we have another annotation called cacheEvict .

CacheEvict

Let’s add another endpoint that will evict an entry in the cache.

Webservice

@GetMapping("/evict/{name}")  
public String evictPerson(@PathVariable(value = "name") String name) {  
    Person person = new Person("",  
            name,  
            "");  
  
    service.evictPerson(person);  
    return person.toString();  
}

Service

@CacheEvict(cacheNames = "cacheStore", key = "#person.name")  
public void evictPerson(Person person) {  
    logger.info("evicting Person = {}", person);  
}

Now, let’s make a series of the following calls and look at the output.

cacheable vs cacheput

As you can see, the evict method removed the cached person and on making a subsequent call to the person endpoint, it returns an empty person.

With this, we just saw some major concepts of Spring Cache i.e Cacheable, CachePut, and CacheEvict.

Now by default, Since there is no additional provider, Spring Cache makes use of the JCache which is the default provider present on the classpath when the Spring-Cache-Starter is added as a dependency. It is nothing but a simple ConcurrentHashMap.

Now there are quite a few supported cache providers that we can integrate with spring cache like Redis, Couchbase, Hazelcast, etc. Today we will check out how we can integrate Ehcache 3.

Integrating Ehcache 3 with Spring Spring Cache

To integrate Ehcache 3, We need to add the following two dependencies.

<dependency>  
   <groupId>org.ehcache</groupId>  
   <artifactId>ehcache</artifactId>  
</dependency>  
  
<dependency>  
   <groupId>javax.cache</groupId>  
   <artifactId>cache-api</artifactId>  
</dependency>

We will not specify its version here, as the spring starter knows which dependency version is compatible.

By default, Spring Cache only supports Ehcache 2. x version, hence we have to add some configuration to make it work with version 3.

@Configuration  
public class AppConfig {  
  
    @Bean  
    public CacheManager EhcacheManager() {  
  
        CacheConfiguration<String, Person> cachecConfig = CacheConfigurationBuilder  
          .newCacheConfigurationBuilder(String.class,  
                        Person.class,  
                      ResourcePoolsBuilder.newResourcePoolsBuilder()  
                                .offheap(10, MemoryUnit.MB)  
                                .build())  
.withExpiry(ExpiryPolicyBuilder.timeToIdleExpiration(Duration.ofSeconds(10)))  
                .build();  
  
     CachingProvider cachingProvider = Caching.getCachingProvider();  
     CacheManager cacheManager = cachingProvider.getCacheManager();  
       
   javax.cache.configuration.Configuration<String, Person> configuration = Eh107Configuration.fromEhcacheCacheConfiguration(cachecConfig);  
        cacheManager.createCache("cacheStore", configuration);  
        return cacheManager;  
    }  
}

Here we have done some configuration to define a cache cacheStore with some properties like cache expiration and cache size. We have set the expiration to 10 seconds if there is no request for a key from the cache and also set an off-heap size of 10 MB to be used for caching.

The rest of the application remains the same and with this, we have just integrated Ehcache 3 with our Spring Boot application.

Now let’s start the application and make calls to the generate endpoint.

spring cache with ehcache 3

ehcache 3 with log

So as you can see in the output, the cache entry for “Jim” expired after 10 secs and the person endpoint returned an empty person.

Conclusion

In this article, we explored the main concepts of Spring Cache and then integrated Ehcache 3 provider. We then configured the cache size, the cache expiry, and checked its output. You can read more in-depth about the Spring Cache concepts on the official site here.

As usual, I have uploaded the code on GitHub.

I keep exploring and learning new things. If you want to know the latest trends and improve your software development skills, then subscribe to my newsletter below and also follow me on Twitter.

Enjoy!!