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

CacheableCachePut
Cacheable will not execute the method, will return when the method is calledCacheput will execute the method and then will return the cache.
Cacheable vs Cacheput

@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,

Save Employee

Step 2:

get the employee by ID: 1,

getEmployee

Step 3:

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

From the logs – Query has been logged – Means DB call is made

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,

updateEmployee

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,

CachePut

Step 8:

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

Evict Employee

Step 9:

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

Step 9

Thats it about the generic cache!

Code can be downloaded here

By Sri

Leave a Reply

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