Ir al contenido

Tutorial: Cargar datos iniciales con data.sql

Al terminar este tutorial vas a ser capaz de:

  • Crear un data.sql que 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 @ManyToMany con subconsultas.
  • Reconocer el error Value too long y 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.


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.


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:

Ventana de terminal
./mvnw spring-boot:run

Falla, 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.

En src/main/resources/application.properties, agrega:

spring.jpa.defer-datasource-initialization=true

Esta 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.


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.

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'.

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');

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).

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:

  1. Las tablas que no dependen de otra: artistas y álbumes.
  2. Después las que dependen de ellas: la tabla intermedia artista_album y 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.


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:

  1. Arranca la aplicación con un data.sql vacío o sin tus entidades.
  2. Abre la consola de H2 y pega tus INSERT en el orden correcto.
  3. 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.


Arranca la aplicación con el data.sql completo:

Ventana de terminal
./mvnw spring-boot:run

Primero 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.DURACION
FROM CANCION_ENTITY c
JOIN 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 album
FROM ARTISTA_ALBUM aa
JOIN ARTISTA_ENTITY ar ON aa.ARTISTA_ID = ar.ID
JOIN ALBUM_ENTITY al ON aa.ALBUM_ID = al.ID;

Debe devolver una fila por cada pareja artista-álbum que insertaste.


Al final de este tutorial tienes:

  • Un data.sql que 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 INSERT al data.sql compartido.