Tutorial: Cargar datos iniciales con data.sql
1. Objetivos de aprendizaje
Sección titulada «1. Objetivos de aprendizaje»Al terminar este tutorial vas a ser capaz de:
- Crear un
data.sqlque Spring Boot ejecute al arrancar y que cargue datos en tus tablas. - Explicar por qué hace falta
spring.jpa.defer-datasource-initialization=true. - Insertar filas en el orden que respeta las llaves foráneas, sin escribir los identificadores a mano.
- Llenar la tabla intermedia de una relación
@ManyToManycon subconsultas. - Reconocer el error
Value too longy la anotación que lo evita. - Verificar los datos cargados desde la consola de H2 y desde
GET /artistas.
Prerrequisitos: haber terminado el
Tutorial: Mapear una asociación ManyToMany
(tienes la tabla intermedia artista_album) y los tutoriales anteriores de
esta serie (tu GET /artistas ya lee de la base de datos), y tener a mano el
datos.json de tu módulo.
2. Qué es data.sql
Sección titulada «2. Qué es data.sql»Con jdbc:h2:mem: la base de datos vive en memoria y se pierde al detener la
aplicación; con ddl-auto=create-drop además se vuelve a crear vacía en cada
arranque. Por eso el GET /artistas del tutorial anterior respondía [].
Spring Boot tiene un mecanismo para esto: si encuentra un archivo
src/main/resources/data.sql, lo ejecuta al arrancar, sentencia por sentencia.
Es SQL normal, un INSERT por cada fila que quieras tener desde el comienzo.
3. El primer INSERT, y el primer error
Sección titulada «3. El primer INSERT, y el primer error»Crea el archivo src/main/resources/data.sql con una fila de ARTISTA_ENTITY:
INSERT INTO artista_entity (nombre, pais_origen)VALUES ('Marc Anthony', 'Estados Unidos');Arranca la aplicación:
./mvnw spring-boot:runFalla, con un error como este:
Failed to execute SQL script statement #1 of file [.../data.sql]:INSERT INTO artista_entity (nombre, pais_origen) VALUES (...)Caused by: org.h2.jdbc.JdbcSQLSyntaxErrorException:Table "ARTISTA_ENTITY" not found (this database is empty)El INSERT está bien escrito. El problema es el orden: por defecto Spring
Boot ejecuta data.sql antes de que Hibernate cree las tablas a partir de
tus entidades. Cuando el script corre, la tabla todavía no existe.
La corrección
Sección titulada «La corrección»En src/main/resources/application.properties, agrega:
spring.jpa.defer-datasource-initialization=trueEsta propiedad hace que data.sql se ejecute después de que Hibernate
cree las tablas. Arranca de nuevo: esta vez la aplicación arranca sin errores.
Pide http://localhost:8080/artistas: ahora la lista trae a Marc Anthony.
Detén la aplicación. Cuando termines de escribir el archivo completo, esta
comprobación se repite al final del tutorial.
4. Escribir los datos de verdad
Sección titulada «4. Escribir los datos de verdad»datos.json tiene los datos que usabas con el repositorio en memoria. Hay que
convertirlos a INSERT. Mira primero qué columnas existen: la forma más
confiable es descargar el esquema, como hiciste en el
primer tutorial de esta serie,
y escribir solo las columnas que existen en tus entidades. En las
entidades de esta serie, la canción no tiene genero, letra ni
fechaLanzamiento, así que esos datos del JSON no se cargan hasta que
agregues esos atributos a la entidad.
4.1 Los artistas
Sección titulada «4.1 Los artistas»ARTISTA_ENTITY no depende de ninguna otra tabla, así que va primero.
Reemplaza el contenido de data.sql:
INSERT INTO artista_entity (nombre, foto_artista_url, pais_origen, biografia, fecha_nacimiento)VALUES ('Marc Anthony', 'https://raw.githubusercontent.com/JoelNino/Artistas-Mock/main/images/MarcAnthony.jpg', 'Estados Unidos', 'Marco Antonio Muñiz, conocido como Marc Anthony, es uno de los salseros más influyentes del mundo. Nacido en Nueva York de padres puertorriqueños, conquistó el mercado latino e internacional con éxitos como ''Vivir Mi Vida'' y ''Valió la Pena''.', '1968-09-16');
INSERT INTO artista_entity (nombre, foto_artista_url, pais_origen, biografia, fecha_nacimiento)VALUES ('Datty', 'https://raw.githubusercontent.com/JoelNino/Artistas-Mock/main/images/Datty.jpg', 'República Dominicana', 'Datty es una intérprete de merengue nacida en Santo Domingo, República Dominicana. Con un estilo festivo y letras llenas de humor y picardía, se ha convertido en una de los artistas más queridas de las fiestas populares del Caribe y Latinoamérica.', '1985-03-22');Fíjate en las comillas: en SQL una comilla simple dentro de un texto se
escribe doble (''). Lo vas a necesitar en 'Pa''lla Voy'.
4.2 Los álbumes
Sección titulada «4.2 Los álbumes»ALBUM_ENTITY tampoco depende de otra tabla. Agrégalos a data.sql, con la
URL de la portada tal como viene en el JSON:
INSERT INTO album_entity (titulo, foto_portada_url, fecha_lanzamiento)VALUES ('Pa''lla Voy', 'https://raw.githubusercontent.com/JoelNino/Artistas-Mock/main/images/AlbumMarc(Pallavoy).jpg', '2023-10-12');
INSERT INTO album_entity (titulo, foto_portada_url, fecha_lanzamiento)VALUES ('La Reina Del Merengue', 'https://raw.githubusercontent.com/JoelNino/Artistas-Mock/main/images/AlbumDatty(LaReinaDelMerengue).jpg', '2020-06-15');4.3 La relación entre artistas y álbumes
Sección titulada «4.3 La relación entre artistas y álbumes»La tabla intermedia ARTISTA_ALBUM depende de las dos anteriores: cada fila
tiene la llave foránea de un artista y la de un álbum. Por eso se llena
después de insertar los artistas y los álbumes. Las dos llaves se piden a
la base de datos con subconsultas, sin escribir ningún id a mano:
INSERT INTO artista_album (artista_id, album_id)VALUES ((SELECT id FROM artista_entity WHERE nombre = 'Marc Anthony'), (SELECT id FROM album_entity WHERE titulo = 'Pa''lla Voy'));
INSERT INTO artista_album (artista_id, album_id)VALUES ((SELECT id FROM artista_entity WHERE nombre = 'Datty'), (SELECT id FROM album_entity WHERE titulo = 'La Reina Del Merengue'));Las subconsultas buscan el artista por nombre y el álbum por título, así que
funcionan mientras esos valores no se repitan en la tabla. Si tu módulo tiene
dos álbumes con el mismo título, distínguelos también por otra columna (por
ejemplo, fecha_lanzamiento).
4.4 Las canciones: el orden importa
Sección titulada «4.4 Las canciones: el orden importa»CANCION_ENTITY tiene una llave foránea hacia ALBUM_ENTITY: una canción
apunta a un álbum. Si insertas una canción antes de que exista su álbum, la
base de datos rechaza el INSERT. Por eso el orden completo del archivo es:
- Las tablas que no dependen de otra: artistas y álbumes.
- Después las que dependen de ellas: la tabla intermedia
artista_albumy las canciones.
Agrega las canciones. Cada una necesita el album_id de su álbum:
pídeselo a la base de datos con una subconsulta que busque el álbum por
título. Fíjate también en que la duración se inserta como texto, igual que
viene en el JSON ('4:06').
INSERT INTO cancion_entity (titulo, foto_portada_url, duracion, album_id)VALUES ('Pa''lla Voy', 'https://picsum.photos/seed/pallávoy/800/800', '4:06', (SELECT id FROM album_entity WHERE titulo = 'Pa''lla Voy'));
INSERT INTO cancion_entity (titulo, foto_portada_url, duracion, album_id)VALUES ('Yo Le Mentí', 'https://picsum.photos/seed/yolementí/800/800', '4:18', (SELECT id FROM album_entity WHERE titulo = 'Pa''lla Voy'));
INSERT INTO cancion_entity (titulo, foto_portada_url, duracion, album_id)VALUES ('La Reina Del Merengue', 'https://picsum.photos/seed/reinamerengue/800/800', '3:34', (SELECT id FROM album_entity WHERE titulo = 'La Reina Del Merengue'));
INSERT INTO cancion_entity (titulo, foto_portada_url, duracion, album_id)VALUES ('Baila Conmigo Luni', 'https://picsum.photos/seed/bailaconmigoluni/800/800', '3:18', (SELECT id FROM album_entity WHERE titulo = 'La Reina Del Merengue'));La subconsulta (SELECT id FROM album_entity WHERE titulo = ...) devuelve el
id real del álbum, sea cual sea.
5. Probar primero en un archivo aparte
Sección titulada «5. Probar primero en un archivo aparte»data.sql es un solo archivo del repositorio del grupo que cargan todos los
módulos, así que un INSERT equivocado lo rompe para todos. Antes de tocarlo,
escribe tus INSERT en un archivo propio (por ejemplo, artista.sql, fuera de
src/main/resources/) y córrelos a mano en la consola de H2:
- Arranca la aplicación con un
data.sqlvacío o sin tus entidades. - Abre la consola de H2 y pega tus
INSERTen el orden correcto. - Corrige lo que falle ahí, donde el error no afecta a nadie más.
Cuando tu módulo funcione, pasas las sentencias a data.sql de una vez, o las
vas agregando a medida que termina cada entidad, siempre respetando el orden
de las llaves foráneas.
6. Verificar
Sección titulada «6. Verificar»Arranca la aplicación con el data.sql completo:
./mvnw spring-boot:runPrimero desde el navegador: http://localhost:8080/artistas devuelve a los dos
artistas, y cada uno trae su álbum en albumes, con las canciones de ese
álbum anidadas y las URLs de las portadas. Es el resultado de la relación
@ManyToMany, la relación @OneToMany y ModelMapper trabajando juntos.
Después, en la consola de H2, comprueba que las llaves foráneas quedaron bien:
SELECT a.TITULO AS album, c.TITULO AS cancion, c.DURACIONFROM CANCION_ENTITY cJOIN ALBUM_ENTITY a ON c.ALBUM_ID = a.ID;Cada canción debe aparecer junto al título de su álbum, con su duración. Y para comprobar la tabla intermedia:
SELECT ar.NOMBRE AS artista, al.TITULO AS albumFROM ARTISTA_ALBUM aaJOIN ARTISTA_ENTITY ar ON aa.ARTISTA_ID = ar.IDJOIN ALBUM_ENTITY al ON aa.ALBUM_ID = al.ID;Debe devolver una fila por cada pareja artista-álbum que insertaste.
¿Dónde quedaste?
Sección titulada «¿Dónde quedaste?»Al final de este tutorial tienes:
- Un
data.sqlque carga artistas, álbumes, la tabla intermedia y canciones en el orden correcto, con las llaves foráneas resueltas por subconsulta. spring.jpa.defer-datasource-initialization=true, sin la cual el script falla.- La explicación de por qué las columnas de texto largo llevan
@Column(length = 1000). - La aplicación que arranca con datos y los sirve por
GET /artistas.
Lo que viene después, siguiendo el orden de Release 6:
- Repetir el proceso con las entidades de tu módulo que falten.
- Mapear las relaciones inter-módulo, una vez que el equipo apruebe el
diagrama del proyecto, y agregar sus
INSERTaldata.sqlcompartido.