Caching
A popular post is opened thousands of times, and each time the server goes to the database for the same data. A cache is memory for ready answers: the first call of a method computes the result and puts it in the cache, later calls with the same arguments get it from there without touching the database. Spring adds caching with annotations, without changing the methods' code.
Setup
<!-- pom.xml -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>Caching is switched on by @EnableCaching — we already put it in AppConfig in the previous lesson. Without extra libraries Spring Boot keeps the cache in a plain ConcurrentHashMap in the application's memory.
Remembering a result
// src/main/java/com/example/blog/PostService.java
@Cacheable("posts")
public PostResponse findById(Long id) {
return PostResponse.from(getPost(id));
}@Cacheable("posts") means a cache named posts. The id argument is the key, the result is the value. It helps that the cached thing is the PostResponse record, not the entity: it is immutable and does not depend on a database connection.
Dropping stale values
When a post is changed or deleted, the old answer must leave the cache:
// src/main/java/com/example/blog/PostService.java
@Transactional
@CacheEvict(value = "posts", key = "#id")
public PostResponse update(Long id, NewPostRequest request) {
// ...
}
@Transactional
@CacheEvict(value = "posts", key = "#id")
public void delete(Long id) {
posts.delete(getPost(id));
}key = "#id" is an expression: take the value of the id parameter. After the method call the entry with this key leaves the cache, and the next findById goes to the database again.
Checking it
Request one post three times and look at the log with show-sql: true:
curl http://localhost:8080/api/posts/65
curl http://localhost:8080/api/posts/65
curl http://localhost:8080/api/posts/65Hibernate: select p1_0.id,p1_0.author_id,p1_0.body,p1_0.created_at,p1_0.title from posts p1_0 where p1_0.id=?
Hibernate: select u1_0.id,u1_0.password,u1_0.role,u1_0.username from users u1_0 where u1_0.id=?Three requests — and only one pair of SQL queries: the post and its author. The second and third answers came from the cache.
Cache annotations
| Annotation | What it does |
|---|---|
@Cacheable | Returns the value from the cache or calls the method and remembers the result |
@CacheEvict | Removes a value; allEntries = true clears the whole cache |
@CachePut | Always calls the method and updates the value in the cache |
An in-memory cache disappears on restart, and each application instance has its own. With several servers you add a shared cache — Redis, for example, through the spring-boot-starter-data-redis starter; the annotations stay the same.
Common mistake
Caching, like transactions, works through Spring's proxy. Calling findById from another method of the same PostService bypasses the cache — the database is hit every time. The cache only works when the method is called from outside, from another bean such as a controller. And do not forget @CacheEvict: without it, after a post is edited readers keep seeing the old title until a restart.