Caching using Spring Boot with Example – Cache:
Caching and its Uses:
Caching is the process of storing the data at a temporary location. The main purpose of it is reduce the number of communications with the database.
In an application it is always best to reduce number of database communications to improve the performance of the application. Certain GET calls will mostly return the same results irrespective of number of times its been queried with the database.
So for querying every time to get the same result, we can store them in the cache and whenever the API is called we can retrieve the data from the Cache.
The cache also has its disadvantage, if you build a larger cache then the system will not execute the cache with the same speed as of smaller cache.
There are various caching techniques available,
- Generic
- JCache (JSR-107) (EhCache 3, Hazelcast, Infinispan, and others)
- EhCache 2.x
- Hazelcast
- Infinispan
- Couchbase
- Redis
- Caffeine
- Simple
Refer here.
In this example let use look at a simple generic cache mechanism.
We are going to use the below annotations in our example,
- @EnableCaching
- @Cacheable
- @CachePut
- @CacheEvict
@EnableCaching:
We need to include this annotation in the SpringBootApplication to notify the spring boot application that caching has been included.
@Cacheable:
This is used to cache the values
@CachePut:
This is also used to cache the values.
But there is a difference between @Cacheable and @CachePut
Cacheable | CachePut |
Cacheable will not execute the method, will return when the method is called | Cacheput will execute the method and then will return the cache. |
@CacheEvict:
When you have to remove the cache we can use cacheEvict
Now let us see an example,
pom.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>2.6.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.database</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Database Demo</description>
<properties>
<java.version>1.8</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-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-cache -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</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>
</plugin>
</plugins>
</build>
</project>
Let us create a model class Employee,
package com.database.demo.model;
import javax.persistence.*;
@Entity
@Table(name = "Employee")
public class Employee {
@Id
@GeneratedValue
private int id;
private String name;
private String empId;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmpId() {
return empId;
}
public void setEmpId(String empId) {
this.empId = empId;
}
}
Now let us create an Repository,
package com.database.demo.repository;
import com.database.demo.model.Employee;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface EmployeeDAO extends JpaRepository<Employee, Integer> {
}
Service class,
package com.database.demo.service;
import com.database.demo.model.Employee;
import com.database.demo.repository.EmployeeDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.stereotype.Service;
import org.springframework.cache.annotation.Cacheable;
@Service
public class EmployeeService {
@Autowired
private EmployeeDAO eDao;
@Cacheable("employee")
public Employee getEmployeeById(int id) {
return eDao.findById(id).orElse(null);
}
public Employee saveEmployee(Employee employee) {
return eDao.save(employee);
}
@CacheEvict("employee")
public Employee getEvictEmployee(int id) {
return eDao.findById(id).orElse(null);
}
@CachePut("employee")
public Employee getPutEmployee(int id) {
return eDao.findById(id).orElse(null);
}
}
Now let us create a controller,
package com.database.demo.controller;
import com.database.demo.model.Employee;
import com.database.demo.service.EmployeeService;
import org.springframework.web.bind.annotation.*;
import javax.transaction.Transactional;
@RestController
public class EmployeeController {
private EmployeeService service;
public EmployeeController(EmployeeService service) {
this.service = service;
}
@PostMapping(value = "/saveEmployee")
@Transactional
public Employee save(@RequestBody Employee employee) {
return service.saveEmployee(employee);
}
@GetMapping(value = "/getEmployee/{id}")
public Employee getEmployee(@PathVariable("id") Integer id) {
return service.getEmployeeById(id);
}
@GetMapping(value = "/getPutEmployee/{id}")
public Employee getPutEmployee(@PathVariable("id") Integer id) {
return service.getPutEmployee(id);
}
@GetMapping(value = "/getEvictEmployee/{id}")
public Employee getEvictEmployee(@PathVariable("id") Integer id) {
return service.getEvictEmployee(id);
}
@PutMapping(value="/updateEmployee")
public Employee updateEmployee(@RequestBody Employee employee){
return service.saveEmployee(employee);
}
}
In the above example we have used @Cacheable Annotation with the value “employee”.
When we execute the method first time it will get the data from database and further calling the same API, the data will be retrieved from the cache and not from database.
application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/caching spring.datasource.username=root spring.datasource.password=root spring.jpa.hibernate.ddl-auto=create spring.jpa.properties.hibernate.show_sql=true spring.jpa.properties.hibernate.use_sql_comments=true spring.jpa.properties.hibernate.format_sql=true
SpringBootApplication,
package com.database.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@SpringBootApplication
@EnableJpaRepositories
@EntityScan("com.database.demo.*")
@EnableCaching
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
Now let us run our application and test it,
Step 1: Save the employee,

Step 2:
get the employee by ID: 1,

Step 3:
Now let us see our log file whether the query has been logged,

Step 4:
Step 3, database call is made and as per our code it has to be cached now..Now when we try to getById again this time there should not be any database call.
Then our cache is working fine.
Step 5:
Now update the employee record,

Step 6:
Now when we try the getById, we will still get empId as 001. Why?
Cause, Cacheable will not execute the method and as per the cache – it will still have the old record!

Step 7:
Now let us try cachePut API, it will execute the method and will give us the updated result,

Step 8:
Now let us try to remove our cache with evict API,

Step 9:
After evict, when we try to getById again, it will fetch from database –> we can validate from Query.


Thats it about the generic cache!
Code can be downloaded here