Javarevisited
Published in

Javarevisited

Caching made easy in Spring Boot

Photo by Akash Kaparaveni

Caching is a common practice to improve your system in many ways. It helps to make your system resilient, scalable, fast, and even save some bucks depending on your use case.

If in your database some values are read often then it’s a good idea to cache them. This could be the content of the email that you send to a newly registered user, a catchy banner text that changes every day or the number of hours you ban a user from your system for suspicious activity.

If these are accessed too frequently, you may store them in application properties or even in your code, but then you have to redeploy to make the change effective. These are the type of things which are most likely set by the business team, so if they decide to change something it is an overkill to deploy the application. So you better give them a portal to update these and inside your application you both store them in database and cache such as redis as well.

Yeah, enough talk, let’s see some code. Suppose we have the following entity in our application:

Just plain and simple key value storage to store generic configurations. But the thing is as our application is very popular, these are accessed a thousand times per second. As creating a thousand db connection each second is a bit resource intensive, or our db might be a nosql in the cloud which is billed for each read, write operation… so we decided to cache this.

The game plan is, whenever we are trying to read this from db, first we check in the cache. If found in cache, instead of accessing the db just read from the cache. If not found, then read from db and populate the cache as well.

Now let’s see how does the KeyValuePairServiceImpl may look like:

Here in this method checkInCache we are first looking with the key in redis, if not found then reading from db and updating the cache with this method getFromDb.

And in the save method we are saving in db and updating the cache at the same time.

Yes I get it, logic related to redis can be separated in a different service or repository that might make this file look less ugly. But still, you have to handle this somewhere right?

Well, actually you don’t have to! The thing about spring is, it has so many goodies for common use cases that, after you know it, it feels so obvious that you start to feel kinda stupid. At least I felt that way when I first found out about Caching Abstraction in Spring.

Trust me, it’s very simple and intuitive.

Let’s rewrite the KeyValuePairServiceImpl in a way such that there is no worries for caching or anything. Just the methods and the repository.

See? We completely removed the interaction with redis, not just refactored. Totally removed. Here for each method application will directly interact with the db.

Now, if we want to make the findByKey method cacheable, we just need to add an annotation on top of it. Which is?…… any guess??

Yeah, literally @Cachable with some parameters in it.

@Override
@Cacheable(value="myAwesomeCache",key="#key")
public KeyValuePair findByKey(String key) {

return keyValuePairRepository.findByKey(key);

}

Here, myAwesomeCache is just an arbitrary name, which is not mandatory but it is recommended to use it. Because it’s quite normal that multiple applications use the same redis instance. There might come some cases when more than one application caches something with the same key! This value parameter helps to prevent that.

#key is very important here. It will correspond with the annotated method parameter which we want to be used as the key during caching. If the method would looked like this:

public KeyValuePair findByKey(String myKey)

The the value of key argument of @Cacheble annotation needs to be #myKey.

And there is a tiny bit of configuration left. In your application’s RedisConfig, you have to add another Bean called RedisCacheConfiguration . Here is my Redis config looks like:

Let’s see some action. To test things I’ve quickly added a controller class interacting with the service. Using that we’ll enter some data and try to retrieve it.

First let’s create a greetings entry with the following cURL.

curl -X POST "http://localhost:8080/" -H  "accept: */*" -H  "Content-Type: application/json" -d "{\"key\":\"greetings\",\"value\":\"Hello people of the world!\"}"

Before retrieving it, let’s see how does my redis looks like:

127.0.0.1:6379> KEYS *;127.0.0.1:6379>

Yeah, empty.

I’ve also used postgres as my db here and from the application properties I’ve turned on the sql logging. So, whenever db hit is occurs we will be able to see it in the console.

Now let’s try to retrieve the greetings.

➜  curl -X GET "http://localhost:8080/greetings" -H  "accept: */*"{"id":4,"key":"greetings","value":"Hello people of the world!"}

So it returned the greetings we inserted, and the following log found in the console:

Hibernate: select keyvaluepa0_.id as id1_0_, keyvaluepa0_.key as key2_0_, keyvaluepa0_.value as value3_0_ from key_value_pair keyvaluepa0_ where keyvaluepa0_.key=?

Let’s try retrieving the same entry again. But.. this time no log in the console, that is no hit in the db!

And if we take a look in the available keys in redis, this is what we will see:

127.0.0.1:6379> KEYS *;127.0.0.1:6379> 1) "myAwesomeCache::greetings"

Alright, there is an entry in Redis then with the key greetings. That means before querying into the database the application looked into the cache and responded with it. Now if you delete from the redis, it will again fetch from the database.

Now imagine that you have to update the greetings. You can easily do that with the controller and it will update the db. But the old value will still be in cache. To solve this we need to annotate the save method like this:

@Override
@CacheEvict(value = "myAwesomeCache", key = "#keyValuePair.key")
public void save(KeyValuePair keyValuePair) {
keyValuePairRepository.save(keyValuePair);
}

What it will do is, before saving into database it will delete the entry from redis which has the same key as the out parameter keyValuePair’s key attribute. Thus when it is retrieved next time, fresh value will be populated in the cache.

There might arise a situation that, you don’t want to cache everything. For example for this case, we may use the KeyValuePair to store many things and only want to cache the objects that contain keys that meet a desired condition…. Like startswith “frequent_” string. That is very possible this way:

@Cacheable(value="myAwesomeCache",key="#key", condition = "#key.startsWith('frequent_')")

That’s it for today, I believe I have delivered the ideas I wanted to share. Feel free to provide any feedback if you have.

All the code for this demo is available to download here.

Stay safe, wear mask!

Happy caching!

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Ruhshan Ahmed Abir

Ruhshan Ahmed Abir

Started with poetry, ended up with codes. Have a university degree on Biotechnology. Works and talks about Java, Python, JS. www.buymeacoffee.com/ruhshan