Ir al contenido

Tutorial: Convertir tu repository a JpaRepository y servir datos de la base

Al terminar este tutorial vas a ser capaz de:

  • Convertir la interfaz de repositorio de tu entidad principal en una JpaRepository, y eliminar la implementación en memoria.
  • Explicar por qué, al hacerlo, el controller deja de compilar.
  • Agregar ModelMapper al proyecto y usarlo para convertir entidades en DTOs dentro del controller.
  • Confirmar que GET /artistas ya no lee de una lista en memoria sino de la tabla de H2.

Prerrequisitos: haber terminado el Tutorial: Crear tu primera entidad JPA y verla en H2 (tienes BaseEntity y ArtistaEntity) y el Tutorial: Mapear una asociación OneToMany (tienes AlbumRepository y CancionRepository). También conviene haber leído la sección Acceso a la base de datos de la teoría.


Desde el tutorial del repositorio falso tu módulo tiene esta cadena:

  • ArtistaRepository, una interfaz con un único método, findAll(), que devuelve List<ArtistaDTO>.
  • InMemoryArtistaRepository, la clase que la implementa leyendo datos.json al arrancar.
  • ArtistaController, que inyecta ArtistaRepository y devuelve lo que este le entrega.

Todo viaja en forma de DTO, de principio a fin. Con JPA eso cambia: el repository ya no entrega DTOs sino entidades, porque es lo que Hibernate sabe leer y escribir en la base de datos.


Abre ArtistaRepository.java y reemplaza todo su contenido:

package co.edu.uniandes.musical.artistas;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ArtistaRepository extends JpaRepository<ArtistaEntity, Long> {
}

Fíjate en lo que cambió:

  • La interfaz ya no declara findAll(): la hereda de JpaRepository, junto con save, findById y deleteById (la tabla completa está en Acceso a la base de datos).
  • JpaRepository<ArtistaEntity, Long> declara los dos tipos que necesita: la entidad con la que trabaja (ArtistaEntity) y el tipo de su identificador (Long, el de BaseEntity.id).
  • Es una interfaz sin cuerpo y no hay que escribir ninguna clase que la implemente: Spring Data JPA genera la implementación al arrancar.

Ya no hace falta una implementación propia, porque JpaRepository la provee. Borra InMemoryArtistaRepository.java.

Deja datos.json donde está, en src/main/resources/: no lo lee ninguna clase ahora, pero en el siguiente tutorial lo vas a usar como fuente para escribir los datos iniciales.


Compila:

Ventana de terminal
./mvnw compile

Falla, y el error es el esperado:

ArtistaController.java:[29,41] incompatible types:
java.util.List<co.edu.uniandes.musical.artistas.ArtistaEntity> cannot be
converted to java.util.List<co.edu.uniandes.musical.artistas.ArtistaDTO>

El controller declara que devuelve List<ArtistaDTO>, pero artistaRepository.findAll() ahora devuelve List<ArtistaEntity>. Son dos clases distintas a propósito: la entidad describe cómo se guarda el dato, el DTO describe cómo viaja por HTTP.

Para convertir entre entidad y DTO el curso usa ModelMapper, el mismo que aparece en la teoría de implementación del API.


ModelMapper no es parte de Spring Boot, así que hay que declarar también su versión. En el pom.xml, dentro de <dependencies>:

<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>3.2.7</version>
</dependency>

Para poder inyectarlo en el controller, Spring tiene que saber cómo crearlo. Crea una carpeta nueva config/ dentro de musical/, al lado de artistas/, y en ella ModelMapperConfig.java:

package co.edu.uniandes.musical.config;
import org.modelmapper.ModelMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ModelMapperConfig {
@Bean
public ModelMapper modelMapper() {
return new ModelMapper();
}
}
  • @Configuration marca la clase como fuente de configuración de Spring.
  • @Bean hace que lo que devuelve el método quede disponible para inyectar, como los repositories.
  • Va en config/, no en tu módulo, y por eso movimos la clase principal al paquete base en el tutorial de la primera entidad: desde config/ Spring solo la encuentra si la clase principal está un nivel más arriba.

En ArtistaController.java, agrega el import y el atributo, y recíbelo por constructor junto al repository, igual que ya haces con este:

import org.modelmapper.ModelMapper;
private final ArtistaRepository artistaRepository;
private final ModelMapper modelMapper;
public ArtistaController(ArtistaRepository artistaRepository, ModelMapper modelMapper) {
this.artistaRepository = artistaRepository;
this.modelMapper = modelMapper;
}

Y reemplaza el método del GET:

@GetMapping("/artistas")
public List<ArtistaDTO> listarArtistas() {
return artistaRepository.findAll().stream()
.map(artista -> modelMapper.map(artista, ArtistaDTO.class))
.toList();
}

modelMapper.map(artista, ArtistaDTO.class) crea un ArtistaDTO y le copia los valores de los atributos de la entidad que se llaman igual (nombre, paisOrigen, etc.). Por eso importa que los nombres coincidan entre entidad y DTO: ModelMapper los empareja por nombre.

El método POST no cambia en este tutorial: sigue devolviendo el DTO que recibe, sin guardarlo. Guardar de verdad llega con el Service en el Release 7.

Compila de nuevo. Esta vez no debe haber errores:

Ventana de terminal
./mvnw compile

Arranca la aplicación:

Ventana de terminal
./mvnw spring-boot:run

Abre http://localhost:8080/artistas en el navegador. Verás:

[]

Una lista vacía es el resultado correcto: la base de datos H2 se crea vacía al arrancar (recuerda que con ddl-auto=create-drop y jdbc:h2:mem: no hay nada guardado de la ejecución anterior), y GET /artistas ahora lee de ella en vez de leer datos.json.

Para comprobar que la lectura es de la base, abre la consola de H2 (como en el primer tutorial de esta serie), inserta una fila y vuelve a pedir /artistas:

INSERT INTO ARTISTA_ENTITY (NOMBRE, PAIS_ORIGEN) VALUES ('Marc Anthony', 'Estados Unidos');

Esta vez la respuesta incluye a Marc Anthony:

[{"id":1,"nombre":"Marc Anthony","fotoArtistaUrl":null,"paisOrigen":"Estados Unidos","biografia":null,"fechaNacimiento":null,"albumes":null}]

Fíjate en dos detalles de esa respuesta:

  • Los campos que no insertaste vienen en null. Es lo esperado: la fila solo tenía nombre y país.
  • albumes también es null. ArtistaEntity todavía no tiene una relación con álbumes, así que ModelMapper no tiene de dónde copiar esa lista. Mapear la relación entre artistas y álbumes es el siguiente tutorial de esta serie.

Detén la aplicación con Ctrl+C.


Al final de este tutorial tienes:

  • ArtistaRepository como una interfaz JpaRepository<ArtistaEntity, Long>, sin implementación propia, y InMemoryArtistaRepository eliminado.
  • ModelMapper en el proyecto, como bean de Spring, y el GET /artistas convirtiendo entidades en DTOs.
  • La confirmación de que el GET lee de la tabla de H2.

Lo que viene después, siguiendo el orden de Release 6: