What is Caching?
Caching is the process of storing frequently or recently accessed data in a location that allows faster retrieval when the same data is requested again. It is used to improve the application’s performance by reducing the time and resources needed to access the data or information.
In an application, we use caching to reduce the round-trip between the application and the database. If our application triggers the database every time, it will take more time to return the response, which will further cause the performance issue. In order to avoid this problem, we enable a caching mechanism that first triggers the database and loads the data inside a cache, and then, when the user again requests the same data, the request will not trigger the database but instead will return a response from the cache, which gives better performance.
Redis
Redis is an open-source in-memory data store that can be used as a database, cache, and message broker. In our previous tutorial, Spring Boot Redis CRUD example, we developed a product-service, which we will be using in this tutorial to learn and understand caching and its implementation in Spring Boot.
Steps to implement Spring Boot Redis Cache
Step 1- Enable Spring Boot Caching
In order to implement a caching mechanism in a Spring Boot application, we first need to add the @EnableCaching annotation to our main class of application.
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
/**
*
* 1. Adding @EnableCaching annotation in Main class
* @author paulsofts
*/
@SpringBootApplication
@EnableCaching
public class ProductServicesApplication {
public static void main(String[] args) {
SpringApplication.run(ProductServicesApplication.class, args);
}
}
Step 2- Add caching annotations
Spring Boot caching provides the following three major annotations to handle caching mechanisms at the application level:
- @Cacheable
- The @Cacheable annotation is used to inform the Spring framework that it should cache the result.
- If the same method is called with the same parameter, it should return result from the cache instead of executing the method again.
- @CachePut
- This annotation is used on a method to tell Spring to always execute the method and update the cache with its result.
- It is used when we want to ensure that our cache is updated, even if the method is called multiple times.
- @CacheEvict
- It is used as a method to tell the Spring framework to remove entries from the cache.
- The @CacheEvict annotation is used whenever we want to clear entries from the cache for a specific method or condition.
We are going to use the @cacheable annotation on the @GetMapping method, which is used to get product details using the product ID. We have used caching over the methods in the controller class.
@Cacheable(key = "#pdtId", value = "Product")
In the above syntax, the key represents the ID through which we are fetching the data, and the value represents the hash value in which the entity class will be stored. We have used the same value in our entity and dao classes.
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
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.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.paulsofts.productservices.entity.Product;
import com.paulsofts.productservices.service.ProductService;
/**
* 1. Added @Cacheable annotation on getProduct method.
* 2. @Cacheable(key = "#pdtId", value = "Product")
* key: It represents the key getting passed to the method
* we need to pass its Spring representation
* value: It is the Hash value, which we have used in our
* entity class.
*
* @author paulsofts
*/
@RestController
@RequestMapping("/api/product")
public class ProductController {
@Autowired
private ProductService productService;
@PostMapping("/save")
public Product saveProduct(@RequestBody Product product) {
return productService.saveProduct(product);
}
@GetMapping("/get")
public List<Product> getAllProducts(){
return productService.getAllProducts();
}
@GetMapping("/get/{pdtId}")
@Cacheable(key = "#pdtId", value = "Product")
public Product getProduct(@PathVariable int pdtId) {
return productService.getProductById(pdtId);
}
@DeleteMapping("/delete/{pdtId}")
public String deleteProduct(@PathVariable int pdtId) {
return productService.deleteProduct(pdtId);
}
}
Step 3- Logging
In order to check whether our application is getting the result from the cache or database, we are going to use logs. We will add logging to the Dao layer to check if our application makes a call to the Redis database.
import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Repository;
import com.paulsofts.productservices.entity.Product;
@Repository
public class ProductDao {
/**
* 1. The HASH_KEY is same as we give in our entity class
* inside @RedisHash("Product") annotation.
*
* 2. Logging for getProductById() method.
*/
public static final String HASH_KEY = "Product";
public static Logger LOGGER = LogManager.getLogger(ProductDao.class);
@Autowired
private RedisTemplate redisTemplate;
/**
* The method opsForHash() return operations performed
* on hash values
*/
public Product saveProduct(Product product) {
redisTemplate.opsForHash().put(HASH_KEY, product.getPdtId(), product);
return product;
}
public List<Product> getAllProducts() {
return redisTemplate.opsForHash().values(HASH_KEY);
}
public Product getProductById(int id) {
LOGGER.info("Making a database call...");
return (Product) redisTemplate.opsForHash().get(HASH_KEY, id);
}
public String deleteProduct(int id) {
redisTemplate.opsForHash().delete(HASH_KEY, id);
return "Product deleted with pdtId: " + id;
}
}
Step 4- Testing
Now, we test our application with the help of Postman. We will be requesting the application for the product with pdtId=1.
Below, we can see the logs for the first time the application makes a database call and fetches records.
Afterward, requesting the same records, the application will use caching and fetch the records; we have no logs getting updated.
Similarly, if we make a request for a new product the first time, it will make a database call, and then after for the same product, it will utilize the cache.