Implement Distributed Caching in Spring Boot with Redis - Complete Guide

Here’s a components diagram represent the interactions between the components in a Spring Boot application using Redis for distributed caching:

Explanation of the Components:

  • User: Represents the client or user making the request for data.
  • Controller: A Spring MVC controller that handles incoming HTTP requests.
  • Service Layer: A service component that contains business logic and communicates with the cache and database.
  • Redis Cache: The caching layer using Redis to store frequently accessed data.
  • Product Database: The backend database where data is fetched from when not found in Redis (cache miss).
  • Cache Manager: Responsible for managing cache expiry and eviction in Redis.

Interactions:

  1. User sends a request for data, which reaches the Controller.
  2. The Controller passes the request to the Service Layer for processing.
  3. The Service Layer checks if the requested data exists in Redis Cache.
  4. If the data is not present in the Redis Cache (cache miss), the Service Layer fetches the data from the Product Database.
  5. The data fetched from the Product Database is then stored in the Redis Cache for future requests.
  6. The Service Layer returns the data to the Controller.
  7. The Controller sends the data back to the User as the final response.
  8. The Cache Manager manages cache expiration, ensuring stale data is removed from Redis Cache.

Cache Management:

  • The Cache Manager handles cache expiry by evicting outdated or expired entries in Redis.


Here's a complete example of implementing distributed caching in a Spring Boot application using Redis. We'll go through all the steps to set it up, configure Redis, and implement caching with annotations.

Step-by-Step Guide

1. Create a Spring Boot Project

Start by creating a Spring Boot project. You can either generate it using Spring Initializr or create it manually.

Select the following dependencies:

  • Spring Web
  • Spring Data Redis
  • Spring Cache

2. Add Dependencies to pom.xml

If you're using Maven, add the following dependencies in your pom.xml:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
</dependencies>

For Gradle, add the following to your build.gradle:

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-data-redis'
    implementation 'org.springframework.boot:spring-boot-starter-cache'
}

3. Configure Redis in application.properties

In your src/main/resources/application.properties, add the following Redis configurations:

spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password= (optional if no password is required)
spring.redis.timeout=2000
spring.cache.type=redis

You can also configure Redis with application.yml:

spring:
  redis:
    host: localhost
    port: 6379
    timeout: 2000
  cache:
    type: redis

4. Enable Caching in Your Spring Boot Application

In your main application class, enable caching by adding the @EnableCaching annotation:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@SpringBootApplication
@EnableCaching
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

5. Set Up Redis Cache Manager (Optional)

Spring Boot will auto-configure Redis, but you can provide custom configuration by creating a RedisConfig class.

import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.redis.RedisCacheConfiguration;
import org.springframework.cache.redis.RedisCacheManager;
import org.springframework.cache.redis.RedisCacheWriter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;

import java.time.Duration;

@Configuration
@EnableCaching
public class RedisConfig {

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(10)); // Set TTL for cache entries
        return RedisCacheManager.builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
                .cacheDefaults(config)
                .build();
    }
}

6. Create a Service with Caching Annotations

Now, let's create a service class that will use caching. In this example, we'll create a method to fetch a product by its ID.

import com.example.demo.model.Product;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class ProductService {

    // Simulate a database query
    public Product findProductById(Long id) {
        // Normally, you would fetch this from a database
        return new Product(id, "Product " + id, "Description of product " + id);
    }

    // Cacheable method - caches the result of this method for the specified key
    @Cacheable(value = "products", key = "#id")
    public Product getProductById(Long id) {
        System.out.println("Fetching product from database...");  // Simulating DB call
        return findProductById(id);
    }
}

In the getProductById() method, the @Cacheable annotation ensures that the result of the method is cached with the specified cache name ("products") and the id as the key.

7. Create a Product Model

Now, create a simple model class Product:

public class Product {

    private Long id;
    private String name;
    private String description;

    // Constructor, Getters, and Setters
    public Product(Long id, String name, String description) {
        this.id = id;
        this.name = name;
        this.description = description;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}

8. Create a Controller to Access Cached Data

Next, create a simple REST controller to expose an API to fetch products by ID:

import com.example.demo.model.Product;
import com.example.demo.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ProductController {

    @Autowired
    private ProductService productService;

    @GetMapping("/products/{id}")
    public Product getProduct(@PathVariable Long id) {
        return productService.getProductById(id);
    }
}

9. Testing the Application

  1. Start Redis Server: Make sure you have Redis running on your machine. If you're using Docker, you can start Redis with:
docker run -p 6379:6379 -d redis
  1. Run the Spring Boot Application: Start your Spring Boot application by running the main class DemoApplication.

  2. Access the API: Open your browser or use a tool like Postman to test the API. Call the following endpoint:

GET http://localhost:8080/products/1

On the first call, it will fetch the product from the database (simulated in our case). On subsequent calls with the same product ID, Redis will serve the cached data.

10. Cache Eviction

You can also use @CacheEvict to remove a cache entry when a product is updated or deleted.

@CacheEvict(value = "products", key = "#product.id")
public void updateProduct(Product product) {
    // Update product logic
}

This example demonstrates how to implement distributed caching with Redis in a Spring Boot application. It improves performance by caching frequently accessed data and reduces load on the underlying database. The caching mechanism is highly customizable, and you can use features like cache expiration, eviction, and clustering for more advanced use cases.


Unlock Your Microservices Mastery for Only $9!

Get your copy now for just $9! and start building resilient and scalable microservices with the help of Microservices with Spring Boot 3 and Spring Cloud.

Comments

Popular posts from this blog

Spring Boot OpenAI Integration: Step-by-Step Guide

Orchestration-Based Saga Architecture and Spring Boot Microservices Implementation Guide

Spring Boot 3 + Angular 15 + Material - Full Stack CRUD Application Example