Implementing Declarative Caching in Spring Boot with Spring Cache and Redis
Dependency Configuration
To integrate Spring Cache with a Redis backend, include the following dependencies in your Maven configuration. Spring Boot automatically selects the cache implementation based on the available libraries on the classpath.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
Activating Caching
Enable the caching functionality by adding the @EnableCaching annotation to the main application class. This instructs Spring to look for cache-related annotations and create the necessary proxy objects.
@SpringBootApplication
@EnableCaching
public class InventoryManagementApplication {
public static void main(String[] args) {
SpringApplication.run(InventoryManagementApplication.class, args);
}
}
Core Cache Annotations and Usage
Spring Cache operates via AOP (Aspect-Oriented Programming). A proxy intercepts method calls to handle cache logic before or after the actual method execution. If a method is marked with @Transactional and the transaction rolls back, cache operations will typically be suppressed to maintain consistency.
Synchronizing Cache with Data Updates
The @CachePut annotation updates the cache with the methods' return value. By default, Redis key are structured as cacheName::keyValue.
@CachePut(cacheNames = "productCache", key = "#product.id")
@PostMapping("/products")
public Product createProduct(@RequestBody Product product) {
productRepository.save(product);
return product;
}
// Alternative SpEL expressions for the key:
// key = "#result.id" (property of the return object)
// key = "#p0.id" (property of the first argument)
// key = "#root.args[0].id" (explicit argument index)
Conditional Cache Retrieval
@Cacheable checks if the data exists in the cache first. If found, the method body is skipped, and the cached value is returned. If not found, the method executes, and the result is stored in the cache.
@GetMapping("/products/{id}")
@Cacheable(cacheNames = "productCache", key = "#id")
public Product fetchProductById(@PathVariable Long id) {
// This code only executes if there is a cache miss
return productRepository.findById(id);
}
Cache Invalidation
@CacheEvict is used to remove entries from the cache, ensuring stale data is not served after a deletion or a bulk update.
// Removes a specific entry based on the ID
@DeleteMapping("/products/{id}")
@CacheEvict(cacheNames = "productCache", key = "#id")
public void removeProduct(@PathVariable Long id) {
productRepository.deleteById(id);
}
// Removes all entries under the specified cache namespace
@DeleteMapping("/products/clear")
@CacheEvict(cacheNames = "productCache", allEntries = true)
public void clearProductCatalog() {
productRepository.deleteAll();
}