Implement Redis Cache in Spring Boot Application
What is Cache?
The cache is a type-safe data structure that allows applications to store data temporarily (as key-value pairs). Its semantics are similar to a java. util. Map object, but it is not exactly the same as a Map.
Why Do We Need Cache?
Caching is a mechanism to improve the performance of any type of application. Technically, caching is the process of storing and accessing data from a cache. But wait, what is a cache? A cache is a software or hardware component aimed at storing data so that future requests for the same data can be served faster.
What is Redis?
Redis is an open-source (BSD licensed), in-memory data structure store used as a database, cache, message broker, and streaming engine. Redis provides data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, geospatial indexes, and streams. Redis has built-in replication, Lua scripting, LRU eviction, transactions, and different levels of on-disk persistence, and provides high availability via Redis Sentinel and automatic partitioning with Redis Cluster.
Redis is an in-memory key-value store that is commonly used as a cache to improve application performance by providing ultra-fast data delivery for increased response times. It does this by accessing data stored in memory, rather than querying the disk of an application database. By doing this, it also alleviates the burden of disk operations.
Steps To Implement Redis Cache in Spring Boot Application
Pre-Requisites:
The Redis server should be installed and running. For installation of redis on mac os please visit my blog-https://codallt.com/index.php/2023/11/16/redis-cache-implementation-in-spring-boot/
Step 1: Add the below dependencies in your pom.xml
</dependencies>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
Note: You might get classpath conflict if you have both “spring-boot-starter-data-jpa” and “spring-boot-starter-data-redis” dependencies.To avoid the conflict remove spring-boot-starter-data-jpa from your pom.xml
Step 2: Enable Caching in Spring Boot Application
Add @EnableCaching annotation in your spring boot application file
package com.codeallt.sampleapp;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@EnableCaching
public class SampleApp extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(SampleApp.class, args);
}
}
Step 3: Redis configuration
package com.codallt.sampleapp.cache;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public JedisConnectionFactory jedisConnectionFactory() {
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
redisStandaloneConfiguration.setHostName("localhost");
redisStandaloneConfiguration.setPort(6379);
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(redisStandaloneConfiguration);
return jedisConnectionFactory;
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(jedisConnectionFactory());
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new JdkSerializationRedisSerializer());
redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
redisTemplate.setEnableTransactionSupport(true);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
Step 4: Creating entity and repository(If you are using data-jpa)
Redis is an in-memory database, we need to transform our object into a stream of bytes for storing as well as the other way around for retrieving data. Therefore, we need to serialize/deserialize our data by ensuring that the entity class implements the Serializable class.
@Entity
public class Book implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
private String code;
private int quantity;
private double price;
}
create our repository by extending the JpaRepository interface. It will provide basic CRUD operations to work with, like save, find, and delete.
@Repository
public interface BookRepository extends JpaRepository<Product, Long> {
}
Step 5: Annotation-based caching
For this example, “we enable caching for specific methods in the controller layer“. The implementation holds good for service and dao layers and any custom methods in any class as well.
@Cacheable
As and when getBookById(@PathVariable long id) method returns the Book data,@Cacheable stores it in the cache. In future method calls, the method retrieves the cached value directly, eliminating the need to execute the method again.
@GetMapping("/book/{id}")
@Cacheable(cacheNames = "book", key = "#id")
public Book getBookById(@PathVariable long id) {
System.out.println("Getting From DB")
Book book=new Book();
//Call Service method and return Book data
return book
}
The cacheName
attribute used to store a cache with a specific name and the key
attribute permits the use of Spring Expression Language to compute the key dynamically. Only when getting the data from the Database we will get “Getting From DB” in our console. From the next time or in future invocation of this method we will not get “Getting From DB” in the console. The reason in Book data will be fetched from Cache, Not from Database.
@CachePut
This will update data in the cache when there is any update in the database for the specified cache.
@PutMapping("/book/{id}")
@CachePut(cacheNames = "book", key = "#id")
public Book editBook(@PathVariable long id, @RequestBody Book book) {...}
@CacheEvict
This is used for removing stale or unused data from the cache
@DeleteMapping("/book/{id}")
@CacheEvict(cacheNames = "book", key = "#id", beforeInvocation = true)
public String removeBookById(@PathVariable long id) {...}
removed for a given cache by using the.If allEntries
attribute as true then all the data for the mentioned cache will be removed
@Caching
If you wish to use multiple or nested caching on the same method then we have to use @Caching.
@PutMapping("/{id}")
@Caching(
evict = {@CacheEvict(value = "bookList", allEntries = true)},
put = {@CachePut(value = "book", key = "#id")}
)
public Book editBook(@PathVariable long id, @RequestBody Book book) {...}