Skip to Content
DatabasesMigrations de schéma

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

Exemples :

V1__init_schema.sql V2__add_users_table.sql V3__add_index_on_users_email.sql

Flyway les exécute dans l’ordre croissant. Les versions invalidées portent le préfixe R :

R__clean.sql

Changelog Liquibase

Liquibase utilise un fichier maître (changelog) qui inclut des &lt;changeSet&gt; 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 &lt;prefix&gt;_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.sql

Flyway 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: true

Migration 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-java

Vérification et validation

spring: flyway: validate-on-migrate: true # Levée d'exception en cas de divergence clean-disabled: true # Interdit flyway clean en production

Liquibase

Configuration Spring Boot

spring: liquibase: enabled: true change-log: classpath:db/changelog/db.changelog-master.yaml contexts: production # Filtrage par contexte default-schema: public

Changelog 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.yaml

Rollback Liquibase

Deux approches :

  1. Balise de rollback — marquer un état et revenir en arrière :
liquibase --tag v1.0 # ... migrations suivantes ... liquibase --rollbackCount 1
  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: phone

Comparaison Flyway vs Liquibase

CritèreFlywayLiquibase
FormatSQL + JavaXML, YAML, JSON, SQL
Syntaxe déclarativeNon (scripts impératifs)Oui
Rollback automatiqueNon (gestion manuelle)Oui (si déclaré dans changeSet)
Génération depuis schéma existantOui (flyway revert inversé)Oui (generateChangeLog)
Intégration CI/CDNative via pluginVia plugin ou CLI
Environnements multiplesBasé sur le contenu des fichiersBasé sur les contextes
Courbe d’apprentissageFaibleModérée
Adoption dans l’écosystème SpringStandard par défautSupporté 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.sql

Activer 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.sql

Placer les versions &lt; 1 pour les données ; les versions &gt;= 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 COLUMN sans contrainte, puis peupler en batchs, puis ajouter la contrainte.
  • Augmenter statement_timeout pour 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 :

  1. Lancer une branche depuis main (jamais depuis une autre branche de migration).
  2. Créer V3__ma_migration.sql.
  3. Fusionner dans main par squash ou merge-commit unique.
  4. É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: true

Ces 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.ContainerDatabaseDriver

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