Tutorial: Convertir tu repository a JpaRepository y servir datos de la base
1. Objetivos de aprendizaje
Sección titulada «1. Objetivos de aprendizaje»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
ModelMapperal proyecto y usarlo para convertir entidades en DTOs dentro del controller. - Confirmar que
GET /artistasya 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.
2. De dónde partes
Sección titulada «2. De dónde partes»Desde el tutorial del repositorio falso tu módulo tiene esta cadena:
ArtistaRepository, una interfaz con un único método,findAll(), que devuelveList<ArtistaDTO>.InMemoryArtistaRepository, la clase que la implementa leyendodatos.jsonal arrancar.ArtistaController, que inyectaArtistaRepositoryy 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.
3. Cambiar la interfaz a JpaRepository
Sección titulada «3. Cambiar la interfaz a JpaRepository»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 deJpaRepository, junto consave,findByIdydeleteById(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 deBaseEntity.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.
4. Borrar InMemoryArtistaRepository
Sección titulada «4. Borrar InMemoryArtistaRepository»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.
5. Ver el error de compilación
Sección titulada «5. Ver el error de compilación»Compila:
./mvnw compileFalla, y el error es el esperado:
ArtistaController.java:[29,41] incompatible types:java.util.List<co.edu.uniandes.musical.artistas.ArtistaEntity> cannot beconverted 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.
6. Agregar ModelMapper
Sección titulada «6. Agregar ModelMapper»6.1 La dependencia
Sección titulada «6.1 La dependencia»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>6.2 Un bean de ModelMapper
Sección titulada «6.2 Un bean de ModelMapper»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;
@Configurationpublic class ModelMapperConfig {
@Bean public ModelMapper modelMapper() { return new ModelMapper(); }}@Configurationmarca la clase como fuente de configuración de Spring.@Beanhace 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: desdeconfig/Spring solo la encuentra si la clase principal está un nivel más arriba.
7. Usar ModelMapper en el controller
Sección titulada «7. Usar ModelMapper en el controller»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:
./mvnw compile8. Arrancar y verificar
Sección titulada «8. Arrancar y verificar»Arranca la aplicación:
./mvnw spring-boot:runAbre 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. albumestambién esnull.ArtistaEntitytodavía no tiene una relación con álbumes, así queModelMapperno 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.
¿Dónde quedaste?
Sección titulada «¿Dónde quedaste?»Al final de este tutorial tienes:
ArtistaRepositorycomo una interfazJpaRepository<ArtistaEntity, Long>, sin implementación propia, yInMemoryArtistaRepositoryeliminado.ModelMapperen el proyecto, como bean de Spring, y elGET /artistasconvirtiendo entidades en DTOs.- La confirmación de que el
GETlee de la tabla de H2.
Lo que viene después, siguiendo el orden de Release 6:
- Mapear una asociación ManyToMany, para relacionar artistas y álbumes.
- Cargar datos iniciales con
data.sql, para que la aplicación no arranque vacía. - Repetir este mismo cambio con las demás entidades de tu módulo: la interfaz
<Entidad>Repositorypasa a extenderJpaRepository.