Ir al contenido

Tutorial: Implementación en memoria del repositorio

Al terminar este tutorial vas a ser capaz de:

  • Usar un mapper de JSON a Java (ObjectMapper de Jackson) para cargar datos reales en memoria, en vez de escribir cada objeto a mano.
  • Explicar por qué TypeReference es necesario para deserializar un tipo genérico como List<ArtistaDTO>.
  • Adaptar un patrón de carga de datos de arranque a tu propio módulo.
  • Anotar la clase que implementa tu interfaz de repositorio con @Repository, para que Spring la registre como bean y se la pueda inyectar al controller.

Prerrequisitos: haber terminado el Tutorial: DTOs, repositorio y controller de tu API REST hasta el Paso 6 (tu controller ya compila), y tener tu propio Gist con datos reales de tu módulo.


Hay dos formas de cargar tus datos reales en memoria. La primera es transcribir el JSON de tu Gist a objetos Java hardcodeados: por cada artista, crear el objeto, sus álbumes, las canciones de cada álbum. Es un trabajo mecánico, repetitivo, y fácil de hacer mal (un campo que no coincide, un álbum en el artista equivocado).

La segunda es usar un mapper: una librería que lee un JSON y construye los objetos Java automáticamente, siempre que tengan la misma forma. Vas a usar la segunda, con ObjectMapper de la librería Jackson.

A modo de recordatorio, así es como un mapper empareja cada propiedad del JSON con el atributo del mismo nombre (lo viste con más detalle en Implementación de un API REST con Spring):

JSON

{
  "carnet": 12345,
  "nombre": "Camila Ruiz",
  "cursosAprobados": [
    { "nombre": "Bases de Datos" },
    { "nombre": "Ingeniería de Software" }
  ]
}

Java (DTOs)

@Data @NoArgsConstructor @AllArgsConstructor
public class EstudianteDTO {
    private Long carnet;
    private String nombre;
    private List<CursoDTO> cursosAprobados;
}

public class CursoDTO {
    private String nombre;
}

Haz clic en una propiedad del JSON o en un atributo de la clase Java para ver con cuál se corresponde.

✅ Con estos nombres, el mapper asigna 12345 a EstudianteDTO.carnet.


Con Spring Boot 4 (la versión que generó tu proyecto en el tutorial anterior), Jackson quedó separado en su propio starter: spring-boot-starter-web ya no lo trae por defecto. Agrega esta dependencia a tu pom.xml, dentro de <dependencies>:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jackson</artifactId>
</dependency>

Guarda el JSON de tu Gist como un recurso del proyecto: crea el archivo src/main/resources/datos.json y pega ahí el contenido completo de tu Gist, tal cual.


Con la dependencia y los datos listos, así se implementa el repositorio (si tu proyecto quedó en Spring Boot 3.x, cambia los imports de Jackson a com.fasterxml.jackson.*, ver sección 3.1):

package co.edu.uniandes.musical.artistas;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Repository;
import java.io.InputStream;
import java.util.List;
@Repository
public class InMemoryArtistaRepository implements ArtistaRepository {
private final List<ArtistaDTO> artistas;
public InMemoryArtistaRepository() {
this.artistas = cargarDatos();
}
@Override
public List<ArtistaDTO> findAll() {
return artistas;
}
private List<ArtistaDTO> cargarDatos() {
ObjectMapper objectMapper = new ObjectMapper();
try (InputStream inputStream = getClass().getResourceAsStream("/datos.json")) {
if (inputStream == null) {
throw new IllegalStateException("No se encontró el archivo /datos.json en el classpath.");
}
return objectMapper.readValue(inputStream, new TypeReference<List<ArtistaDTO>>() {});
} catch (Exception e) {
throw new IllegalStateException("Error al leer o deserializar el archivo /datos.json", e);
}
}
}

Fíjate en cada parte:

  • @Repository. Sin esta anotación, Spring no registra la clase como bean y tu controller falla al arrancar con un error de inyección de dependencias.
  • private final List<ArtistaDTO> artistas, cargado una sola vez en el constructor. No necesitas un ArrayList mutable: la lista se construye completa la primera vez y no vuelve a cambiar.
  • getClass().getResourceAsStream("/datos.json"). Lee el archivo desde el classpath, no desde una ruta de disco. El / inicial significa “desde la raíz del classpath”: todo lo que pusiste en src/main/resources/ queda ahí. Si el archivo no existe, este método devuelve null en vez de lanzar una excepción, por eso el if explícito.
  • new TypeReference<List<ArtistaDTO>>() {}. Java borra la información de tipos genéricos en tiempo de ejecución (type erasure): en runtime, List<ArtistaDTO> es solo List. TypeReference es el truco que usa Jackson para capturar el tipo genérico completo, así sabe que debe crear objetos ArtistaDTO (y, dentro de cada uno, AlbumDTO y CancionDTO, siguiendo la forma anidada de tus DTOs) en vez de devolver mapas genéricos.
  • catch (Exception e). Cualquier falla al leer o interpretar el JSON se convierte en un error de arranque explícito, en vez de dejar la lista vacía en silencio y que el error aparezca después, sin explicación, cuando el front no muestre datos.

Antes de pegar esta clase en tu proyecto, revisa:

  • ¿Cambiaste el paquete, el nombre de la clase y el DTO principal por los de tu propio módulo?
  • ¿El nombre del archivo en getResourceAsStream coincide con el que guardaste en src/main/resources/ (sección 3.2)?
  • ¿Los imports de Jackson corresponden a tu versión de Spring Boot? tools.jackson.* en Spring Boot 4 o superior, com.fasterxml.jackson.* en 3.x (sección 3.1).
  • ¿Agregaste la dependencia de Jackson al pom.xml si tu proyecto es Spring Boot 4 o superior (sección 3.1)?
  • ¿Compila?

Al final de este tutorial, ArtistaRepository ya tiene una implementación real corriendo: tu backend responde GET /artistas con tus datos, cargados en memoria a partir de tu Gist.

Vuelve al Paso 7 del tutorial anterior para conectar este backend con tu front en Angular.

Lo que viene después:

  • Release 3: vas a agregar tu primer POST /artistas, todavía Controller → Repository directo, sin Service ni reglas de negocio.
  • Release 6: vas a reemplazar esta implementación en memoria por una real con JPA, sin tocar el controlador ni la interfaz.