Spring Boot Caching Example
In this tutorial, we'll go over Spring Caching in detail with example. We'll look at how to enable default caching in spring boot and enable caching for some of the use cases. Also will see how application performance by using cache. We'll use the Thread.sleep() method to mimic the delay in the actual method call (database call).
What is Caching?
Caching is a way for improving application performance. It is a type of temporary memory that sits between the application and the persistent database. Cache memory store previously utilised data items in order to minimise database calls as much as feasible.
Spring Caching benefits in reducing load and improving application performance.
Benefits of Caching
Caching of commonly reused data in an application is an useful technique for improving application speed. We cache such frequently requested data in memory to prevent calling the costly backends every time the user wants the data. Data access from memory is indeed faster than data access from storage such as a database, file system, or other service calls.
Difference between Cache and Buffer
Cache |
Buffer |
It uses Least Recently Used technique. |
It uses First-In-First-Out technique. |
It have long life span. |
It have short life span. |
It refers to the size of the page cache. |
It refers to an in-memory raw block I/O buffer. |
it read from the cache. |
It write into the buffer. |
It enhances read performance. |
It enhances write performance. |
Spring boot cache annotations
Spring boot offers integration with following cache providers.
- JCache (JSR-107) (EhCache 3, Hazelcast, Infinispan, and others)
- EhCache 2.x
- Hazelcast
- Infinispan
- Couchbase
- Redis
- Caffeine
- Simple Cache
@EnableCaching
.
Now, let's create the Spring Boot Project from Spring Initializer site https://start.spring.io/.
Project Structure
Maven Dependencies
Add spring-boot-starter-cache
for cache and spring-boot-starter-web
for
Rest Api's.
<?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>
<groupId>com.techgeeknext</groupId>
<artifactId>spring-boot-caching</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spring-boot-caching</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</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>
Take a look at our suggested posts:
The caching API provides a set of Java annotations available for use.
@EnableCaching
Enable caching in the Spring Boot application by using the
@EnableCaching
annotation.
It enables caching and in case if there is no CacheManager instance created, then it creates one. It
scans for a certain provider, and, if none is found, it uses concurrent HashMap to establish an
in-memory cache.
package com.techgeeknext;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@EnableCaching
public class SpringBootCachingApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootCachingApplication.class, args);
}
}
@Cacheable
It is a method level annotation. It specific a cache for a method's return value.
The @Cacheable
annotation handles storing the result to the cache.
In this example, our major goal is to cache the data in the service layer, where we'll intentionally delay the response to replicate the actual backend service call. The response will be delayed on the first hit due to a simulated wait in the program, however subsequent calls will receive significantly faster responses.
In the first call, the Employee value will get retrieved from the database and returned value will get store in the cache.
And from the seconds call onwards the Employee value will get retrieved from the cache.
package com.techgeeknext.service;
import com.techgeeknext.caching.annotation.CustomEmployeeCachingAnnotation;
import com.techgeeknext.data.Employee;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class EmployeeServiceImpl implements EmployeeService {
private static final Logger logger = LoggerFactory.getLogger(EmployeeServiceImpl.class);
private List<Employee> employees = new ArrayList<>();
/**
* Method to get the employee default values
* First time, it'' get from database
* Next time onwards it will get it from Cache
*
* @return employees
*/
@Override
@Cacheable("employees")
public List<Employee> getEmployees() {
retrieveDataFromDatabase();
logger.info("----Getting employee data from database.----");
return employees;
}
/**
* Create static employees data
* @param employees
*/
private void createEmployeesData(List<Employee> employees) {
employees.add(Employee.builder().id(1).name("TechGeekNextUser1").role("Admin").build());
employees.add(Employee.builder().id(2).name("TechGeekNextUser2").role("User").build());
employees.add(Employee.builder().id(3).name("TechGeekNextUser3").role("Supervisor").build());
}
@Override
@CustomEmployeeCachingAnnotation
public Employee getEmployeeById(Integer employeeId) {
retrieveDataFromDatabase();
logger.info("----Getting employee data from database.----");
return employees.get(employeeId);
}
/**
* Added sleep for 4 second to mimic for database call
*/
private void retrieveDataFromDatabase() {
try {
createEmployeesData(employees);
logger.info("----Sleep for 4 Secs.. to mimic like it's a backend call.-----");
Thread.sleep(1000 * 4);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Also, we have created CustomCaching as given below and used in above class.
package com.techgeeknext.caching.annotation;
import org.springframework.cache.annotation.Cacheable;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Cacheable(cacheNames = "product")
public @interface CustomEmployeeCachingAnnotation {
}
@CachePut
It is a method level annotation. It is used to update the cache without interfering with the method execution.
The @Cacheable
and @CachePut
annotations differ in that the @Cacheable
annotation skips method execution
whereas the @CachePut
annotation executes the method and caches the result.
@CachePut(cacheNames="employee", key="#id") //updating cache
public Employee updateEmployee(int id, Employee employeeData)
{
//some code
}
@CacheEvict
It is a method level annotation. CacheEvict
used to delete stale or
unused data from the cache.
- Evict the complete cache, remove all data from cache:
//removing all entries of employee from the cache
@CacheEvict(value="employee", allEntries=true)
public String getEmployee(int id)
{
//some code
}
@CacheEvict(key="#employee.name")
@Caching
It is a method level annotation, it's used when needs to regroups multiple cache operations.
@CacheEvict("employees")
@CacheEvict(value="item", key="#key")
public List<Employee> getEmployees()() {...}
Above operations can be put together using @Caching annotation. The @Caching annotation offers a solution to handle multiple use cases together.
@Caching(evict = { @CacheEvict("employees"),
@CacheEvict(cacheNames="item", key="#key") })
public List<Employee> getEmployees()() {...}
@CacheConfig
It is a class-level annotation. It specifies Spring to store the cache for the complete class.
@CacheConfig(cacheNames={"employee"})
public class EmployeeService
{
//some code
}
REST API's
We will create REST API's to test the cache feature.package com.techgeeknext.controller;
import com.techgeeknext.data.Employee;
import com.techgeeknext.service.EmployeeService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class EmployeeController {
private static final Logger logger = LoggerFactory.getLogger(EmployeeController.class);
@Autowired
private EmployeeService employeeService;
@GetMapping("/get/employees")
public List<Employee> getEmployees()
{
logger.info("------------------------------------------");
logger.info("---In getEmployees Controller----");
return employeeService.getEmployees();
}
@GetMapping("/employee/{id}")
public Employee getEmployeeById(@PathVariable Integer id)
{
logger.info("------------------------------------------");
logger.info("---In getEmployeeById Controller----");
return employeeService.getEmployeeById(id);
}
}
Test
Start the Spring Boot Application and follow below steps to understand the cache feature.
- Call to the HTTP GET URL http://localhost:8080//get/employees to get all employees, delay would be notice while getting response first time.
- Again hit the same URL, second time it'll get the data from the cache.
- If you notice the logs printed in the console, first time spring boot will call the mimic database and return the records.
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot ::(v2.0.3.RELEASE)
17:02:09.384 INFO 43924 --- [main] c.t.SpringBootCachingApplication : Starting SpringBootCachingApplication (D:\spring-boot-caching\target\classes started by in D:\spring-boot-caching)
17:02:09.391 INFO 43924 --- [main] c.t.SpringBootCachingApplication : No active profile set, falling back to default profiles: default
17:02:09.610 INFO 43924 --- [main] ConfigServletWebServerApplicationContext : Refreshing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@19f4a5e: startup date [Fri Oct 08 17:02:09 IST 2022]; root of context hierarchy
17:02:13.325 INFO 43924 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
17:02:13.511 INFO 43924 --- [main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
17:02:13.512 INFO 43924 --- [main] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.5.31
17:02:13.571 INFO 43924 --- [ost-startStop-1] o.a.catalina.core.AprLifecycleListener : The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: [C:\Users\\softwares\jdk1.8.0_91\bin;C:\WINDOWS\Sun\Java\bin;C:\WINDOWS\system32;C:\WINDOWS;C:\Program Files (x86)\Common Files\Oracle\Java\javapath;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Windows\System32\OpenSSH\;C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common;C:\Program Files\NVIDIA Corporation\NVIDIA NvDLISR;C:\Program Files\Git\cmd;C:\Program Files\Cloud Foundry;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;C:\WINDOWS\System32\OpenSSH\;C:\Program Files\Docker\Docker\resources\bin;C:\ProgramData\DockerDesktop\version-bin;C:\Program Files\nodejs\;C:\Users\\AppData\Local\Programs\Python\Python36\Scripts\;C:\Users\\AppData\Local\Programs\Python\Python36\;C:\Program Files\MySQL\MySQL Shell 8.0\bin\;C:\Users\\AppData\Local\Programs\Microsoft VS Code\bin;C:\Users\\softwares\apache-maven-3.6.2\bin;C:\Users\\AppData\Local\Microsoft\WindowsApps;C:\Users\\softwares\jdk1.8.0_91\bin;D:\Softwares\apache-storm-1.2.3\apache-storm-1.2.3\\bin;"C:\Python;C:\Python\Lib\site-packages\;C:\Python\Scripts\";C:\tool\terraform_0.15.4_windows_amd64;C:\Users\\AppData\Roaming\npm;C:\bin;;.]
17:02:13.918 INFO 43924 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
17:02:13.919 INFO 43924 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 4322 ms
17:02:14.488 INFO 43924 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean : Servlet dispatcherServlet mapped to [/]
17:02:14.497 INFO 43924 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'characterEncodingFilter' to: [/*]
17:02:14.498 INFO 43924 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
17:02:14.498 INFO 43924 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'httpPutFormContentFilter' to: [/*]
17:02:14.498 INFO 43924 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'requestContextFilter' to: [/*]
17:02:14.878 INFO 43924 --- [main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
17:02:15.805 INFO 43924 --- [main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@19f4a5e: startup date [Fri Oct 08 17:02:09 IST 2022]; root of context hierarchy
17:02:16.165 INFO 43924 --- [main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/get/employees],methods=[GET]}" onto public java.util.List<com.techgeeknext.data.Employee> com.techgeeknext.controller.EmployeeController.getEmployees()
17:02:16.168 INFO 43924 --- [main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/employee/{id}],methods=[GET]}" onto public com.techgeeknext.data.Employee com.techgeeknext.controller.EmployeeController.getEmployeeById(java.lang.Integer)
17:02:16.177 INFO 43924 --- [main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
17:02:16.182 INFO 43924 --- [main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
17:02:16.328 INFO 43924 --- [main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
17:02:16.328 INFO 43924 --- [main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
17:02:17.072 INFO 43924 --- [main] o.s.j.e.a.AnnotationMBeanExporter: Registering beans for JMX exposure on startup
17:02:17.273 INFO 43924 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
17:02:17.289 INFO 43924 --- [main] c.t.SpringBootCachingApplication : Started SpringBootCachingApplication in 9.582 seconds (JVM running for 29.702)
17:03:11.874 INFO 43924 --- [nio-8080-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet'
17:03:11.875 INFO 43924 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet: FrameworkServlet 'dispatcherServlet': initialization started
17:03:11.902 INFO 43924 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet: FrameworkServlet 'dispatcherServlet': initialization completed in 26 ms
2022-10-08 17:03:11.943 INFO 43924 --- [nio-8080-exec-2] c.t.controller.EmployeeController
: ------------------------------------------
2022-10-08 17:03:11.943 INFO 43924 --- [nio-8080-exec-2] c.t.controller.EmployeeController
: ---In getEmployees Controller----
2022-10-08 17:03:11.955 INFO 43924 --- [nio-8080-exec-2] c.t.service.EmployeeServiceImpl
: ----Sleep for 4 Secs.. to mimic like it's a backend call.-----
2022-10-08 17:03:15.964 INFO 43924 --- [nio-8080-exec-2] c.t.service.EmployeeServiceImpl
: ----Getting employee data from database.----
2022-10-08 17:17:33.648 INFO 43924 --- [nio-8080-exec-5] c.t.controller.EmployeeController
: ------------------------------------------
2022-10-08 17:17:33.653 INFO 43924 --- [nio-8080-exec-5] c.t.controller.EmployeeController
: ---In getEmployees Controller----
Download Source Code
The full source code for this article can be found on below.Download it here - Spring Boot Caching Example