Open In App

Spring Boot - Caching with Redis

Last Updated : 30 Aug, 2024
Comments
Improve
Suggest changes
Like Article
Like
Report

Caching is a crucial optimization technique used to enhance the performance and scalability of web applications. It temporarily stores data in cache memory to reduce access time and load on backend systems. Redis (Remote Dictionary Server) is a popular open-source, in-memory data structure store used as a cache, database, and message broker. In this article, we will guide you on how to integrate Redis caching into a Spring Boot application to improve its efficiency and response times.

Spring Boot Caching with Redis

Caching is crucial for building high-performance, scalable applications. It helps store frequently accessed data in the cache, reducing the need to access slower underlying storage systems like a database. Redis is a popular in-memory data structure store used as a cache, database, and message broker. Spring Boot seamlessly integrates with Redis for caching through its Spring Cache abstraction.

Introduction to Caching and Redis

  • Caching: Caching involves storing frequently accessed data in memory so that future requests for that data can be served faster, without fetching it from the primary data source (e.g., database, API).
  • Redis: Redis (Remote Dictionary Server) is an open-source, in-memory data store that supports various data structures such as strings, hashes, lists, sets, and sorted sets. Redis is highly performant and commonly used for caching due to its low latency and high throughput.

Why Use Redis for Caching in Spring Boot?

  • Performance: Redis operates in-memory, providing extremely low latency and high-speed data access.
  • Scalability: Redis handles a large number of read/write operations, making it suitable for highly scalable applications.
  • Flexibility: It supports various data types and operations, allowing for complex caching scenarios.
  • Persistence: Redis can be configured to persist data to disk, providing a balance between speed and durability.

Spring Cache Abstraction

The Spring Framework provides a caching abstraction that allows you to define caching logic without binding the application to a specific caching solution. This abstraction can be easily integrated with various caching providers, including Redis.

The Spring Cache abstraction uses annotations to define cache behavior:

  • @EnableCaching: Enables Spring's annotation-driven cache management.
  • @Cacheable: Indicates that the method's return value should be cached.
  • @CachePut: Updates the cache without interfering with the method execution.
  • @CacheEvict: Removes data from the cache.

How Caching Works with Redis in the Spring Boot

  • Cacheable: When @Cacheable is used, Spring first checks if the value is present in the Redis cache. If present, it returns the cached value without executing the method. If not, it executes the method, caches the result in Redis, and returns the result.
  • CachePut: When @CachePut is used, it updates the cache with new data, regardless of whether the method was executed or not.
  • CacheEvict: When @CacheEvict is used, it removes data from the cache, which is useful for maintaining cache consistency when the underlying data changes.

Implementation of Caching with Redis in a Spring Boot Application

Step 1: Create the Spring Boot Project

Create a new Spring Boot project using IntelliJ IDEA with the following options:

  • Name: redis-spring-boot-crud
  • Language: Java
  • Type: Maven
  • Packaging: Jar

Click on the Next button.

Project Metadata

Step 2: Add the Dependencies

Add the following dependencies to your Spring Boot project:

  • Spring Web
  • Lombok
  • Spring Boot DevTools
  • Spring Data Redis
  • Validation
  • Spring Data JPA
  • MySQL Driver

Click on the create button.

Add Dependencies

Project Structure

After Project creation done successfully, then the folder structure will look like the below image:

Project Folder Structure

Step 3: Configure the Application Properties

Open the application.properties file and add the following MySQL and Redis configuration:

spring.application.name=redis-spring-boot-crud

# Redis configuration
spring.data.redis.host=localhost
spring.data.redis.port=6379

# MySQL Database configuration
spring.datasource.url=jdbc:mysql://localhost:3306/redisdb?useSSL=false&serverTimezone=UTC
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=mypassword
spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect

# Enable SQL logging
spring.jpa.show-sql=true

Step 4: Create the Product Class

Create the Product class with fields like id, name, and description:

Java
package com.gfg.redisspringbootcrud.model;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serial;
import java.io.Serializable;

@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Product implements Serializable {

    @Serial
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String description;
}

This class represents a product entity with fields for id, name, and description. It uses JPA annotations to map to the database table.

Step 5: Create the ProductRepository Interface

Create the ProductRepository interface that extends JpaRepository for CRUD operations:

Java
package com.gfg.redisspringbootcrud.repository;

import com.gfg.redisspringbootcrud.model.Product;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ProductRepository extends JpaRepository<Product, Long> {
}

This interface extends JpaRepository to provide CRUD operations for the Product entity.

Step 6: Create the ProductService Class with Caching

Create the ProductService class and use the @Cacheable, @CachePut, and @CacheEvict annotations for caching:

Java
package com.gfg.redisspringbootcrud.service;

import com.gfg.redisspringbootcrud.model.Product;
import com.gfg.redisspringbootcrud.repository.ProductRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

@Service
public class ProductService {

    @Autowired
    private ProductRepository productRepository;

