Caching is a very useful method to improve applications performance. Introducing a cache layer in our application gives faster response as data is fetched from cache, instead of database or other back end system.

In this article we will learn about how to use ehcache with spring boot JPA. We will create RESTful service and enable caching for the end points.

In this example we are going to create RESTful web service to store and retrieve employee object from database. We will load the available employee data on application startup into cache. We also evict or update cache on corresponding end points.

Following are the 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.

Steps involved in enabling EhCache for Spring boot application

  • Create spring boot application with required dependencies.
  • Configure Eh cache.
  • Enable caching with spring annotations.

Create Spring Boot application with required dependencies

Create a spring boot application with required dependencies. In our case, add spring-boot-starter-web, spring-boot-starter- cache and spring-boot-starter-data-jpa dependencies.

Since we are using PostgreSQL database, we have to add that dependency too in our pom.xml file. File content is given below.

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

Configure Eh Cache

To configure Eh cache, add the property spring.cache.jcache.config in application.properties file under /resources directory. The property is given below. This property is used to set Eh cache configuration file for our application(ehcache.xml).

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

application.properties
#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 named ehcache.xml file under /resources directory. We have defined a cache with name employeeCache with other configurations.

ehcache.xml
<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 @EnableCaching to enable cache capability.

SpringEhcacheExampleApplication.java
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);
	}
}

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

Controller class with CRUD operation RESTful service end points is given below. We have also created an spring event listener method by using @EventListener annotation. This method loads all the available employee entities from database into Eh cache.

EmployeeController.java
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 name Employee.java as shown below. This entity class is mapped with database table employee.

Employee.java
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;
}
Create Database table Employee

Create a database table named employee by executing SQL script given below.

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)
)

Create EmployeeRepository.java interface and extend JpaRepository to get the out of the box support for CRUD operation.

EmployeeRepository.java
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> {

}

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

EmployeeService.java
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 EmployeeService.java interface as given below.

We have used Cache Manager to put the employee entity into cache on application start event.

We are adding newly created employee entity into cache by using @CachePut annotation. We are using @CacheEvict to remove cached entity on delete operation.

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

Value of the cache(employeeCache) is the one which is defined earlier in ehcache.xml file. In our example, cache key is employee id which is used in all the service methods.

EmployeeServiceImpl.java
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);
	}
}

Time to run the application 🙂

Start the spring boot application. We have application startup event listener, which loads all available employee entities into cache. Following image shows the hibernate query generated on application startup to load the employee entities from database.

Spring boot Eh cache example load on startup.

Create an employee by using POST RESTful service end point as shown below. This will also insert employee entity into cache along with database.

Create example spring boot jpa eh cache.

Hibernate insert query triggered to store the employee into database.

Hibernate insert query

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

Get end point Eh cache.

Notice that the employee details are fetched from Eh cache, as there is no SQL select query is triggered from application as shown below.

Spring boot Ehcache get example

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

spring update ehcache example.

Now Access the GET end point and verify that cache is updated accordingly and no SQL select query is triggered.

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

delete ehcache example spring Rest

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

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

Conclusion :

In this article we learned about setting up the Eh cache on our spring boot application. Using cache on web applications reduces response time taken by the application and also improves the overall applications performance.

You may also interested in :