Capítulo 3: Migración de Esquema con Flyway
Introducción
¿Alguna vez ha vivido esta situación?
"Espera, ¿qué script SQL ejecutó en producción?"
"El que tiene las nuevas columnas... ¿creo?"
"¡Pero había 3 versiones! ¿V1, V2 o V2_final_final?"
💀
Flyway resuelve este problema versionando su esquema de base de datos como versiona su código.
🎯 Objetivos del Capítulo
Al final de este capítulo, usted sabrá:
- ✅ Por qué Flyway es esencial para producción
- ✅ Instalar y configurar Flyway con Spring Boot
- ✅ Crear scripts de migración
- ✅ Comprender la convención de nombres
- ✅ Gestionar migraciones repetibles
- ✅ Resolver problemas comunes
1. ¿Por qué versionar el esquema?
El Problema
Problemas:
- No se sabe qué script se ha ejecutado
- No hay historial
- Riesgo de olvidar una migración
- Imposible reproducir el entorno
La Solución: Flyway
Ventajas:
- ✅ Historial completo de migraciones
- ✅ Ejecución automática al iniciar
- ✅ Idéntico en todos los entornos
- ✅ Imposible olvidar una migración
2. Instalación
Dependencia Maven
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<!-- Para PostgreSQL, agregue también: -->
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-database-postgresql</artifactId>
</dependency>
Con Spring Boot
¡Eso es todo! Spring Boot auto-configura Flyway automáticamente.
# application.properties
spring.flyway.enabled=true # Por defecto: true si Flyway está en el classpath
3. Estructura de Archivos
Ubicación por Defecto
src/
└── main/
└── resources/
└── db/
└── migration/
├── V1__create_users_table.sql
├── V2__add_email_column.sql
├── V3__create_orders_table.sql
└── V4__add_index_on_email.sql
Convención de Nombres
V{version}__{description}.sql
│ │ │
│ │ └── Descripción (underscores = espacios)
│ └── Número de versión (1, 2, 2.1, etc.)
└── Prefijo "V" (Migración versionada)
Ejemplos válidos:
V1__create_users.sql
V2__add_email_to_users.sql
V2.1__add_phone_number.sql
V10__create_products.sql
V100__final_cleanup.sql
Ejemplos inválidos:
V1_create_users.sql ❌ Solo un underscore
v1__create_users.sql ❌ Minúscula
1__create_users.sql ❌ Sin prefijo V
V1__create users.sql ❌ Espacios en el nombre
4. Crear Migraciones
Migración 1: Crear una Tabla
-- V1__create_users_table.sql
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(100) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Migración 2: Agregar una Columna
-- V2__add_email_to_users.sql
ALTER TABLE users
ADD COLUMN email VARCHAR(100) UNIQUE;
Migración 3: Crear Otra Tabla con FK
-- V3__create_orders_table.sql
CREATE TABLE orders (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id),
total_amount DECIMAL(10, 2) NOT NULL,
status VARCHAR(20) DEFAULT 'PENDING',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_orders_user_id ON orders(user_id);
Migración 4: Insertar Datos
-- V4__insert_initial_data.sql
INSERT INTO users (username, password, email) VALUES
('admin', '$2a$10$hashedpassword', 'admin@example.com'),
('user1', '$2a$10$hashedpassword', 'user1@example.com');
5. ¿Cómo funciona Flyway?
La Tabla flyway_schema_history
Flyway crea automáticamente una tabla para rastrear las migraciones:
SELECT * FROM flyway_schema_history;
| installed_rank | version | description | type | script | checksum | installed_on | execution_time | success |
|---|---|---|---|---|---|---|---|---|
| 1 | 1 | create users table | SQL | V1__create_users_table.sql | -123456 | 2025-01-15 10:00 | 45 | true |
| 2 | 2 | add email to users | SQL | V2__add_email_to_users.sql | 789012 | 2025-01-15 10:01 | 12 | true |
| 3 | 3 | create orders table | SQL | V3__create_orders_table.sql | 345678 | 2025-01-16 09:00 | 67 | true |
Flujo de Ejecución
6. Configuración Spring Boot
Configuración Básica
# application.properties
# Activar Flyway
spring.flyway.enabled=true
# Ubicación de los scripts (por defecto: classpath:db/migration)
spring.flyway.locations=classpath:db/migration
# Baseline (para bases de datos existentes)
spring.flyway.baseline-on-migrate=false
spring.flyway.baseline-version=1
Configuración Avanzada
# Esquema objetivo
spring.flyway.schemas=public
# Limpieza (¡PELIGROSO en producción!)
spring.flyway.clean-disabled=true
# Validación
spring.flyway.validate-on-migrate=true
# Sustitución de placeholders
spring.flyway.placeholder-replacement=true
spring.flyway.placeholders.table_prefix=app_
Configuración YAML
spring:
flyway:
enabled: true
locations: classpath:db/migration
baseline-on-migrate: false
clean-disabled: true
validate-on-migrate: true
7. Migraciones Repetibles
Casos de Uso
Algunos objetos deben ser recreados en cada cambio:
- Vistas (CREATE OR REPLACE VIEW)
- Procedimientos almacenados
- Funciones
- Datos de referencia
Convención
R__{description}.sql
│
└── Prefijo "R" (Repeatable)
Ejemplo
-- R__create_active_users_view.sql
CREATE OR REPLACE VIEW active_users AS
SELECT id, username, email
FROM users
WHERE last_login > NOW() - INTERVAL '30 days';
Comportamiento:
- Flyway calcula un checksum del archivo
- Si el archivo cambia → se reejecuta
- Se ejecuta DESPUÉS de todas las migraciones versionadas
8. Gestión de Entornos
Estructura Multi-Entorno
src/main/resources/
├── db/
│ └── migration/
│ ├── V1__create_tables.sql # Todos los entornos
│ ├── V2__add_columns.sql
│ └── V3__create_indexes.sql
│
├── db-dev/
│ └── migration/
│ └── V1000__dev_test_data.sql # Datos de prueba (solo dev)
│
└── db-prod/
└── migration/
└── V1001__prod_config.sql # Configuración producción
Configuración por Perfil
# application-dev.properties
spring.flyway.locations=classpath:db/migration,classpath:db-dev/migration
# application-prod.properties
spring.flyway.locations=classpath:db/migration,classpath:db-prod/migration
9. Integración con JPA/Hibernate
Orden de Ejecución
# IMPORTANTE: Flyway se ejecuta ANTES que Hibernate
spring.jpa.hibernate.ddl-auto=validate # No generar el esquema
# O para desarrollo:
spring.jpa.hibernate.ddl-auto=none # Flyway gestiona todo
Flujo de Trabajo Recomendado
En desarrollo: Utilice ddl-auto=validate para verificar que sus entidades correspondan al esquema.
En producción: Utilice ddl-auto=none y deje que Flyway gestione todo.
10. Comandos Flyway
Vía Maven Plugin
<plugin>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-maven-plugin</artifactId>
<configuration>
<url>jdbc:postgresql://localhost:5432/mydb</url>
<user>postgres</user>
<password>secret</password>
</configuration>
</plugin>
# Ver migraciones aplicadas
mvn flyway:info
# Aplicar migraciones
mvn flyway:migrate
# Validar migraciones
mvn flyway:validate
# Reparar el historial (¡cuidado!)
mvn flyway:repair
# Limpiar la base de datos (¡PELIGROSO!)
mvn flyway:clean
Ejemplo de Salida flyway:info
+-----------+---------+----------------------+------+---------------------+---------+
| Category | Version | Description | Type | Installed On | State |
+-----------+---------+----------------------+------+---------------------+---------+
| Versioned | 1 | create users table | SQL | 2025-01-15 10:00:00 | Success |
| Versioned | 2 | add email to users | SQL | 2025-01-15 10:01:00 | Success |
| Versioned | 3 | create orders table | SQL | 2025-01-16 09:00:00 | Success |
| Versioned | 4 | add indexes | SQL | | Pending |
+-----------+---------+----------------------+------+---------------------+---------+
11. Problemas Comunes y Soluciones
❌ Checksum Mismatch
FlywayException: Validate failed: Migration checksum mismatch for migration version 2
-> Applied to database : -123456
-> Resolved locally : 789012
Causa: El archivo de migración fue modificado después de su ejecución.
Soluciones:
- Nunca modificar una migración ya ejecutada.
- Si es en desarrollo:
mvn flyway:repair - Crear una nueva migración para corregir
❌ Migration Not Found
FlywayException: Detected resolved migration not applied to database: 3
Causa: Existe una migración en la base de datos pero no en los archivos.
Solución: Restaurar el archivo de migración faltante.
❌ Out of Order Migration
FlywayException: Detected resolved migration not applied to database: 2
(Applied migrations are: 1, 3)
Causa: Migración V2 agregada después de V3.
Solución:
spring.flyway.out-of-order=true # Permitir migraciones fuera de orden
❌ Failed Migration
FlywayException: Migration V4__add_index.sql failed
Solución:
- Corregir el SQL en el archivo
- Ejecutar
mvn flyway:repair(marca como resuelto) - Ejecutar
mvn flyway:migrate
12. Mejores Prácticas
✅ Reglas de Oro
1. NUNCA modificar una migración ya ejecutada
→ Crear una nueva migración para corregir
2. NUNCA eliminar una migración ejecutada
→ El historial debe permanecer coherente
3. SIEMPRE probar las migraciones en un entorno de pruebas
→ Antes de desplegar en producción
4. SIEMPRE tener un backup antes de migrar en producción
→ Por si acaso
5. Mantener las migraciones pequeñas y enfocadas
→ Una migración = una modificación lógica
✅ Nombres Claros
-- ✅ BUENO
V1__create_users_table.sql
V2__add_email_column_to_users.sql
V3__create_orders_with_foreign_keys.sql
-- ❌ MALO
V1__changes.sql
V2__update.sql
V3__fix.sql
✅ Scripts Idempotentes
-- ✅ BUENO: Verifica antes de crear
CREATE TABLE IF NOT EXISTS users (...);
-- ✅ BUENO: Verifica antes de agregar una columna (PostgreSQL)
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
WHERE table_name = 'users' AND column_name = 'phone') THEN
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
END IF;
END $$;
13. Resumen
Puntos Clave
| Concepto | Descripción |
|---|---|
| Flyway | Herramienta de migración de esquema versionado |
| V{n}__ | Prefijo para migraciones versionadas |
| R__ | Prefijo para migraciones repetibles |
| flyway_schema_history | Tabla que rastrea las migraciones |
| checksum | Hash del archivo para detectar modificaciones |
Flujo de Trabajo Típico
1. Crear un archivo V{n}__description.sql
2. Escribir el SQL
3. Iniciar la aplicación (o mvn flyway:migrate)
4. Flyway ejecuta automáticamente
5. Verificar con mvn flyway:info
Configuración Mínima
# Spring Boot + Flyway
spring.flyway.enabled=true
spring.flyway.locations=classpath:db/migration
spring.jpa.hibernate.ddl-auto=validate
🎯 Próximo Paso
Flyway es la herramienta más popular, pero Liquibase ofrece una alternativa con funcionalidades diferentes. Veamos cuándo elegir una u otra.
➡️ Capítulo 4: Migración con Liquibase