Show List
Spring Boot Data Cache
Spring Boot Data Cache is a feature of the Spring Boot framework that allows you to cache data in your application to improve performance. Caching can be used to store frequently-accessed data in memory, so that it can be retrieved more quickly when needed. This can help reduce the amount of time spent querying a database or other data source, and can improve the overall performance and responsiveness of your application.
In the example below, we are going to create a REST web service and use @Cacheable annotation to cache some of the get request data. We will also be using Cache Manager to expire the cached data after a specified time period.
1. Go to Spring initializr website and create a Java Maven Project with dependencies: H2 and Web. Download the project zip and extract to a folder.
2. Import the project into IDE (I am using IntelliJ Idea). Add guava dependency in the Pom.xml to use Guava cache manager in the application:
<?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.7.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2. Create Student class in the com.example.demo.model package.
package com.example.demo.model;
import javax.persistence.*;
@Entity
@Table
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int student_id;
private String name;
private String grade;
public Student(){
}
public Student( String name, String grade) {
this.name = name;
this.grade = grade;
}
public int getStudent_id() {
return student_id;
}
public void setStudent_id(int student_id) {
this.student_id = student_id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGrade() {
return grade;
}
public void setGrade(String grade) {
this.grade = grade;
}
}
3. Create StudentRepo class extending CrudRepository. CrudRepository will provide the Create, Read, Update and Delete methods.
package com.example.demo.dao;
import com.example.demo.model.Student;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface StudentRepo extends CrudRepository<Student, Integer> {
}
4. Create controller class StudentController.
- @RestController is combination of @Controller and @ResponseBody. @Controller means this class will handle the requests. @ResponseBody means that object returned is automatically serialized into JSON.
- @GetMapping, @PostMapping, @DeleteMapping, @PutMapping are to map the handler methods. Handler methods call the repo class for database updates.
We are also going to use an intermediate class RepoCache only for caching. @Cacheable annotation caches the method data only if method is called from outside the class. So we will put the cache annotations on the methods of RepoCache class and call those methods from StudentCotroller class methods.
StudentController class:
package com.example.demo.controller;
import com.example.demo.dao.RepoCache;
import com.example.demo.dao.StudentRepo;
import com.example.demo.model.Student;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Optional;
@RestController
public class StudentController {
@Autowired
private RepoCache repoC;
@GetMapping("/students")
private List<Student> getAllStudents(){
return repoC.getAllStudents();
}
@GetMapping("/student/{id}")
private Optional<Student> getStudents(@PathVariable int id){
return repoC.getStudents(id);
}
@PostMapping("/students")
private Student addStudent(@RequestBody Student newStudent){
return repoC.addStudent(newStudent);
}
@DeleteMapping("/students/{id}")
private void removeStudent(@PathVariable int id){
repoC.removeStudent(id);
}
@PutMapping("/students/{id}")
private Student updateStudent(@RequestBody Student newStudent, @PathVariable int id){
return repoC.updateStudent(newStudent, id);
}
}
RepoCache class:
package com.example.demo.dao;
import com.example.demo.model.Student;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Optional;
@Component
public class RepoCache {
@Autowired
private StudentRepo repo;
@Cacheable("AllStudents")
public List<Student> getAllStudents(){
System.out.println("Calling Repo - getAllStudents");
return (List<Student>) repo.findAll();
}
@Cacheable(value = "StudentInfo", key = "#id")
public Optional<Student> getStudents(@PathVariable int id){
System.out.println("Calling Repo - getStudents");
return repo.findById(id);
}
public Student addStudent(@RequestBody Student newStudent){
return repo.save(newStudent);
}
public void removeStudent(@PathVariable int id){
repo.deleteById(id);
}
public Student updateStudent(@RequestBody Student newStudent, @PathVariable int id){
return repo.findById(id)
.map(student -> {
student.setName(newStudent.getName());
student.setGrade(newStudent.getGrade());
return repo.save(student);
})
.orElseGet(() -> {
newStudent.setStudent_id(Math.toIntExact(id));
return repo.save(newStudent);
});
}
}
5. Create CacheConfig class in the com.example.demo.config package. "StudentInfo" and "AllStudents" cache are going to be managed by cache manager. Cache expiry has been set as 10 seconds.
package com.example.demo.config;
import com.google.common.cache.CacheBuilder;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
@Configuration
public class CacheConfig extends CachingConfigurerSupport {
@Bean
@Override
public CacheManager cacheManager() {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager() {
@Override
protected Cache createConcurrentMapCache(final String name) {
return new ConcurrentMapCache(name, CacheBuilder.newBuilder().expireAfterWrite(10, TimeUnit.SECONDS)
.maximumSize(100).build().asMap(), false);
}
};
cacheManager.setCacheNames(Arrays.asList("StudentInfo", "AllStudents"));
return cacheManager;
}
}
6. Add below line in the application.properties file. This will make JPA queries appear in the log
spring.jpa.show-sql=true
7. Run the Spring Boot application and test using web service client (such as Postman). After the get call to /students or /students/{id} the data gets cached for 10 seconds.
"C:\Program Files\Java\jdk-11.0.15\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2021.2\lib\idea_rt.jar=50658:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2021.2\bin" -Dfile.encoding=UTF-8 -classpath C:\Users\mail2\Downloads\spring-boot-data-cache\target\classes;D:\.m2\repository\org\springframework\boot\spring-boot-starter-data-jpa\2.7.3\spring-boot-starter-data-jpa-2.7.3.jar;D:\.m2\repository\org\springframework\boot\spring-boot-starter-aop\2.7.3\spring-boot-starter-aop-2.7.3.jar;D:\.m2\repository\org\springframework\spring-aop\5.3.22\spring-aop-5.3.22.jar;D:\.m2\repository\org\aspectj\aspectjweaver\1.9.7\aspectjweaver-1.9.7.jar;D:\.m2\repository\org\springframework\boot\spring-boot-starter-jdbc\2.7.3\spring-boot-starter-jdbc-2.7.3.jar;D:\.m2\repository\com\zaxxer\HikariCP\4.0.3\HikariCP-4.0.3.jar;D:\.m2\repository\org\springframework\spring-jdbc\5.3.22\spring-jdbc-5.3.22.jar;D:\.m2\repository\jakarta\transaction\jakarta.transaction-api\1.3.3\jakarta.transaction-api-1.3.3.jar;D:\.m2\repository\jakarta\persistence\jakarta.persistence-api\2.2.3\jakarta.persistence-api-2.2.3.jar;D:\.m2\repository\org\hibernate\hibernate-core\5.6.10.Final\hibernate-core-5.6.10.Final.jar;D:\.m2\repository\org\jboss\logging\jboss-logging\3.4.3.Final\jboss-logging-3.4.3.Final.jar;D:\.m2\repository\net\bytebuddy\byte-buddy\1.12.13\byte-buddy-1.12.13.jar;D:\.m2\repository\antlr\antlr\2.7.7\antlr-2.7.7.jar;D:\.m2\repository\org\jboss\jandex\2.4.2.Final\jandex-2.4.2.Final.jar;D:\.m2\repository\com\fasterxml\classmate\1.5.1\classmate-1.5.1.jar;D:\.m2\repository\org\hibernate\common\hibernate-commons-annotations\5.1.2.Final\hibernate-commons-annotations-5.1.2.Final.jar;D:\.m2\repository\org\glassfish\jaxb\jaxb-runtime\2.3.6\jaxb-runtime-2.3.6.jar;D:\.m2\repository\org\glassfish\jaxb\txw2\2.3.6\txw2-2.3.6.jar;D:\.m2\repository\com\sun\istack\istack-commons-runtime\3.0.12\istack-commons-runtime-3.0.12.jar;D:\.m2\repository\com\sun\activation\jakarta.activation\1.2.2\jakarta.activation-1.2.2.jar;D:\.m2\repository\org\springframework\data\spring-data-jpa\2.7.2\spring-data-jpa-2.7.2.jar;D:\.m2\repository\org\springframework\data\spring-data-commons\2.7.2\spring-data-commons-2.7.2.jar;D:\.m2\repository\org\springframework\spring-orm\5.3.22\spring-orm-5.3.22.jar;D:\.m2\repository\org\springframework\spring-context\5.3.22\spring-context-5.3.22.jar;D:\.m2\repository\org\springframework\spring-tx\5.3.22\spring-tx-5.3.22.jar;D:\.m2\repository\org\springframework\spring-beans\5.3.22\spring-beans-5.3.22.jar;D:\.m2\repository\org\slf4j\slf4j-api\1.7.36\slf4j-api-1.7.36.jar;D:\.m2\repository\org\springframework\spring-aspects\5.3.22\spring-aspects-5.3.22.jar;D:\.m2\repository\com\h2database\h2\2.1.214\h2-2.1.214.jar;D:\.m2\repository\org\springdoc\springdoc-openapi-ui\1.6.4\springdoc-openapi-ui-1.6.4.jar;D:\.m2\repository\org\springdoc\springdoc-openapi-webmvc-core\1.6.4\springdoc-openapi-webmvc-core-1.6.4.jar;D:\.m2\repository\org\springdoc\springdoc-openapi-common\1.6.4\springdoc-openapi-common-1.6.4.jar;D:\.m2\repository\io\swagger\core\v3\swagger-core\2.1.12\swagger-core-2.1.12.jar;D:\.m2\repository\org\apache\commons\commons-lang3\3.12.0\commons-lang3-3.12.0.jar;D:\.m2\repository\com\fasterxml\jackson\dataformat\jackson-dataformat-yaml\2.13.3\jackson-dataformat-yaml-2.13.3.jar;D:\.m2\repository\io\swagger\core\v3\swagger-annotations\2.1.12\swagger-annotations-2.1.12.jar;D:\.m2\repository\io\swagger\core\v3\swagger-models\2.1.12\swagger-models-2.1.12.jar;D:\.m2\repository\jakarta\validation\jakarta.validation-api\2.0.2\jakarta.validation-api-2.0.2.jar;D:\.m2\repository\org\webjars\swagger-ui\4.1.3\swagger-ui-4.1.3.jar;D:\.m2\repository\org\webjars\webjars-locator-core\0.50\webjars-locator-core-0.50.jar;D:\.m2\repository\com\fasterxml\jackson\core\jackson-core\2.13.3\jackson-core-2.13.3.jar;D:\.m2\repository\io\github\classgraph\classgraph\4.8.138\classgraph-4.8.138.jar;D:\.m2\repository\org\springframework\boot\spring-boot-starter\2.7.3\spring-boot-starter-2.7.3.jar;D:\.m2\repository\org\springframework\boot\spring-boot\2.7.3\spring-boot-2.7.3.jar;D:\.m2\repository\org\springframework\boot\spring-boot-autoconfigure\2.7.3\spring-boot-autoconfigure-2.7.3.jar;D:\.m2\repository\org\springframework\boot\spring-boot-starter-logging\2.7.3\spring-boot-starter-logging-2.7.3.jar;D:\.m2\repository\ch\qos\logback\logback-classic\1.2.11\logback-classic-1.2.11.jar;D:\.m2\repository\ch\qos\logback\logback-core\1.2.11\logback-core-1.2.11.jar;D:\.m2\repository\org\apache\logging\log4j\log4j-to-slf4j\2.17.2\log4j-to-slf4j-2.17.2.jar;D:\.m2\repository\org\apache\logging\log4j\log4j-api\2.17.2\log4j-api-2.17.2.jar;D:\.m2\repository\org\slf4j\jul-to-slf4j\1.7.36\jul-to-slf4j-1.7.36.jar;D:\.m2\repository\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;D:\.m2\repository\org\yaml\snakeyaml\1.30\snakeyaml-1.30.jar;D:\.m2\repository\jakarta\xml\bind\jakarta.xml.bind-api\2.3.3\jakarta.xml.bind-api-2.3.3.jar;D:\.m2\repository\jakarta\activation\jakarta.activation-api\1.2.2\jakarta.activation-api-1.2.2.jar;D:\.m2\repository\org\springframework\spring-core\5.3.22\spring-core-5.3.22.jar;D:\.m2\repository\org\springframework\spring-jcl\5.3.22\spring-jcl-5.3.22.jar;D:\.m2\repository\org\springframework\boot\spring-boot-starter-web\2.7.3\spring-boot-starter-web-2.7.3.jar;D:\.m2\repository\org\springframework\boot\spring-boot-starter-json\2.7.3\spring-boot-starter-json-2.7.3.jar;D:\.m2\repository\com\fasterxml\jackson\core\jackson-databind\2.13.3\jackson-databind-2.13.3.jar;D:\.m2\repository\com\fasterxml\jackson\core\jackson-annotations\2.13.3\jackson-annotations-2.13.3.jar;D:\.m2\repository\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.13.3\jackson-datatype-jdk8-2.13.3.jar;D:\.m2\repository\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.13.3\jackson-datatype-jsr310-2.13.3.jar;D:\.m2\repository\com\fasterxml\jackson\module\jackson-module-parameter-names\2.13.3\jackson-module-parameter-names-2.13.3.jar;D:\.m2\repository\org\springframework\boot\spring-boot-starter-tomcat\2.7.3\spring-boot-starter-tomcat-2.7.3.jar;D:\.m2\repository\org\apache\tomcat\embed\tomcat-embed-core\9.0.65\tomcat-embed-core-9.0.65.jar;D:\.m2\repository\org\apache\tomcat\embed\tomcat-embed-el\9.0.65\tomcat-embed-el-9.0.65.jar;D:\.m2\repository\org\apache\tomcat\embed\tomcat-embed-websocket\9.0.65\tomcat-embed-websocket-9.0.65.jar;D:\.m2\repository\org\springframework\spring-web\5.3.22\spring-web-5.3.22.jar;D:\.m2\repository\org\springframework\spring-webmvc\5.3.22\spring-webmvc-5.3.22.jar;D:\.m2\repository\org\springframework\spring-expression\5.3.22\spring-expression-5.3.22.jar;D:\.m2\repository\com\google\guava\guava\18.0\guava-18.0.jar com.example.demo.DemoApplication . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.7.3) 2022-09-12 21:12:21.926 INFO 2080 --- [ main] com.example.demo.DemoApplication : Starting DemoApplication using Java 11.0.15 on sm15 with PID 2080 (C:\Users\mail2\Downloads\spring-boot-data-cache\target\classes started by mail2 in C:\Users\mail2\Downloads\spring-boot-data-cache) 2022-09-12 21:12:21.926 INFO 2080 --- [ main] com.example.demo.DemoApplication : No active profile set, falling back to 1 default profile: "default" 2022-09-12 21:12:23.054 INFO 2080 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode. 2022-09-12 21:12:23.132 INFO 2080 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 56 ms. Found 1 JPA repository interfaces. 2022-09-12 21:12:24.029 INFO 2080 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http) 2022-09-12 21:12:24.029 INFO 2080 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2022-09-12 21:12:24.029 INFO 2080 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.65] 2022-09-12 21:12:24.154 INFO 2080 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2022-09-12 21:12:24.154 INFO 2080 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2150 ms 2022-09-12 21:12:24.357 INFO 2080 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting... 2022-09-12 21:12:24.592 INFO 2080 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed. 2022-09-12 21:12:24.670 INFO 2080 --- [ main] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default] 2022-09-12 21:12:24.732 INFO 2080 --- [ main] org.hibernate.Version : HHH000412: Hibernate ORM core version 5.6.10.Final 2022-09-12 21:12:24.920 INFO 2080 --- [ main] o.hibernate.annotations.common.Version : HCANN000001: Hibernate Commons Annotations {5.1.2.Final} 2022-09-12 21:12:25.076 INFO 2080 --- [ main] org.hibernate.dialect.Dialect : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect Hibernate: drop table if exists student CASCADE Hibernate: create table student (student_id integer generated by default as identity, grade varchar(255), name varchar(255), primary key (student_id)) 2022-09-12 21:12:25.607 INFO 2080 --- [ main] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform] 2022-09-12 21:12:25.623 INFO 2080 --- [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default' 2022-09-12 21:12:26.170 WARN 2080 --- [ main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning 2022-09-12 21:12:27.076 INFO 2080 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' 2022-09-12 21:12:27.092 INFO 2080 --- [ main] com.example.demo.DemoApplication : Started DemoApplication in 5.68 seconds (JVM running for 6.197) Hibernate: insert into student (student_id, grade, name) values (default, ?, ?) Hibernate: insert into student (student_id, grade, name) values (default, ?, ?) Hibernate: insert into student (student_id, grade, name) values (default, ?, ?) Hibernate: insert into student (student_id, grade, name) values (default, ?, ?)
Source Code:
https://github.com/NumeroUnoDeveloper/spring-boot-data-cache
Leave a Comment