¿Qué es una caché?
“Una caché es una capa de almacenamiento de datos de alta velocidad que almacena un subconjunto de datos, normalmente transitorios, de modo que las solicitudes futuras de dichos datos se atienden con mayor rapidez que si se debe acceder a los datos desde la ubicación de almacenamiento principal. El almacenamiento en caché permite reutilizar de forma eficaz los datos recuperados o procesados anteriormente.” (https://aws.amazon.com/es/caching/)
¿Cuándo utilizar una caché?
Puede ser beneficioso para el rendimiento de nuestros sistemas implementar una caché en situaciones en las que detectemos que nuestro sistema se vuelve más lento debido a:
- Accesos a datos muy frecuentes (tablas maestras, por ejemplo).
- Cálculos complejos que se repiten.
- Solicitudes a determinadas APIs.
Implementando una caché en Spring Boot
En este ejemplo usaremos Gradle como gestor de dependencias.
En primer lugar, debemos incluir la siguiente dependencia correspondiente en nuestro proyecto.
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-cache'
Posteriormente, con la etiqueta @EnableCaching en la clase de inicio o principal del proyecto, habilitaremos nuestra aplicación para usar el almacenamiento en caché.
@SpringBootApplication
@EnableCaching
public class MyCacheableApplication {
public static void main(String[] args) {
SpringApplication.run(MyCacheableApplication.class, args);
}
}
@Cacheable
Para activar el cacheo en un método, usaremos la etiqueta @Cacheable con el nombre de la caché donde se van a almacenar los resultados de las invocaciones a dicho método.
Vamos a simular un método que ocupa 5 segundos aproximadamente en cada ejecución, y que retorna un objeto de tipo Resource.
@Cacheable("resources")
public Resource getResource(Integer resourceId) {
Resource resource = new Resource();
resource.setId(resourceId);
try {
Thread.sleep(Long.parseLong("5000"));
} catch (InterruptedException e) {
e.printStackTrace();
}
return resource;
}
Podemos ver como para un valor de entrada determinado (54), la primera llamada dura 5 segundos pero las sucesivas son inmediatas, ya que se ha almacenado en caché el resultado del método.
2022-06-27 17:07:16.773 INFO… : INIT: getResource with ID: 54
2022-06-27 17:07:21.786 INFO… : END: getResource with ID: 54
2022-06-27 17:07:24.271 INFO… : INIT: getResource with ID: 54
2022-06-27 17:07:24.273 INFO… : END: getResource with ID: 54
2022-06-27 17:07:49.201 INFO… : INIT: getResource with ID: 54
2022-06-27 17:07:49.202 INFO… : END: getResource with ID: 54
Ejecutando con otro valor de entrada, la primera llamada vuelve a ocupar 5 seg pero las sucesivas vuelven a ser inmediatas.
2022-06-27 17:07:54.481 INFO… : INIT: getResource with ID: 55
2022-06-27 17:07:59.483 INFO… : END: getResource with ID: 55
2022-06-27 17:08:00.842 INFO… : INIT: getResource with ID: 55
2022-06-27 17:08:00.843 INFO… : END: getResource with ID: 55
@CacheEvict
Evidentemente, el espacio en memoria es limitado, y las cachés pueden crecer muy rápido. Utilizaremos la anotación @CacheEvict para eliminar la caché indicada (en este caso “resources”)
@CacheEvict("resources")
public Resource save(Resource resource) {
//....
}
@CachePut
Actualiza el contenido de la caché, de igual forma que @Cacheable, pero además invocará la llamada del método cada vez. El método siempre se ejecutará y el resultado será cacheado.
@CachePut("resources")
public Resource save(Resource resource) {
//....
}
Este artículo práctico ha pretendido ser un ejemplo básico sobre el uso de la caché por defecto de Spring, que utiliza un ConcurrentHashMap en memoria.
Además, Spring Boot incluye soporte para otras implementaciones de proveedores de caché como Redis, EhCache, Caffeine, … (https://docs.spring.io/spring-boot/docs/current/reference/html/io.html#io.caching)