TP3 - Migración de Esquema con Flyway
Objetivos del TP
En este TP, usted va a:
- ✅ Configurar Flyway en un proyecto Spring Boot
- ✅ Crear scripts de migración versionados
- ✅ Simular una evolución de esquema de base de datos
- ✅ Gestionar los datos iniciales con Flyway
- ✅ Utilizar migraciones repetibles
Duración estimada: 1h30
Contexto
Usted desarrolla una aplicación de e-commerce. Con el tiempo, el esquema evoluciona:
- V1: Creación de las tablas básicas (users, products)
- V2: Adición de columnas (email a users)
- V3: Creación de la tabla orders
- V4: Adición de índices para el rendimiento
- V5: Inserción de datos de referencia
Parte 1: Configuración del Proyecto
1.1 Crear el Proyecto
Cree un nuevo proyecto Spring Boot con:
- Artifact:
flyway-demo - Dependencies:
- Spring Web
- Spring Data JPA
- H2 Database
- PostgreSQL Driver
- Flyway Migration
- Lombok
1.2 Dependencias Maven
<!-- pom.xml - Verifique que Flyway está presente -->
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<!-- Para PostgreSQL (opcional) -->
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-database-postgresql</artifactId>
</dependency>
1.3 Estructura de Carpetas
src/main/resources/
├── application.properties
├── application-dev.properties
├── application-prod.properties
└── db/
└── migration/
├── V1__create_users_table.sql
├── V2__add_email_to_users.sql
├── V3__create_products_table.sql
├── V4__create_orders_table.sql
├── V5__add_indexes.sql
├── V6__insert_reference_data.sql
└── R__create_views.sql
1.4 Configuración
# application.properties
spring.profiles.active=dev
# Flyway
spring.flyway.enabled=true
spring.flyway.locations=classpath:db/migration
spring.flyway.baseline-on-migrate=false
# Logging
logging.level.org.flywaydb=DEBUG
# application-dev.properties
spring.datasource.url=jdbc:h2:mem:flywaydb;DB_CLOSE_DELAY=-1
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
# JPA NO gestiona el esquema - ¡Flyway se encarga!
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.show-sql=true
Parte 2: Primera Migración - Users
2.1 Script V1
Cree el archivo src/main/resources/db/migration/V1__create_users_table.sql:
-- V1__create_users_table.sql
-- Creación de la tabla de usuarios
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
password VARCHAR(100) NOT NULL,
first_name VARCHAR(50),
last_name VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uk_users_username UNIQUE (username)
);
-- Comentario para documentación
COMMENT ON TABLE users IS 'Tabla de usuarios de la aplicación';
2.2 Entidad Correspondiente
// src/main/java/com/example/flywaydemo/model/User.java
package com.example.flywaydemo.model;
import jakarta.persistence.*;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Entity
@Table(name = "users")
@Data
@NoArgsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true, length = 50)
private String username;
@Column(nullable = false, length = 100)
private String password;
@Column(name = "first_name", length = 50)
private String firstName;
@Column(name = "last_name", length = 50)
private String lastName;
@Column(name = "created_at")
private LocalDateTime createdAt = LocalDateTime.now();
@Column(name = "updated_at")
private LocalDateTime updatedAt = LocalDateTime.now();
}
2.3 Probar
# Iniciar la aplicación
mvn spring-boot:run
# Verificar los logs - debería ver:
# Flyway Community Edition
# Successfully validated 1 migration
# Migrating schema "PUBLIC" to version "1 - create users table"
# Successfully applied 1 migration
Verificación H2:
- Abrir http://localhost:8080/h2-console
- URL:
jdbc:h2:mem:flywaydb - Verificar que la tabla
usersexiste - Verificar que la tabla
flyway_schema_historyexiste
SELECT * FROM flyway_schema_history;
Parte 3: Evolución del Esquema
3.1 V2 - Añadir una Columna
-- V2__add_email_to_users.sql
-- Adición de la columna email
ALTER TABLE users ADD COLUMN email VARCHAR(100);
-- Crear un índice en el email
CREATE UNIQUE INDEX idx_users_email ON users(email);
Actualización de la entidad:
// Añadir en User.java
@Column(unique = true, length = 100)
private String email;
3.2 V3 - Nueva Tabla Products
-- V3__create_products_table.sql
-- Tabla de productos
CREATE TABLE products (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
description TEXT,
price DECIMAL(10, 2) NOT NULL,
stock_quantity INT NOT NULL DEFAULT 0,
category VARCHAR(50),
active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_price_positive CHECK (price >= 0),
CONSTRAINT chk_stock_positive CHECK (stock_quantity >= 0)
);
// Product.java
package com.example.flywaydemo.model;
import jakarta.persistence.*;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Entity
@Table(name = "products")
@Data
@NoArgsConstructor
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 100)
private String name;
@Column(columnDefinition = "TEXT")
private String description;
@Column(nullable = false, precision = 10, scale = 2)
private BigDecimal price;
@Column(name = "stock_quantity", nullable = false)
private Integer stockQuantity = 0;
@Column(length = 50)
private String category;
@Column(nullable = false)
private Boolean active = true;
@Column(name = "created_at")
private LocalDateTime createdAt = LocalDateTime.now();
}
3.3 V4 - Tabla Orders con FK
-- V4__create_orders_table.sql
-- Tabla de pedidos con relación a users
CREATE TABLE orders (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
status VARCHAR(20) DEFAULT 'PENDING',
total_amount DECIMAL(12, 2) NOT NULL,
shipping_address TEXT,
CONSTRAINT fk_orders_user FOREIGN KEY (user_id)
REFERENCES users(id) ON DELETE CASCADE
);
-- Tabla de relación orders-products
CREATE TABLE order_items (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
order_id BIGINT NOT NULL,
product_id BIGINT NOT NULL,
quantity INT NOT NULL,
unit_price DECIMAL(10, 2) NOT NULL,
CONSTRAINT fk_order_items_order FOREIGN KEY (order_id)
REFERENCES orders(id) ON DELETE CASCADE,
CONSTRAINT fk_order_items_product FOREIGN KEY (product_id)
REFERENCES products(id)
);
3.4 V5 - Índices para Rendimiento
-- V5__add_indexes.sql
-- Adición de índices para mejorar el rendimiento
-- Índice en la fecha de pedido
CREATE INDEX idx_orders_date ON orders(order_date);
-- Índice en el estado
CREATE INDEX idx_orders_status ON orders(status);
-- Índice en el usuario
CREATE INDEX idx_orders_user ON orders(user_id);
-- Índice en la categoría de producto
CREATE INDEX idx_products_category ON products(category);
-- Índice en productos activos
CREATE INDEX idx_products_active ON products(active) WHERE active = TRUE;
3.5 V6 - Datos de Referencia
-- V6__insert_reference_data.sql
-- Inserción de datos de referencia
-- Usuario admin
INSERT INTO users (username, password, email, first_name, last_name)
VALUES ('admin', '$2a$10$hashedpassword', 'admin@example.com', 'Admin', 'System');
-- Categorías de productos (vía productos de demo)
INSERT INTO products (name, description, price, stock_quantity, category) VALUES
('Laptop Pro', 'Ordenador portátil de alto rendimiento', 1299.99, 50, 'ELECTRONICS'),
('Smartphone X', 'Teléfono de última generación', 899.99, 100, 'ELECTRONICS'),
('T-Shirt Basic', 'Camiseta 100% algodón', 19.99, 500, 'CLOTHING'),
('Jeans Classic', 'Jeans corte recto', 49.99, 200, 'CLOTHING'),
('Libro Java', 'Guía completa de Java', 39.99, 75, 'BOOKS');
Parte 4: Migraciones Repetibles
4.1 Crear una Vista
Las migraciones repetibles se reejecutan si el archivo cambia.
-- R__create_views.sql
-- Vista de productos activos con stock
CREATE OR REPLACE VIEW v_available_products AS
SELECT
id,
name,
price,
stock_quantity,
category
FROM products
WHERE active = TRUE
AND stock_quantity > 0;
-- Vista de estadísticas por categoría
CREATE OR REPLACE VIEW v_category_stats AS
SELECT
category,
COUNT(*) as product_count,
AVG(price) as avg_price,
SUM(stock_quantity) as total_stock
FROM products
WHERE active = TRUE
GROUP BY category;
4.2 Probar
Modifique la vista y reinicie:
-- Modifique R__create_views.sql
CREATE OR REPLACE VIEW v_available_products AS
SELECT
id,
name,
price,
stock_quantity,
category,
'IN_STOCK' as availability -- ¡Nueva columna!
FROM products
WHERE active = TRUE
AND stock_quantity > 0;
Al reiniciar, Flyway detecta el cambio de checksum y reejecuta el script.
Parte 5: Verificación y Comandos
5.1 Verificar el Estado de las Migraciones
Abra la consola H2 y ejecute:
-- Historial completo de migraciones
SELECT
installed_rank,
version,
description,
type,
installed_on,
execution_time,
success
FROM flyway_schema_history
ORDER BY installed_rank;
Resultado esperado:
| installed_rank | version | description | type | success |
|---|---|---|---|---|
| 1 | 1 | create users table | SQL | true |
| 2 | 2 | add email to users | SQL | true |
| 3 | 3 | create products table | SQL | true |
| 4 | 4 | create orders table | SQL | true |
| 5 | 5 | add indexes | SQL | true |
| 6 | 6 | insert reference data | SQL | true |
| 7 | NULL | create views | SQL | true |
5.2 Comandos Maven (Opcional)
Agregue el plugin Flyway en pom.xml:
<plugin>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-maven-plugin</artifactId>
<configuration>
<url>jdbc:h2:file:./target/flywaydb</url>
<user>sa</user>
<password></password>
</configuration>
</plugin>
# Ver estado
mvn flyway:info
# Aplicar las migraciones
mvn flyway:migrate
# Validar
mvn flyway:validate
Parte 6: Gestión de Errores
6.1 Simular un Error
Cree un script con un error:
-- V7__broken_migration.sql
-- Script con error de sintaxis
CREATE TABLE broken_table (
id BIGINT PRIMARY KEY
name VARCHAR(50) -- ¡Falta la coma!
);
Reinicie la aplicación → ¡Error!
6.2 Corregir
- Opción A: Corregir el script y usar repair (solo desarrollo)
# Marcar como resuelto
mvn flyway:repair
# Reaplicar
mvn flyway:migrate
-
Opción B: Eliminar el archivo V7 y la línea en flyway_schema_history
-
Opción C (Producción): Crear V8 para corregir
-- V8__fix_broken_table.sql
-- Corrección del error V7
DROP TABLE IF EXISTS broken_table;
CREATE TABLE broken_table (
id BIGINT PRIMARY KEY,
name VARCHAR(50)
);
6.3 Regla de Oro
NUNCA modifique un script de migración ya ejecutado en producción.
Siempre cree un NUEVO script para corregir los problemas.
Parte 7: Migración PostgreSQL
7.1 Configuración Producción
# application-prod.properties
spring.datasource.url=jdbc:postgresql://localhost:5432/flywaydb
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.username=postgres
spring.datasource.password=secret
spring.h2.console.enabled=false
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
# Flyway
spring.flyway.enabled=true
spring.flyway.clean-disabled=true
7.2 Adaptar los Scripts (si es necesario)
Algunas sintaxis difieren entre H2 y PostgreSQL:
-- H2
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
...
);
-- PostgreSQL
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
...
);
Solución: Utilizar scripts específicos por entorno:
db/migration/
├── h2/
│ └── V1__create_tables.sql
└── postgresql/
└── V1__create_tables.sql
# application-dev.properties
spring.flyway.locations=classpath:db/migration/h2
# application-prod.properties
spring.flyway.locations=classpath:db/migration/postgresql
Parte 8: Endpoint de Verificación
8.1 Controller para Verificar las Migraciones
// src/main/java/com/example/flywaydemo/controller/MigrationController.java
package com.example.flywaydemo.controller;
import org.flywaydb.core.Flyway;
import org.flywaydb.core.api.MigrationInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/api/migrations")
public class MigrationController {
@Autowired
private Flyway flyway;
@GetMapping
public List<Map<String, Object>> getMigrations() {
return Arrays.stream(flyway.info().all())
.map(this::toMap)
.collect(Collectors.toList());
}
@GetMapping("/pending")
public List<Map<String, Object>> getPendingMigrations() {
return Arrays.stream(flyway.info().pending())
.map(this::toMap)
.collect(Collectors.toList());
}
private Map<String, Object> toMap(MigrationInfo info) {
return Map.of(
"version", info.getVersion() != null ? info.getVersion().toString() : "N/A",
"description", info.getDescription(),
"type", info.getType().name(),
"state", info.getState().name(),
"installedOn", info.getInstalledOn() != null ? info.getInstalledOn().toString() : "N/A"
);
}
}
Prueba:
curl http://localhost:8080/api/migrations
Resumen
Archivos Creados
| Archivo | Descripción |
|---|---|
V1__create_users_table.sql | Tabla users básica |
V2__add_email_to_users.sql | Adición columna email |
V3__create_products_table.sql | Tabla products |
V4__create_orders_table.sql | Tablas orders y order_items |
V5__add_indexes.sql | Índices de rendimiento |
V6__insert_reference_data.sql | Datos iniciales |
R__create_views.sql | Vistas (repetible) |
Comandos Clave
| Comando | Acción |
|---|---|
mvn flyway:info | Ver el estado de las migraciones |
mvn flyway:migrate | Aplicar las migraciones |
mvn flyway:validate | Validar la coherencia |
mvn flyway:repair | Reparar el historial |
mvn flyway:clean | Limpiar la BDD (¡PELIGROSO!) |
Lo Que Usted Ha Aprendido
- ✅ Configurar Flyway con Spring Boot
- ✅ Nombrar correctamente los scripts (V1__, V2__, R__)
- ✅ Crear migraciones incrementales
- ✅ Gestionar los datos de referencia
- ✅ Utilizar migraciones repetibles para las vistas
- ✅ Corregir errores de migración
🎯 ¡Módulo Terminado!
¡Felicidades! Ha completado el Módulo 7 sobre bases de datos y transacciones.
Resumen del módulo:
- Capítulo 1: Configuración de Datasources (HikariCP)
- Capítulo 2: Gestión de Transacciones (@Transactional)
- Capítulo 3: Migración con Flyway
- Capítulo 4: Migración con Liquibase
- TP1: Configuración Datasource
- TP2: Transacciones y Rollback
- TP3: Migración Flyway (este TP)