Spring Boot EhCache example

Caching is a useful method to improve applications performance. Introducing a cache layer in our application gives a faster response while fetching the data from the cache instead of database or other back-end systems.

In this article, we will learn about how to use the Ehcache with spring boot JPA. Also, we will create a RESTful service and enable the caching for the endpoints.

We will create a RESTful Spring boot web service to store and retrieve the employee objects from the database. Then, we will load the available employee data on application startup into the Ehcache. We will also evict or update the cache on corresponding endpoints.

versions details:

  • Spring Boot Version : 2.1.6 RELEASE
  • Ehcache 3
  • STS 4
  • Java version 1.8
  • PostgreSQL 9.5
  • Postman to test the REST end points.

Creating the Spring Boot application with the Ehcache

Create a spring boot application with required dependencies. Add the web, cache, and the spring data jpa starter dependencies to the application.

As we are using the PostgreSQL database, add that dependency also in our pom.xml file.

<?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 http://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.1.6.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.asb.example</groupId>
	<artifactId>spring-ehcache-example</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>spring-ehcache-example</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>1.8</java.version>
		<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-cache</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.postgresql/postgresql -->
		<dependency>
		    <groupId>org.postgresql</groupId>
		    <artifactId>postgresql</artifactId>
		</dependency>		
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

Configuring the EhCache

To configure the Eh cache, add the below property in the application.properties file under the resources folder.

The property is given below. This property is used to set the Eh cache configuration file for our application(ehcache.xml).

We also have the jpa properties set to connect to the PostgreSQL database. For more details about spring data jpa, refer to this article.

#Ehcache Configuration:
spring.cache.jcache.config=ehcache.xml
#Databse Configuration:
spring.datasource.url=jdbc:postgresql://localhost/postgres
spring.datasource.username=postgres
spring.datasource.password=asbnotebook
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
spring.jpa.hibernate.ddl-auto=none
spring.jpa.show-sql=true

Create an XML configuration file with the name ehcache under the /resources folder. Here, we have defined a cache with the name employeeCache along with other configurations.

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="true"
	monitoring="autodetect" dynamicConfig="true">
	<cache name="employeeCache" 
		maxBytesLocalHeap="10m"
		eternal="false" 
		timeToIdleSeconds="3600" 
		timeToLiveSeconds="1800">
	</cache>
</ehcache>

Enable caching with spring annotations

Spring supports caching with the help of annotations. Annotate the spring boot application class with the @EnableCaching annotation to enable caching.

package com.asb.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@EnableCaching
public class SpringEhcacheExampleApplication {
	public static void main(String[] args) {
		SpringApplication.run(SpringEhcacheExampleApplication.class, args);
	}
}

Creating the controller layer

Create the controller layer, an entity class, a repository layer, and the service layers to store and retrieve the employee details from the database.

The controller class with the CRUD operation endpoints is given below. We have also created a spring event listener method by using the @EventListener annotation. This method loads all the available employee entities from the database into the Eh cache.

package com.asb.example.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.http.HttpStatus;
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.RestController;
import com.asb.example.model.Employee;
import com.asb.example.service.EmployeeService;
@RestController
public class EmployeeController {
	@Autowired
	private EmployeeService employeeService;
	@PostMapping(consumes = "application/json", produces = "application/json", path = "/employee")
	public ResponseEntity<Employee> createEmployee(@RequestBody Employee emp) {
		return new ResponseEntity<>(employeeService.createEmployee(emp), HttpStatus.CREATED);
	}
	@PutMapping(consumes = "application/json", produces = "application/json", path = "/employee")
	public ResponseEntity<Employee> updateEmployee(@RequestBody Employee emp) {
		return new ResponseEntity<>(employeeService.updateEmployee(emp), HttpStatus.CREATED);
	}
	@DeleteMapping(produces = "application/json", consumes = "text/plain", path = "/employee/{empId}")
	public ResponseEntity<String> deleteEmployee(@PathVariable(value = "empId") Long empId) {
		employeeService.deleteEmployee(empId);
		return new ResponseEntity<>("Employee with EmployeeId : " + empId + " deleted successfully", HttpStatus.OK);
	}
	@GetMapping(path = "/employee/{empId}", produces = "application/json")
	public ResponseEntity<Employee> getEmployee(@PathVariable(value = "empId") Long empId) {
		return new ResponseEntity<>(employeeService.getEmployee(empId), HttpStatus.OK);
	}
	@EventListener(classes = { ApplicationStartedEvent.class })
	public ResponseEntity<List<Employee>> getAllEmployees() {
		return new ResponseEntity<>(employeeService.getAllEmployee(), HttpStatus.OK);
	}
}

Create an entity class with the name Employee as shown below. This entity class is mapped with the employee table.

