Getting started with Spring Boot Caching
Caching is a mechanism used to enhance the performance of a system. It acts as a temporary memory that between the application and the persistent database. <!--more--> Cache memory stores only the recently used data items. This helps to reduce the number of database hits as much as possible.
Data access from in-memory is always faster than fetching from an external storages like databases, file systems, or other service calls.
We can set up a cache in a Spring Boot application using technologies like Hazelcast, Ehcache, or Redis. They are cache providers, since they provide a key-data to store the cached data.
In this article, we will learn how to implement a cache in a Spring Boot REST application using Ehcache as the cache provider. We are going to build a Todo
REST APIs then add cache capabilities to the REST endpoints.
Table of contents
- Prerequisites
- Project setup
- Domain layer
- Repository layer
- Service layer
- Controller layer
- Configuration layer
- Adding data into the Cache
- Updating data in the cache
- Deleting data from the cache
- Conclusion
Prerequisites
- JDK installed on your computer.
- Knowledge of Spring Boot.
Project setup
We are going to use Spring Initializr to bootstrap our application.
- Visit Spring Initializr, and enter the project name as
SpringBootCaching
. - Add
Spring Web
,H2 database
,Spring data JPA
, andLombok
as project dependencies. - Click on the
Generate Project
button to download the project boilerplate code as azip
file. - Extract the
zip
file and open the uncompressed files in your favorite IDE. - In the
pom.xml
file in the dependencies section, add a few more dependencies as shown below:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
</dependency>
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
</dependencies>
The above dependencies add spring-boot-starter-cache
, an abstraction layer implemented by Spring for cache manipulation and Ehcache
as the cache provider that we will use in our application.
Domain layer
- Create a package named
domain
in the root folder. - Create a Java file named
Todo.java
and add the code below:
@Entity // Makes this class to be a JPA entity
@Getter // Creates setters for all the fields in the class
@Setter // Creates getters for all the fields in the class
@Builder // Creates a builder pattern for this class
@NoArgsConstructor // Creates a constructor with no arguements for this class
@AllArgsConstructor // Creates a constructor with all arguements for this class
class Todo {
@Id // Marks the Id as the primary key
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id", nullable = false)
private Long id;
@Column(name = "title", nullable = false)
private String title;
@Column(name = "description", nullable = false)
private String description;
}
In the above code snippet, we create a Todo
model that will hold an instance of a todo
in the database.
Repository layer
- Create a new package named
repository
in the root project package. - Create a new Java interface named
TodoRepository
in therepository
package created above. Add the code snippet below:
public interface TodoRepository extends JpaRepository<Todo, Long> {
}
In the code snippet above, we create a TodoRepository
interface that extends the JpaRepository
interface with all the methods required to perform basic CRUD database operations.
Service layer
- Create a new package named
service
in the root project directory. - In the
service
package created above, create a new Java interface namedTodoService.java
.
Add the below code snippet to the TodoService
interface:
// Interface that will be implemented to provide database operations
public interface TodoService {
List<Todo> getAllTodos();
Todo getTodoById(Long id);
Todo updateTodo(Todo todo, Long id);
void createTodo(Todo todo);
void deletedTodo(Long id);
}
In the service
package, create a new Java file named TodoServiceImpl.java
and add the code snippet below:
@Service
@AllArgsConstructor
public class TodoServiceImpl implements TodoService {
private final TodoRepository todoRepository;
// Gets a list of todos from the database
@Override
public List<Todo> getAllTodos() {
return todoRepository.findAll();
}
// Gets a todo with the specified Id from the database
@Override
public Todo getTodoById(Long id) {
return todoRepository.getById(id);
}
// Updates todo with the specified Id
@Override
public Todo updateTodo(Todo todo, Long id) {
Todo newTodo = Todo
.builder()
.id(id)
.description(todo.getDescription())
.title(todo.getTitle())
.build();
todoRepository.save(newTodo);
return newTodo;
}
// Creates a new todo in the database
@Override
public void createTodo(Todo todo) {
todoRepository.save(todo);
}
// Deletes a todo with the specified Id in the database
@Override
public void deletedTodo(Long id) {
todoRepository.deleteById(id);
}
}
Controller layer
- Create a new package named
controllers
in the root project package. - In the
controllers
package, create a new file namedTodoController.java
.
Add the code snippet below in the TodoController.java
file:
@RestController
@RequestMapping("/api/todos")
@AllArgsConstructor
public class TodoController {
private final TodoService todoService;
// API endpoint returning a list of Todos
@GetMapping({"", "/"})
public ResponseEntity<List<Todo>> getAllTodos() {
return new ResponseEntity<>(todoService.getAllTodos(), HttpStatus.OK);
}
// API endpoint returning a todo with the specied Id
@GetMapping("/{id}")
public ResponseEntity<Todo> getTodoById(@PathVariable Long id) {
return new ResponseEntity<>(todoService.getTodoById(id), HttpStatus.OK);
}
// API endpoint that creates a todo in the database with the request body
@PostMapping("")
public ResponseEntity<String> createTodo(Todo todo) {
todoService.createTodo(todo);
return new ResponseEntity<>("Todo Created successfully", HttpStatus.CREATED);
}
// API endpoint that allows for updating a Todo with a specified Id
@PutMapping("{id}")
public ResponseEntity<Todo> updateTodo(@PathVariable Long id, @RequestBody Todo todo) {
return new ResponseEntity<>(todoService.updateTodo(todo, id), HttpStatus.OK);
}
// API endpoint that allows users to delete Todo wit specified Id
@DeleteMapping("/{id}")
public ResponseEntity<String> deleteTodoById(@PathVariable Long id) {
todoService.deletedTodo(id);
return new ResponseEntity<>("Todo deleted successfully", HttpStatus.NO_CONTENT);
}
}
Run the application to confirm that everything is working as expected.
Configuration layer
Now that we have a fully functional CRUD REST API application, we are going to learn how to add cache support in our application.
First, we need to create a configuration class to enable caching.
- In the root project package, create a new package named
config
. - In the
config
package created above, create a new Java file namedCacheConfig.java
.
Add the below code snippet to the CacheConfig.java
file:
@EnableCaching
@Configuration
public class CacheConfig {
}
@EnableCaching
annotation enables the Spring Boot caching abstraction layer in our application.@Configuration
annotation marks theCacheConfig
class as a Spring configuration class.
Ehcache cache provider requires an independent configuration to function correctly.
In the resources folder, create a new XML file named ehcache.xml
and add the code snippet below:
<config
xmlns:jsr107='http://www.ehcache.org/v3/jsr107'
xmlns='http://www.ehcache.org/v3'>
<service>
<jsr107:defaults enable-management="true" enable-statistics="true"/>
</service>
<cache alias="todos" uses-template="config-cache"/>
<cache alias="todo" uses-template="config-cache"/>
<cache-template name="config-cache">
<expiry>
<ttl unit="minutes">5</ttl>
</expiry>
<resources>
<heap>1</heap>
<offheap unit="MB">1</offheap>
</resources>
</cache-template>
</config>
In the above code snippet, we set the time taken for the cache contents to be updated and the amount of memory to be used by the cache.
We need to inform our application of the path to the Ehcache configuration file.
To ensure the XML is added to our configuration, add the below line to application.properties
file:
spring.cache.jcache.config=classpath:ehcache.xml
CRUD operations in cache
Adding data into the cache
In the TodoServiceImpl.java
file, update the getAllTodos()
method with the code snippet below:
@Cacheable(cacheNames = "todos")
@Override
public List<Todo> getAllTodos() {
return todoRepository.findAll();
}
The above method returns a list of todos
. Here, it is annotated with @Cacheable(cacheNames = "todos")
annotation. This grabs the data returned from this method and stores them in a cache with key todos
.
Data in the cache is stored in a key-value pattern. The key is the name stored in the cacheName variable while the value is the data that is returned by the method annotated with @Cacheable annotation.
To add a single item into the cache, we need to add the id
that will be used to retrieve the item and update it in the cache.
Update the getTodoById()
method with the code snippet below:
@Cacheable(cacheNames = "todo", key = "#id")
@Override
public Todo getTodoById(Long id) {
return todoRepository.getById(id);
}
@Cacheable(cacheNames = "todo", key = "#id")
gets atodo
with the providedid
from the cache.
Note: we pass the
id
as the key, since the cache stores data in the form of key-value pair. When we pass theid
of thetodo
, we are expecting atodo
data associated with that key.
Updating data in the cache
In the TodoServiceImpl.java
file, update the updateTodo()
method with the code snippet below:
// Updates a todo with the specified Id in the cache and in the database. `#todo.id` gets the Id property from the `todo` passed to the `updateTodo` function.
@CachePut(value = "todos", key = "#todo.id")
@Override
public Todo updateTodo(Todo todo, Long id) {
Todo newTodo = Todo
.builder()
.id(id)
.description(todo.getDescription())
.title(todo.getTitle())
.build();
todoRepository.save(newTodo);
return newTodo;
}
To update data in the cache, we use the @CachePut
annotation to update the item's key.
Deleting data from the cache
In the TodoServiceImpl.java
file, update the deleteTodo()
with the code snippet below.
@CacheEvict(value = "todo", key = "#id")
@Override
public void deletedTodo(Long id) {
todoRepository.deleteById(id);
}
To delete data from the cache, we use the @CacheEvict
annotation by passing the key of the specific item to be deleted.
To delete all items from the cache, we set the attribute
allEntries
to true.
Conclusion
Based on what we have learned, caching reduces the number of reads and writes operations on the database. In the case where a paid cloud database is used like Google Cloud SQL, the billing will be based on the read and write operations on the database.
With reduced number of read and write operations, the cost of using the managed database can be reduced.
Now that you have learned how to add caching to a Spring Boot application, implement caching in other applications. You can download the complete source code here.
To learn more about Ehcache, read the documentation here.
Happy coding!
Peer Review Contributions by: Srishilesh P S