With Redis, your Spring Boot app can become faster and handle more traffic without breaking a sweat. This article will show you how to set it up step by step. However, before diving deep into Redis, we need to understand the concept of caching.
What is Cache?
Cache is a fast, small, temporary storage frequently used by the computer or application to store and access important data.
It stores data in a key-value format. By leveraging cache memory, we can minimize database calls, improving application performance since database queries are typically resource-intensive.
The main objective of a cache is to speed up the retrieval of data by making a copy of the data in a location that can be accessed faster than the source or database.
A cache is a small and fast, and efficient memory space that an application frequently uses to store or access important data.
Why Caching?
The main objective of a cache is to speed up the retrieval of data by making a copy of the data in a location that can be accessed faster than the source or database.
In our application, whenever multiple requests access static data (data that is not changed frequently), we fetch the data from the database every time. Therefore, the number of database calls increases, which affects the performance of our application because database calls are always costly.
However, the static data can be stored in a cache, and whenever a request is made to access the data, it is fetched from the cache. As a result, the number of database calls is reduced, and the application's performance is improved.
How does a Cache work?
In the diagram above, multiple requests made to access the data in the application will first check the cache to determine whether the data is present. If the data is found, it is returned from the cache, a concept known as a Cache Hit. If the data is not found, it is retrieved from the database, which is referred to as a Cache Miss.
Cache Hit
Data is found in the cache, so it has to be fetched from a faster source.
We can understand the cache hit like this: imagine you have a notebook where you write down answers to questions you frequently ask. You ask a question, and the answer is already written in your notebook. You quickly find it without searching elsewhere.
Cache Miss
Data is not found in the cache, so it has to be fetched from a slower source.
Similarly, you asked a question, but it’s not in your notebook. You have to search in a big textbook (which takes more time) and then write the answer in your notebook for future use.
Redis
Redis (Remote Dictionary Server) is an open-source, in-memory key-value data store that supports various data structures, including strings, lists, sets, and hashes. Its in-memory architecture ensures high performance, making it an ideal choice for caching and session management in modern applications.
The spring-boot-starter-redis is a Spring Boot starter that simplifies integrating Redis into Spring applications. It includes all the necessary dependencies to connect, configure, and operate with Redis.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
Spring Boot provides the spring-boot-starter-redis
starter, enabling seamless communication with the Redis server. It includes various components that facilitate efficient interaction with Redis.
JedisConnectionFactory: Manages and establishes the connection between the Spring Boot application and the Redis server.
RedisTemplate: It provides methods to perform operations like saving, retrieving, and deleting data in Redis.
StringRedisTemplate: A specialized version of
RedisTemplate
that simplifies operations for String-based keys and values.OpsForValue: Supports operations on simple key-value pairs in Redis.
OpsForHash: It is providing methods to perform operations based on the Hash Data structure.
OpsForList: Provides methods to interact with Redis Lists.
OpsForSet: Facilitates operations on Redis Sets.
Spring Boot with Redis Integration
Spring Boot provides seamless integration with Redis, an in-memory data store, through the spring-boot-starter-redis
starter. It simplifies configuration and enables developers to use Redis for caching, messaging, and data persistence.
Prerequisites for Running the Spring Boot Redis Integration Project
Redis Server (Local or Cloud-based; here we use Docker)
Java Development Kit (JDK 17 or above)
Maven (Build tool)
IDE (e.g., IntelliJ IDEA, Eclipse, or Spring Tool Suite)
Postman (Optional, for testing REST APIs)
Project Build Using Maven
Docker Redis Setup
In this article, we install Redis using Docker. We can also manually download it from the Redis website, but here, we download the latest Redis image from Docker Hub (ensure Docker is already installed on your machine).
We have a sample Spring Boot application that you can clone from GitHub for initial setup. The initial code is available in the main branch, so please check out the main or redis integration branch. I also added the Postman collection to the root directory of the project so you can test all the API before integration.
Steps to Integrate Redis with Spring Boot
Add the Maven dependency in the
pom.xml
file. Since we are using Docker, we also include the Docker Compose dependency.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-docker-compose</artifactId>
<scope>runtime</scope>
</dependency>
2. Configure Redis Connection or database connection (we’re using H2 as database). Add the Redis server configuration to application.properties
or application.yml
spring.application.name=spring-boot-redis-cache
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.cache.type=redis
spring.data.redis.host=localhost
spring.data.redis.port=6379
# if we are using local redis or cloud but here we use docker so there is no need of username or password
spring.data.redis.username=
spring.data.redis.password=
3. Create the docker-compose.yml
file in the root folder with the same naming convention we follow for docker-redis configurations.
services:
redis:
image: redis:7.4.2
ports:
- 6379:6379
4. Mark the Spring Boot application class as @EnableCaching
to enable caching in our Spring Boot application. To enable caching in our Spring Boot application, add the @EnableCaching
annotation to one of our configuration classes. This annotation triggers a post-processor that inspects each Spring bean for caching annotations.
package com.ayshriv.springbootrediscache;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@EnableCaching
public class SpringBootRedisCacheApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootRedisCacheApplication.class, args);
}
}
5. Create the class RedisConfig.class
inside the config package
package com.ayshriv.springbootrediscache.config;
import com.techie.springbootrediscache.dto.ProductDto;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import java.time.Duration;
@Configuration // Marks this class as a Spring configuration class
public class RedisConfig {
@Bean // Defines a Spring bean for RedisCacheManager
public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
// Define cache configuration
RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10)) // Set time-to-live (TTL) for cache entries to 10 minutes
.disableCachingNullValues() // Prevent caching of null values
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new Jackson2JsonRedisSerializer<>(ProductDto.class))); // Serialize values using Jackson JSON serializer
// Create and return a RedisCacheManager with the specified configuration
return RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(cacheConfig) // Apply default cache configuration
.build();
}
}
This class configures Redis caching in a Spring Boot application. The @Configuration
annotation marks it as a configuration class, and the @Bean
annotation defines a RedisCacheManager
bean.
The cache configuration includes a time-to-live (TTL) of 10 minutes (Duration.ofMinutes(10))
, which ensures that cached data expires automatically after this period. It also disables the caching of null
values (disableCachingNullValues())
to optimize memory usage.
For serialization, the Jackson2JsonRedisSerializer
is used, which converts Java objects (ProductDto)
to JSON before storing them in Redis. This ensures that cached data remains structured and readable.
The RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(cacheConfig).build()
the method initializes the cache manager with the given configuration and connects it to the Redis database using RedisConnectionFactory
.
6. After doing all the configurations, we implement the caching on the business class (service class).
package com.ayshriv.springbootrediscache.service;
import com.techie.springbootrediscache.dto.ProductDto;
import com.techie.springbootrediscache.entity.Product;
import com.techie.springbootrediscache.repository.ProductRepository;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
public class ProductService {
private final ProductRepository productRepository;
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
@CachePut(value="PRODUCT_CACHE", key="#result.id")
public ProductDto createProduct(ProductDto productDto) {
var product = new Product();
product.setName(productDto.name());
product.setPrice(productDto.price());
Product savedProduct = productRepository.save(product);
return new ProductDto(savedProduct.getId(), savedProduct.getName(),
savedProduct.getPrice());
}
@Cacheable(value="PRODUCT_CACHE", key="#productId")
public ProductDto getProduct(Long productId) {
Product product = productRepository.findById(productId)
.orElseThrow(() -> new IllegalArgumentException("Cannot find product with id " + productId));
return new ProductDto(product.getId(), product.getName(),
product.getPrice());
}
@CachePut(value="PRODUCT_CACHE", key="#result.id")
public ProductDto updateProduct(ProductDto productDto) {
Long productId = productDto.id();
Product product = productRepository.findById(productId)
.orElseThrow(() -> new IllegalArgumentException("Cannot find product with id " + productId));
product.setName(productDto.name());
product.setPrice(productDto.price());
Product updatedProduct = productRepository.save(product);
return new ProductDto(updatedProduct.getId(), updatedProduct.getName(),
updatedProduct.getPrice());
}
@CacheEvict(value="PRODUCT_CACHE", key="#productId")
public void deleteProduct(Long productId) {
productRepository.deleteById(productId);
}
}
When we mark the createProduct()
method with this @CachePut(value = "PRODUCT_CACHE", key = "#result.id")
annotation, this ensures that the object returned by this method is stored or updated in the cache named PRODUCT_CACHE
, and the key part specifies that the cache key should be the ID of the returned ProductDto
object.
This is useful in scenarios where a new product is created or an existing product is updated, as it ensures that the latest product details are stored in the cache. By using this annotation, subsequent requests for the same product can be served quickly from the cache, reducing database queries and improving application performance while keeping the cache up to date.
Next, when we mark the getProductById()
method with this @Cacheable(value = "PRODUCT_CACHE", key = "#productId")
annotation, this ensures that the returned object by this method is stored in the cache named "PRODUCT_CACHE"
, and the key
The part specifies that the cache key should be the productId
parameter passed to the method.
If the requested productId
is already present in the cache, the method skips execution and directly returns the cached value, avoiding a database call. However, if the productId
is not found in the cache, the method executes, retrieves the product from the database, stores the result in the cache, and then returns it.
When we mark the deleteProduct()
method with this @CacheEvict(value = "PRODUCT_CACHE", key = "#productId")
annotation, this ensures that the cache entry associated with the given productId
is removed from the cache named "PRODUCT_CACHE"
. The key
part specifies that the cache key to be evicted is the productId
parameter passed to the method.
This means that after deleting a product from the database, its cached entry will also be removed, ensuring that stale data is not served from the cache in future requests. If the deleted product is requested again, it will be fetched from the database and re-cached if applicable.
Here, we do all the things with the help of the annotation-based approach, but we can also do the same things with the help of CacheManager
The CacheManager
It is a Spring framework interface responsible for managing different cache implementations. It acts as a central mechanism to store, retrieve, and manage cached data efficiently..
private final CacheManager cacheManager;
public ProductDto createProduct(ProductDto productDto) {
var product = new Product();
product.setName(productDto.name());
product.setPrice(productDto.price());
Product savedProduct = productRepository.save(product);
Cache productCache = cacheManager.getCache("PRODUCT_CACHE");
productCache.put(savedProduct.getId(), savedProduct);
return new ProductDto(savedProduct.getId(), savedProduct.getName(),
savedProduct.getPrice());
}
Test the application
1. ADD-PRODUCT: http://localhost:8080/api/product
2. GET-PRODUCT: http://localhost:8080/api/product/3
3. UPDATE PRODUCT: http://localhost:8080/api/product
4. DELETE PRODUCT: http://localhost:8080/api/product/2
Conclusion
Redis with Spring Boot makes apps faster by caching data and reducing database calls. Using @Cacheable
, @CachePut
, and @CacheEvict,
We can easily store, update, and delete cached data. This improves speed, reduces server load, and helps the app handle more users smoothly.
When we are performing the create operation, the response of this operation is stored in the cache, so whenever we perform operations like the getProduct()
It checks the product in the cache first; if the product is present inside the cache, it returns that; if not, then it will return from the database.
Similarly, when we perform the delete operation, first it deletes the data from the database and then deletes it from the cache as well. All these types of operations reduce the number of DB calls so the performance of the application is increased.