package com.asb.example.model;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@Entity
@AllArgsConstructor
@Getter
@Setter
@ToString
@NoArgsConstructor
@Table(name="Employee")
public class Employee implements Serializable{
	private static final long serialVersionUID = 1L;
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	@Column(name="Id")
	private Long id;
	
	@Column(name="EmployeeName")
	private String employeeName;
	
	@Column(name="EmployeeCode")
	private String employeeCode;
	
	@Column(name="Designation")
	private String designation;
}

Creating the Database table

Create a database table with the name employee, by executing the below SQL script.

CREATE TABLE employee
(
  id bigint NOT NULL,
  designation character varying(255),
  employee_code character varying(255),
  employee_name character varying(255),
  CONSTRAINT employee_pkey PRIMARY KEY (id)
)

Creating the repository layer

Create an EmployeeRepository interface and extend the JpaRepository interface to get the basic support for the CRUD operation.

package com.asb.example.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.asb.example.model.Employee;
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}

Creating the service layer

Create a service layer by creating the EmployeeService interface. Also, add the required methods as given below.

package com.asb.example.service;
import java.util.List;
import com.asb.example.model.Employee;
public interface EmployeeService {
	public Employee getEmployee(Long empId);
	public List<Employee> getAllEmployee();
	void deleteEmployee(Long empId);
	Employee updateEmployee(Employee emp);
	Employee createEmployee(Employee emp);
}

Create an implementation class for the service interface as given below. We are using the Cache Manager to put the employee entity into the cache on the application start event.

Also, we are adding a newly created employee entity into the cache by using the @CachePut annotation. We are using the @CacheEvict to remove the cached entities on the delete operation.

Update operation will put the updated entity back into cache by using the @CachePut annotation. Get employee method by employee id uses the @Cacheable annotation, which returns the entity from cache instead of calling the database.

The value of the cache is the one that is defined earlier in the ehcache.xml file.

Also, we are using the employee id as the cache key in all the service methods.

package com.asb.example.service;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
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.asb.example.model.Employee;
import com.asb.example.repository.EmployeeRepository;
@Service
public class EmployeeServiceImpl implements EmployeeService {
	@Autowired
	private EmployeeRepository employeeRepository;
	@Autowired
	private CacheManager cacheManager;
	@Override
	@CachePut(value = "employeeCache", key = "#result.id")
	public Employee createEmployee(Employee emp) {
		return employeeRepository.save(emp);
	}
	@Override
	@Cacheable(value = "employeeCache", key = "#empId", unless = "#result=null")
	public Employee getEmployee(Long empId) {
		Optional<Employee> optionalEmp = employeeRepository.findById(empId);
		if (optionalEmp.isPresent()) {
			return optionalEmp.get();
		}
		return null;
	}
	@Override
	@CacheEvict(value = "employeeCache", key = "#empId")
	public void deleteEmployee(Long empId) {
		employeeRepository.deleteById(empId);
	}
	@Override
	@CachePut(value = "employeeCache", key = "#emp.id")
	public Employee updateEmployee(Employee emp) {
		return employeeRepository.save(emp);
	}
	@Override
	public List<Employee> getAllEmployee() {
		List<Employee> employees = employeeRepository.findAll();
		for (Employee emp : employees) {
			addToCache(emp);
		}
		return employees;
	}
	public void addToCache(Employee emp) {
		Cache cache = cacheManager.getCache("employeeCache");
		cache.putIfAbsent(emp.getId(), emp);
	}
}

Running the Spring boot application with the Ehcache

Start the spring boot application. On the application startup, the event listener loads all the available employee entities into the cache. The below image shows the hibernate query generated on application startup to load the employee entities from the database.

Spring boot Eh cache example load on startup.

Create an employee by using HTTP POST service endpoint as shown below. This will also insert the employee entity into the cache along with the database.

Create example spring boot jpa eh cache.

Hibernate insert query triggered to store the employee into the database.

Hibernate insert query

Get the saved employee details from the cache by using the GET endpoint.

Get end point Eh cache.

Notice that the application fetches the employee details from Eh cache. We can also observe that the application is not triggering any SQL select query as shown below.

Spring boot Ehcache get example

Update the created employee data by using the PUT endpoint as shown below.

spring update ehcache example.

Now access the GET endpoint and verify if spring updates the cache accordingly without triggering any SQL select query.

Similarly, If we delete the employee details by passing employee id, the Eh cache also should be updated accordingly.

delete ehcache example spring Rest

Deleting the employee entity also evicts the stored cache details from the Eh cache.

If we try to fetch the employee details again, SQL select query will be triggered from the application as there are no employee details present in the Eh cache. Notice the select SQL query issued from the application after the delete query in the below image.

Conclusion

In this article, we learned about setting up the Ehcache on our spring boot application.

In conclusion, using caching techniques on web applications reduces application response time, and also improves the overall applications performance.