1. Introducción

En este post veremos como configurar una caché para SpringBoot utilizando la implementación de ehcache.
Una caché es una memoria de almacenamiento donde se guardan temporalmente datos que podrán ser rescatados con posterioridad. La idea consiste en que podamos almacenar el resultado de ciertos servicios en dicha memoria para que en futuras invocaciones los datos se obtenga de aquí y no de la invocación del servicio.
EhCache es una de las implementaciones más conocidas de caché. Para configurarlo necesitaremos

  • Añadir las dependencias correspondientes
  • Crear clase de configuración y fichero ehcache.xml
  • Utilización de la anotación @Cacheable en nuestros servicios

Puedes decargarte el ejemplo de mi GitHub.

Tecnologías empleadas:

  • Java 8
  • Gradle 3.1
  • SpringBoot 1.5.2.RELEASE
  • Spring 4.3.7.RELEASE
  • EhCache 2.10.2.2.21

2. Estructura del proyecto

3. Requisitos previos

El proyecto ha sido montado sobre gradle, así que si no lo tienes instalado deberás.

  1. Descargar gradle de la web oficial
  2. Descomprimir los binarios en un directorio dentro de tu filesystem
  3. Importar el directorio /bin del gradle a la variable de entorno PATH

Para verificar que hemos configurado gradle correctamente deberemos hacer

gradle -v

4. Ficheros

A continuación se muestra el fichero build.gradle. Necesitaremos incluir las dependencias:

  • spring-boot-starter-web para levantar nuestro microservicio
  • spring-boot-starter-cache para la gestión de caches por parte de Spring
  • ehcache para cargar la implementación de caché de ehcache
group 'com.jorgehernandezramirez.spring.cache'
version '1.0-SNAPSHOT'

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.2.RELEASE")
    }
}

apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
apply plugin: 'maven'

sourceCompatibility = 1.8

springBoot {
    mainClass = "com.jorgehernandezramirez.spring.cache.ehcache"
}

repositories {
    mavenCentral()
}

dependencies {
    compile("org.springframework.boot:spring-boot-starter-web")
    compile("org.springframework.boot:spring-boot-starter-cache")
    compile group: 'net.sf.ehcache', name: 'ehcache', version: ehcache_version
}

Nuestra main para levantar SpringBoot

