Spring Boot With Redis Cache Example

Spring Boot With Redis Cache Example | Redis Cache: It is a temporary memory, which will be used between the server(application) and the database. This is used to reduce network calls if we trying to fetch the same data multiple times from the database.

We should not implement the cache for all modules, instead select only those modules that are mostly used. If we implement the cache for all modules, then there will be no difference between the database and cache. The cache is a minimal memory that needs to be used in a proper manner. For example in the Gmail application Inbox, Sent, Drafts and other modules are there. But “Inbox” is the most used module among them. Therefore cache should be implemented in the inbox module.

Redis cache supports three operations:-

  • @Cacheable:- Store data in cache while fetching data from the database to the application.
  • @CachePut:- Update data in the cache (if exists) while updating in the database.
  • @CacheEvict:- Remove data from the cache (if exists) while removing from the database.

These operations are affected by selected regions. The region is an area/memory part of the cache created for one module. Example:- EMP-REG, STD-REG, and e.t.c.

Syntax to use @Cacheable/@CachePut/@CacheEvict:-
@Cache__(value = “CACHE-REG”, key = “#Variable”)

Hibernate contains 1st level cache (also called the Session cache) and 2nd level cache (also called as Session Factory cache). But while working with Spring Data JPA, session or session factory objects are not created. Spring Data JPA works on its own cache manager and contains:- EntityManager & EntityManagerFactory.

Spring Boot App without Redis Cache

Let us first see the application without using the Redis cache. Later in this application, we will enable the Redis cache. Create a Spring Boot project by adding the following dependencies:-

  • Spring Data JPA
  • Spring Web
  • MySQL Driver
  • Lombok
  • Spring Boot DevTools
  • Spring Boot Actuator

The “Spring Boot Actuator” dependency is added to check whether the Redis cache is enabled or not. Check the complete application code here:- AppWithoutCache. The controller code is given as:-

package com.knowprogram.demo.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.knowprogram.demo.model.Employee;
import com.knowprogram.demo.service.impl.EmployeeService;

@RestController
@RequestMapping("/api/employee")
public class EmployeeRestController {

    @Autowired
    private EmployeeService service;

    @PostMapping("/save")
    public Employee saveEmployee(@RequestBody Employee employee) {
        return service.saveEmployee(employee);
    }

    @GetMapping("/all")
    public ResponseEntity<List<Employee>> getAlleEmployees() {
        return ResponseEntity.ok(service.getAllEmployees());
    }

    @GetMapping("/one/{id}")
    public Employee getOneEmployee(@PathVariable("id") Integer empId) {
        return service.getOneEmployee(empId);
    }

    @PutMapping("/update/{id}")
    public Employee updateEmployee(@PathVariable("id") Integer empId, 
      @RequestBody Employee employee) {
        return service.updateEmployee(empId, employee);
    }

    @DeleteMapping("/delete/{id}")
    public String deleteEmployee(@PathVariable("id") Integer empId) {
        service.deleteEmployee(empId);
        return "Employee " + empId + " deleted";
    }
}

Run the application, and save some records in the employee table through API. To save employee details give a request to the following URL with the following details. Request URL (POST type):- http://localhost:8080/api/employee/save. Request Body:-

{
    "empName": "Amelia",
    "empSal": 600.0
}

Send the request, and it will save the record in the database, and gives the following response:-

{
    "id": 1,
    "empName": "Amelia",
    "empSal": 600.0
}

You can save a few more records. Now let us fetch the record and see how many times an SQL query has been generated to fetch those records from the database. In the application.properties file, we have enabled spring.jpa.show-sql=true so, generated SQL for each and every operation performed through the database will be displayed on the console.

To fetch the record use the following URL:- http://localhost:8080/api/employee/one/1. Response:-

{
    "id": 1,
    "empName": "Amelia",
    "empSal": 600.0
}

The Following SQL query was generated to fetch the record from the database. You can see them on the console:-

select e1_0.id,e1_0.ename,e1_0.esal from employee e1_0 where e1_0.id=?

When you give a request once again to get records of Employee with id=1 then again the same SQL query will be generated means the application is communication is communicating with the database another time to fetch the same record. For 10 requests, 10 times applications will be communicated to the database.

We are going to enable cache to the method containing logic for fetching employee records based on “id”. When we will give a request then the application will first check within the cache whether related records exist or not, if exist then don’t communicate with the database, directly return the record. But if the related record doesn’t exist in the cache then communicate with the database, fetch the record, store the record in the cache, and return the record. Now, if we give requests more than once within a particular time period for the same “id” then the application is not going to communicate with the database, but fetch the record directly from the cache.

You can also check whether the cache is enabled or not using the actuator. We have already added a dependency for it in our application. You can request the following to check all matrics from the actuator:- http://localhost:8080/actuator (it is a GET request, so the request can also be given from the browser). The following URL contains cache information:- http://localhost:8080/actuator/caches/{cache}, but currently, the cache isn’t enabled right now.

Spring Boot App with Redis Cache Example

Step-1:- Add dependency for Redis.

Add the following dependencies:- “Spring Data Redis(Access+Driver)“. To add a new dependency in an existing project in the STS/Eclipse IDE:- Right-click on the project => Spring => Add Starters => Add “Spring Data Redis(Access+Driver)” => Next => Select pom.xml => Finish.

In the pom.xml file, we can find the following new entry.

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

Step-2: In “Starter class”, add @EnableCaching.

package com.knowprogram.demo;

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

@SpringBootApplication
@EnableCaching
public class AppWithCacheApplication {

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

}

Step-3: Add the below properties in the application.properties file:-

# mandatory properties to enable redis caching
spring.cache.type=redis

# Below are optional
spring.cache.redis.cache-null-values=true
# Store cache for 1 minute
# Time is given in milliseconds
spring.cache.redis.time-to-live=6000

Step-4: Modify service methods.

In EmployeeService class, we want to enable the cache for selecting, updating, & deleting one record. @Cacheable is used to store the cache, @Cacheput is used to update the cache if exists, and @CacheEvict is used to remove the cache if exists.

@Cacheable(value = “employees”, key = “#empId”)
The key value (“#empId“) and method parameter must have the same name.

package com.knowprogram.demo.service.impl;

import java.util.List;

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 com.knowprogram.demo.exception.ResourceNotFoundException;
import com.knowprogram.demo.model.Employee;
import com.knowprogram.demo.repo.EmployeeRepository;
import com.knowprogram.demo.service.IEmployeeService;

@Service
public class EmployeeService implements IEmployeeService {

    @Autowired
    private EmployeeRepository repo;

    @Override
    public Employee saveEmployee(Employee e) {
        return repo.save(e);
    }

    @Override
    @CachePut(value = "employees", key = "#empId")
    public Employee updateEmployee(Integer empId, Employee e) {
        Employee emp = repo.findById(empId).orElseThrow(() 
                -> new ResourceNotFoundException("Employee Not Exist"));
        emp.setEmpName(e.getEmpName());
        emp.setEmpSal(e.getEmpSal());
        return repo.save(emp);
    }

    @Override
    @CacheEvict(value = "employees", key = "#empId")
    public void deleteEmployee(Integer empId) {
        Employee emp = repo.findById(empId).orElseThrow(() 
                -> new ResourceNotFoundException("Employee Not Exist"));
        repo.delete(emp);

    }

    @Override
    @Cacheable(value = "employees", key = "#empId")
    public Employee getOneEmployee(Integer empId) {
        Employee emp = repo.findById(empId).orElseThrow(() 
                -> new ResourceNotFoundException("Employee Not Exist"));
        return emp;
    }

    @Override
    public List<Employee> getAllEmployees() {
        return repo.findAll();
    }

}

In delete, if you want to delete only a particular employee from the cache:-
@CacheEvict(value = “employees”, key = “#empId”)

But if you want to delete including association mapping (child records also) then:-
@CacheEvict(value = “employees”, allEntries = true)

Check the complete code:- AppWithCache. Now, run the application. We already have some records in the database. Give requests to the following URL multiple times with the same employee “id”:- http://localhost:8080/api/employee/one/1

On the console, you will see SQL query generated only once. We have set the cache for 1 minute, so 1 minute later starting from the time when the cache was stored, the cache will be deleted. On updating the record, the record will be first updated in the cache (if exist), and then in the database. Similarly, on deleting the record, the record will be first deleted from the cache (if exist), then from the database.

These all operations are performed internally by the Spring Boot itself because of the “Spring Data Redis(Access+Driver)” dependency, where Redis is used as a database and operations are performed. Also see:- Redis as Database Example in Spring Boot.

While enabling the cache we have given value as “employees”. From the actuator, you can check whether the cache is enabled or not:- http://localhost:8080/actuator/caches/employees. It gives the following response:-

{
    "target": "org.springframework.data.redis.cache.DefaultRedisCacheWriter",
    "name": "employees",
    "cacheManager": "cacheManager"
}

If you enjoyed this post, share it with your friends. Do you want to share more information about the topic discussed above or do you find anything incorrect? Let us know in the comments. Thank you!

Leave a Comment

Your email address will not be published. Required fields are marked *