    @Cacheable(value = "products", key = "#id")
    public Optional<Product> getProductById(Long id) {
        System.out.println("Fetching product from database...");
        return productRepository.findById(id);
    }

    @CachePut(value = "products", key = "#product.id")
    public Product saveProduct(Product product) {
        return productRepository.save(product);
    }

    @CacheEvict(value = "products", key = "#id")
    public void deleteProduct(Long id) {
        productRepository.deleteById(id);
    }

    public List<Product> getAllProducts() {
        return productRepository.findAll();
    }
}

This service class handles CRUD operations and caching with Redis. It uses caching annotations to control caching behavior for fetching, saving, and deleting products.

Step 7: Create the ProductController Class

Create the ProductController class to expose CRUD endpoints for products:

Java
package com.gfg.redisspringbootcrud.controller;

import com.gfg.redisspringbootcrud.model.Product;
import com.gfg.redisspringbootcrud.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Optional;

@RestController
@RequestMapping("/api/products")
public class ProductController {

    @Autowired
    private ProductService productService;

    @GetMapping("/{id}")
    public ResponseEntity<Product> getProductById(@PathVariable Long id) {
        Optional<Product> product = productService.getProductById(id);
        return product.map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build());
    }

    @GetMapping
    public List<Product> getAllProducts() {
        return productService.getAllProducts();
    }

    @PostMapping
    public Product createProduct(@RequestBody Product product) {
        return productService.saveProduct(product);
    }

    @PutMapping("/{id}")
    public ResponseEntity<Product> updateProduct(@PathVariable Long id, @RequestBody Product product) {
        Optional<Product> existingProduct = productService.getProductById(id);
        if (existingProduct.isPresent()) {
            product.setId(id);
            return ResponseEntity.ok(productService.saveProduct(product));
        } else {
            return ResponseEntity.notFound().build();
        }
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteProduct(@PathVariable Long id) {
        Optional<Product> product = productService.getProductById(id);
        if (product.isPresent()) {
            productService.deleteProduct(id);
            return ResponseEntity.ok().build();
        } else {
            return ResponseEntity.notFound().build();
        }
    }
}

This controller exposes RESTful endpoints for CRUD operations on Product entities. It handles HTTP requests and interacts with the ProductService.

Step 8: Main Class (Enable Caching in Spring Boot)

Add the @EnableCaching annotation to the main application class to enable caching support:

Java
package com.gfg.redisspringbootcrud;

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

@SpringBootApplication
@EnableCaching
public class RedisSpringBootCrudApplication {

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

This is the main class of the Spring Boot application. The @EnableCaching annotation activates caching support.

pom.xml file:

XML
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.3.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.gfg</groupId>
    <artifactId>redis-spring-boot-crud</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>redis-spring-boot-crud</name>
    <description>redis-spring-boot-crud</description>
    <url/>
    <licenses>
        <license/>
    </licenses>
    <developers>
        <developer/>
    </developers>
    <scm>
        <connection/>
        <developerConnection/>
        <tag/>
        <url/>
    </scm>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</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-validation</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Step 9: Run the Application

After all the steps, now run the project and it will start at port 8080.

Application Started

Step 11. Start the Redis Server

If you are not aware of how to install and running the redis server, then you can refer this article link for better understanding. We can start it using the below command.

redis-server

Step 12: Testing the Application

1. Create the Product

POST http://localhost:8080/api/products

Output:

 Create the Product


2. Fetch All the Products

GET http://localhost:8080/api/products

Output:

Fetch All the Products


3. Fetch the Product by ID

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

Output:

 Fetch the Product by ID

4. Update a Product

PUT http://localhost:8080/api/products/1

Output:

Update a Product

5. Delete a Product

DELETE http://localhost:8080/api/products

Output:

Delete a Product

6. Fetch the Product by ID (Cached)

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

Output:

Fetch the Product by ID (Cached)

The first time this request is made, the product is fetched from the database. On subsequent requests, the product is retrieved from the Redis cache.

Step 13: Verify the Redis Cache

We will now verify the redis cache to check the data can be stored into the Redis Server.

Verify the Redis Cache

This example project demonstrates the basic CRUD application using Spring Boot application with Redis Caching. By implementing the caching, application can handle the repeated read requests efficiently, improving the performance and reducing the database load.

Best Practices for Redis Caching in Spring Boot

  1. Choose Appropriate Cache Keys: Use unique and meaningful keys to avoid cache collisions and ensure accurate cache retrieval.
  2. Set Expiry Times: Configure expiration times to prevent stale data and manage memory usage effectively.
  3. Monitor Cache Performance: Regularly monitor Redis performance and adjust configurations based on usage patterns to maintain optimal performance.
  4. Evict Cache When Necessary: Use the @CacheEvict annotation to clear stale or invalid data from the cache to maintain consistency.
  5. Use an Appropriate Caching Strategy: Choose the right caching strategy (e.g., read-through, write-through, write-behind) based on the specific needs of your application.


Next Article

Similar Reads