Careeroza
Cache Annotations
Enable Caching
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>@SpringBootApplication
@EnableCaching
public class ShopEaseApplication { ... }Without any provider library Spring Boot uses a simple ConcurrentHashMap cache (no TTL), fine for learning.
@Cacheable, @CachePut, @CacheEvict
@Service
@RequiredArgsConstructor
public class ProductService {
private final ProductRepository repo;
@Cacheable(cacheNames = "products", key = "#id")
public ProductDto findById(Long id) {
log.info("Loading product {} from DB", id);
return repo.findById(id).map(ProductDto::from)
.orElseThrow(() -> new ProductNotFoundException(id));
}
@Cacheable(cacheNames = "productsByCategory", key = "#category", unless = "#result.isEmpty()")
public List<ProductDto> findByCategory(String category) { ... }
@CachePut(cacheNames = "products", key = "#result.id()") // always runs, refreshes the entry
@CacheEvict(cacheNames = "productsByCategory", allEntries = true)
public ProductDto update(Long id, UpdateProductRequest req) { ... }
@CacheEvict(cacheNames = "products", key = "#id")
public void delete(Long id) { repo.deleteById(id); }
}Hit and Miss in the Logs
Example
Call the same product twice:
curl -s localhost:8080/api/products/3
curl -s localhost:8080/api/products/3Example output
INFO ProductService : Loading product 3 from DB <- first call (miss)
{"id":3,"name":"Pixel 9","price":54999.00,"stock":12,"category":"mobiles"}
{"id":3,"name":"Pixel 9","price":54999.00,"stock":12,"category":"mobiles"} <- second call, no DB log (hit)| Attribute | Use |
|---|---|
key | SpEL for the key: #id, #req.sku, #root.methodName |
condition | cache only if true before the call: condition = "#id > 0" |
unless | skip caching based on the result: unless = "#result == null" |
sync = true | only one thread loads a missing key (prevents a stampede) |
Note: Cached return values should be immutable DTOs (records), not JPA entities: entities hold lazy proxies and session state.
Practice
Exercise: Cache getCartTotal(Long userId) in cache cartTotals, and evict that user's entry whenever addToCart(Long userId, AddItemRequest req) runs.
Show answer
@Cacheable(cacheNames = "cartTotals", key = "#userId")
public BigDecimal getCartTotal(Long userId) { ... }
@CacheEvict(cacheNames = "cartTotals", key = "#userId")
public CartDto addToCart(Long userId, AddItemRequest req) { ... }