Migrations de schéma — Flyway & Liquibase
Gérer les changements de structure d’une base de données dans le temps : versioning, application, rollback. Outils standards du JVM pour Spring Boot.
Concepts
Le schéma d’une base de données évolue avec le code. Les migrations automatisées permettent de :
- Appliquer les changements de manière reproductible et ordonnée.
- Versionner chaque modification de schéma.
- Réappliquer l’état attendu après une réinitialisation ou une mise à jour.
- Collaborer sans conflits de schéma en production.
Convention de nommage Flyway
Les fichiers de migration suivent le pattern :
V<version>__<description>.sqlExemples :
V1__init_schema.sql
V2__add_users_table.sql
V3__add_index_on_users_email.sqlFlyway les exécute dans l’ordre croissant. Les versions invalidées portent le préfixe R :
R__clean.sqlChangelog Liquibase
Liquibase utilise un fichier maître (changelog) qui inclut des <changeSet> individuelles. Chaque changeSet possède un identifiant unique et un auteur :
<changeSet id="1" author="dev">
<createTable tableName="users">
<column name="id" type="BIGINT" autoIncrement="true">
<constraints primaryKey="true"/>
</column>
<column name="email" type="VARCHAR(255)"/>
</createTable>
</changeSet>Liquibase crée automatiquement une table <prefix>_changelog (par défaut databasechangelog) qui suit les changements appliqués.
Flyway
Structure de projet
src/main/resources/
db/migration/
V1__init_schema.sql
V2__add_users_table.sqlFlyway scanne récursivement db/migration/ dans l’ordre alphabétique.
Configuration Spring Boot
spring:
flyway:
enabled: true
locations: classpath:db/migration
baseline-on-migrate: true
baseline-version: 0
clean-disabled: true
validate-on-migrate: trueMigration SQL inline
Un fichier .sql contient une ou plusieurs instructions. Flyway l’exécute dans une transaction par défaut (si le moteur le supporte) :
-- V1__init_schema.sql
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE orders (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT REFERENCES users(id),
total NUMERIC(10,2) NOT NULL
);Migration Java
Pour les migrations complexes (procédures, triggers, données de référence) :
import org.flywaydb.core.api.migration.BaseJavaMigration;
import org.flywaydb.core.api.migration.Context;
public class V2__seed_roles implements BaseJavaMigration {
@Override
public void migrate(Context context) throws Exception {
context.getConnection()
.createStatement()
.execute("INSERT INTO roles (name) VALUES ('admin'), ('user')");
}
}Placer dans src/main/java/org/example/migrations/ et configurer :
spring:
flyway:
locations:
- classpath:db/migration
- classpath:db/migrations-javaVérification et validation
spring:
flyway:
validate-on-migrate: true # Levée d'exception en cas de divergence
clean-disabled: true # Interdit flyway clean en productionLiquibase
Configuration Spring Boot
spring:
liquibase:
enabled: true
change-log: classpath:db/changelog/db.changelog-master.yaml
contexts: production # Filtrage par contexte
default-schema: publicChangelog YAML
databaseChangeLog:
- changeSet:
id: 1
author: dev
changes:
- createTable:
tableName: users
columns:
- column:
name: id
type: bigint
autoIncrement: true
constraints:
primaryKey: true
- column:
name: email
type: varchar(255)
constraints:
nullable: false
unique: true
- changeSet:
id: 2
author: dev
changes:
- addColumn:
tableName: users
columns:
- column:
name: created_at
type: timestamptz
defaultValueComputed: "NOW()"Génération de changelog depuis JDBC
Comparer une base existante à un modèle défini :
liquibase \
--driver=org.postgresql.Driver \
--url=jdbc:postgresql://localhost/appdb \
--username=app \
--password=secret \
diffChangeLog \
--referenceUrl=changelog.xml \
--outputFile=db/changelog/db.changelog-init.yamlRollback Liquibase
Deux approches :
- Balise de rollback — marquer un état et revenir en arrière :
liquibase --tag v1.0
# ... migrations suivantes ...
liquibase --rollbackCount 1- Réversibilité dans le changeSet :
- changeSet:
id: 3
author: dev
changes:
- addColumn:
tableName: users
columns:
- column:
name: phone
type: varchar(20)
rollback:
- dropColumn:
tableName: users
columnName: phoneComparaison Flyway vs Liquibase
| Critère | Flyway | Liquibase |
|---|---|---|
| Format | SQL + Java | XML, YAML, JSON, SQL |
| Syntaxe déclarative | Non (scripts impératifs) | Oui |
| Rollback automatique | Non (gestion manuelle) | Oui (si déclaré dans changeSet) |
| Génération depuis schéma existant | Oui (flyway revert inversé) | Oui (generateChangeLog) |
| Intégration CI/CD | Native via plugin | Via plugin ou CLI |
| Environnements multiples | Basé sur le contenu des fichiers | Basé sur les contextes |
| Courbe d’apprentissage | Faible | Modérée |
| Adoption dans l’écosystème Spring | Standard par défaut | Supporté mais secondaire |
Environnements multiples
Stratégie : fichiers séparés par environnement
db/migration/
common/
V1__init_schema.sql
V2__add_users_table.sql
dev/
V2.1__seed_test_data.sql
prod/
V2.1__seed_prod_data.sqlActiver les répertoires selon l’environnement Spring :
spring:
flyway:
locations:
- classpath:db/migration/common
- classpath:db/migration/${spring.profiles.active}Données de référence
Gérer les données initiales (rôles, codes postaux) dans des migrations V0_* ou des fichiers dédiés :
db/migration/V0__reference_data.sqlPlacer les versions < 1 pour les données ; les versions >= 1 pour la structure.
Pièges courants
Migration en production sur table volumineuse
Flyway exécute chaque fichier dans une transaction. Sur une grande table, ALTER TABLE ADD COLUMN peut bloquer longtemps. Solutions :
- Décomposer les changements en plusieurs étapes.
- Utiliser
ADD COLUMNsans contrainte, puis peupler en batchs, puis ajouter la contrainte. - Augmenter
statement_timeoutpour les gros changements.
Conflits de migration entre développeurs
Chaque développeur doit créer une migration avec un numéro de version unique. Procédure :
- Lancer une branche depuis
main(jamais depuis une autre branche de migration). - Créer
V3__ma_migration.sql. - Fusionner dans
mainpar squash ou merge-commit unique. - Éviter les renumérotations après fusion.
baseline-on-migrate en production
spring.flyway.baseline-on-migrate: true permet d’ignorer les écarts de version. Danger : il peut masquer des migrations jamais exécutées. À activer uniquement sur initialisation ou contournement d’urgence.
Validation des scripts
spring:
flyway:
validate-on-migrate: true
validate-on-clean: trueCes options empêchent Flyway de lancer des scripts dont le descripteur a changé (nom, hash). En cas de divergence, l’application refuse de démarrer au lieu d’exécuter une migration incohérente.
Liquibase et les clés étrangères
Liquibase gère l’ordre d’application des createTable via l’option runWith et l’ordre de déclaration. Pour s’assurer que les FK sont créées après les tables référencées, utiliser un contexts ou décomposer en deux changeSets.
Configuration de test
Dans src/test/resources/application-test.yml :
spring:
flyway:
enabled: true
locations: classpath:db/migration
clean-disabled: true
datasource:
url: jdbc:tc:postgresql:16-alpine:///testdb
driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriverUtiliser Testcontainers pour un PostgreSQL éphémère à chaque exécution de tests :
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<scope>test</scope>
</dependency>Les migrations s’appliquent avant chaque test, garantissant un état cohérent.