package com.jorgehernandezramirez.spring.cache.ehcache;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

    public Application(){
        //For Spring
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Basta con utilizar la anotación @EnableCaching para habilitar la gestión de las cachés en todos los bean de Spring. Así de fácil.

package com.jorgehernandezramirez.spring.cache.ehcache.configuration;

import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableCaching
public class CacheConfiguration {

    public CacheConfiguration() {
        //Para Spring
    }
}

A continuación nos creamos un servicio de ejemplo. En la clase UserDummyServiceImpl es en donde hacemos uso de la caché a través de la anotación @Cacheable

package com.jorgehernandezramirez.spring.cache.ehcache.service.dto;

public class UserDto {

    private String id;

    private String name;

    private String surname;

    public UserDto(){
        super();
    }

    public UserDto(String id, String name, String surname) {
        this.id = id;
        this.name = name;
        this.surname = surname;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSurname() {
        return surname;
    }

    public void setSurname(String surname) {
        this.surname = surname;
    }

    @Override
    public String toString() {
        return "UserDto{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", surname='" + surname + '\'' +
                '}';
    }
}
package com.jorgehernandezramirez.spring.cache.ehcache.service.api;

import com.jorgehernandezramirez.spring.cache.ehcache.service.dto.UserDto;

import java.util.List;

/**
 * Api de Usuarios. Se definen los métodos para la gestión de usuarios
 */
public interface IUserService {

    /**
     * Método que devuelve la lista de todos los usuarios del sistema
     * @return
     */
    List<UserDto> getUsers();
}
package com.jorgehernandezramirez.spring.cache.ehcache.service.impl;

import com.jorgehernandezramirez.spring.cache.ehcache.service.api.IUserService;
import com.jorgehernandezramirez.spring.cache.ehcache.service.dto.UserDto;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.List;

@Service
public class UserDummyService implements IUserService{

    private static final Logger LOGGER = LoggerFactory.getLogger(UserDummyService.class);

    public UserDummyService(){
        //Para Spring
    }

    @Override
    @Cacheable(value = "userCache")
    public List<UserDto> getUsers() {
        LOGGER.info("Obteniendo todos los usuarios del sistema");
        return Arrays.asList(new UserDto("1", "jorge", "hernandez"),
                new UserDto("2", "jose", "hernandez"));
    }
}

Habrás visto que hacemos uso de la caché userCache. Ésta debe ser definida en el fichero de configuración de ehcache (ehcache.xml) que se debe encontrar en la raíz de nuestro classpath. Los atributos que se pueden configurar para cada caché se pueden consultar en la web de EhCache. En nuestro caso definimos los siguientes atributos

  • name. Nombre de la caché
  • maxElementsInMemory. Define el número máximo de elementos en la caché
  • memoryStoreEvictionPolicy. Define la política de reemplazo cuando no hay más espacio en la caché
  • timeToLiveSeconds. Define el tiempo de vida de los elementos en la caché
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="true" monitoring="autodetect" dynamicConfig="true">
    <cache name="userCache"
           maxElementsInMemory="500"
           memoryStoreEvictionPolicy="LFU"
           timeToLiveSeconds="1440" />
</ehcache>

A continuación se muestra un controlador para poder borrar las cachés de forma manual.

   
package com.jorgehernandezramirez.spring.cache.ehcache.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/cache")
public class CacheController {

    @Autowired
    private CacheManager cacheManager;

    @RequestMapping(method = RequestMethod.DELETE)
    public ResponseEntity clearAllCaches() {
        cacheManager.getCacheNames().forEach(cacheName -> {
            clearCacheFromCacheName(cacheName);
        });
        return new ResponseEntity(HttpStatus.OK);
    }

    @RequestMapping(value = "/{cacheName}", method = RequestMethod.DELETE)
    public ResponseEntity clearCache(@PathVariable("cacheName") String cacheName) {
        return clearCacheFromCacheName(cacheName) ? new ResponseEntity(HttpStatus.OK) : new ResponseEntity(HttpStatus.NOT_FOUND);
    }

    private Boolean clearCacheFromCacheName(final String cacheName) {
        final Cache cache = cacheManager.getCache(cacheName);
        if (cacheExists(cache)) {
            cache.clear();
            return true;
        }
        return false;
    }

    private Boolean cacheExists(final Cache cache) {
        return cache != null;
    }
}                                         

Definimos un controlador de pruebas para realizar las invocaciones sobre nuestro servicio de usuarios.

package com.jorgehernandezramirez.spring.cache.ehcache.controller;

import com.jorgehernandezramirez.spring.cache.ehcache.service.api.IUserService;
import com.jorgehernandezramirez.spring.cache.ehcache.service.dto.UserDto;
import com.jorgehernandezramirez.spring.cache.ehcache.service.impl.UserDummyService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping(value = "/user")
public class UserController {

    private static final Logger LOGGER = LoggerFactory.getLogger(UserController.class);

    @Autowired
    private IUserService userService;

    public UserController(){
        //Para Spring
    }

    @RequestMapping
    public List<UserDto> getUsers(){
        LOGGER.info("Llamando al servicio de usuarios para obtener todos los usuarios del sistema.");
        return userService.getUsers();
    }
}                                        

Probando la aplicación. Vamos a http://localhost:8080/user en varias ocasiones y vemos en consola que se realiza una única invocación.


5. Conclusiones

El uso de las cachés es una de las herramientas más comunes que disponemos para mejorar el rendimiento de nuestra aplicación. Es importante identificar y cachear aquellos elementos que son inmutables en nuestro sistema para no ir constantemente a nuestro sistema de datos a buscar la información.