Using Cache in Spring Boot

Let’s imagine a web application, where for each request received, it must read some configuration data of a database. That data doesn’t change usually, but the application, in each request, must connect, execute the correct instructions to read the data, pick it up from the network, etc. Imagine also that the database is very busy or the connection is slow. What would happen? We would have a slow application because it is reading continuously data that hardly changes.

A solution to that problem could be using a cache, but how do you implement it? In that article, I explain how to use a basic cache in Spring Boot.

A Little Theory

The cache is replicated over functions, where for the same entry value, we are waiting for the same return value. That’s why we always have at least one parameter for entry and exit.

A typical example will be this:

@Cacheable(cacheNames="headers")
public int cachedFunction(int value){
..... complicated and difficult calculations ....
  return N;
}

And now, let’s suppose we have the next code for calling that function:

int value=cachedFunction(1);
int otherValue=cachedFunction(2);
int thirdValue=cachedFunction(1);

When executing the program, in the first line, Spring will execute the function and save the result that returns. In the second line, if it doesn’t know the value it must return for the input “2.” Nevertheless, in the third line, Spring will detect that a function tagged as @Cacheable   with the name “headers” was already called with the value “1.” It won’t execute the function, it will only return the value that in the first call it saved.

The cache’s name is important because, among other things, it permits us to have different independent caches, which we could clean to instruct Spring Boot to execute the functions again.

So, the idea is that in each call to a function tagged as @Cacheable it will save the return values for each call in an internal table, in such a way that if it already has a return value for one entry, it doesn’t call to the function.

The Practice

And now, let’s get to the practice.

An example project can be found here.

First, we must include the following dependency in our project.

<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

Now, we can use the tags that will allow us to use Cache  in our application.

The first tag set is  @EnableCaching. With this label, we tell Spring that it must prepare the support to use Cache. If we do not put it, it will simply not use Cache, regardless of whether we then mark the functions with cache tags.

@SpringBootApplication
@EnableCaching
public class CacheExampleApplication {
    public static void main(String[] args) {
          SpringApplication.run(CacheExampleApplication.class, args);
    }
}

In this example, we read the data of a database using REST requests.

Data  in the CacheDataImpl.java class which is in the package com.profesorp.cacheexample.impl

The function that reads the data is the following:

@Cacheable(cacheNames="headers", condition="#id > 1")
public DtoResponse getDataCache(int id) {         
    try {
        Thread.sleep(500);
    } catch (InterruptedException e) {
    }                              
    DtoResponse requestResponse=new DtoResponse();                     
    Optional<Invoiceheader> invoice=invoiceHeaderRepository.findById(id);
    .....MORE CODE WITHOUT IMPORTANCE ...
}

As can be seen, we have the tag  @Cacheable(cacheNames="headers", condition="#id > 1") 

With this, we told Spring two things:

  1. We want to cache the result of this function.
  2. We put it as a condition that it must store the results in cache if the input is greater than one.

Later, in the function flushCache we put the tag @CacheEvict that cleans the indicated cache. Also, in this case, we tell it to delete all the entries that it has in cache.

@CacheEvict(cacheNames="headers", allEntries=true)
public void flushCache() { }

In the function update we update the database and with the label @CachePut, we inform Spring that it updates the data for the existing value in dtoRequest.id.

Of course, this function must return an object equal to the function labeled with the tag @Cacheable , and we must indicate the input value on which we want to update the data

Running

To understand the application better, we will execute it and give it a request .

The application at the beginning has four invoices in the invoiceHeader table. You can see how it fills the table in the data.sql file

Let’s run the get function of the PrincipalController class. For this we write this:

> curl -s http://localhost:8080/2

The application will return the following:

{"interval":507,"httpStatus":"OK","invoiceHeader":{"id":2,"active":"N","yearFiscal":2019,"numberInvoice":2,"customerId":2}}

The field interval is the time in milliseconds that has takes the application making the request. As can be seen, it has taken more than half a second, because in the getDataCachefunction of CacheDataImpl.java we have a sleep 500instruction.

Now, we execute the call again:

> curl -s http://localhost:8080/2
{"interval":1,"httpStatus":"OK","invoiceHeader":{"id":2,"activo":"N","yearFiscal":2019,"numberInvoice":2,"customerId":2}}

Now the time the call has taken is 1, because Spring hasn’t executed the code of the function, and it has simply returned the value that it had cached.

However, if we request the id as 1, we have indicated that you should not cache this value, always execute the function and therefore we will have a time exceeding 500 milliseconds:

>curl -s http://localhost:8080/1
{"interval":503,"httpStatus":"OK","invoiceHeader":{"id":1,"activo":"S","yearFiscal":2019,"numberInvoice":1,"customerId":1}}
>curl -s http://localhost:8080/1
{"interval":502,"httpStatus":"OK","invoiceHeader":{"id":1,"activo":"S","yearFiscal":2019,"numberInvoice":1,"customerId":1}}
>curl -s http://localhost:8080/1
{"interval":503,"httpStatus":"OK","invoiceHeader":{"id":1,"activo":"S","yearFiscal":2019,"numberInvoice":1,"customerId":1}}

If we call to the flushcache function, we’ll clean the cache and therefore, the next call to the function will execute the code in it.

> curl -s http://localhost:8080/flushcache
Cache Flushed!
> curl -s http://localhost:8080/2
{"interval":508,"httpStatus":"OK","invoiceHeader":{"id":2,"activo":"N","yearFiscal":2019,"numberInvoice":2,"customerId":2}}
> curl -s http://localhost:8080/2
{"interval":0,"httpStatus":"OK","invoiceHeader":{"id":2,"activo":"N","yearFiscal":2019,"numberInvoice":2,"customerId":2}}

Finally, we will see as if we change the value of the field activo to N, since the function that makes the change is labeled with @CacheEvict, it will update the value of the cache, but the getDataCache function won’t execute in the next call.

> curl -X PUT   http://localhost:8080/   -H "Content-Type: application/json"   -d "{\"id\": 2, \"active\": \"N\"}"
>curl -s http://localhost:8080/2
{"interval":0,"httpStatus":"OK","invoiceHeader":{"id":2,"activo":"N","yearFiscal":2019,"numberInvoice":2,"customerId":2}}

Conclusions

Spring without any difficulty allows us to cache the results of the functions. However, you have to take into account that cache is very basic and it is realized in memory. Spring Boot permits us to use external libraries that will allow us to save the data in disc or database.

In the documentation, you can find the different implementations of cache that Spring Boot supports, one of which is EhCache with which you will can different kinds of backend for the data, as well as specify validity times for the data, and more.