Migration de données à partir du code Java : une approche dite code-first

Comment faciliter la migration de données lorsque l'on a généré son schéma à partir des entités métier

RonnyGuillaume

Cet article vous montrera comment faire évoluer votre schéma de base de données lorsque vous l'avez généré à partir des entités métier.
Commentez

- Titre : Migration de données à partir du code Java : une approche dite code-first

- Auteur : RonnyGuillaume

- Parution : 3 décembre 2012

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2012 RonnyGuillaume. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- II. Présentation du point de départ de la migration de données (V1)

- III. Présentation du point d'arrivée de la migration de données (V2)

- IV. Passage du point de départ au point d'arrivée

- V. Pour aller plus loin : gestion des schémas

- VI. Conclusion

- VII. Remerciements

I - Introduction

Supposons que :

- vous avez développé une application en version V1 qui interagit avec une base de données relationnelle ;

- que vous avez livré cette application à votre client et que ce dernier l'a mise en production ;

- que quelques mois plus tard le client vous commande un gros lot d'évolutions (en somme une V2 de l'application) nécessitant de modifier le schéma de la base de données ;

- que vous avez généré le schéma de votre base de données à partir de vos entités métier (vous avez adopté une approche code-first).

Dans cette situation, vous aurez un script SQL à concevoir qui permette de faire passer le schéma de la base de données de production (qui est en version V1) en version V2. Tâche aussi délicate que fastidieuse, surtout lorsque le schéma contient beaucoup de tables.
Ce que je vous propose, c'est une méthode vous permettant de faciliter le passage du schéma V1 au schéma V2.
Dans cet article nous utiliserons :

- l'ORM Hibernate (http://www.hibernate.org/) ;

- la base de données relationnelle H2 (http://www.h2database.com/html/main.html) en version 1.3.167, particulièrement véloce en mode Embedded.

Cela dit, la démarche peut très bien s'appliquer à un projet utilisant un ORM comme EclipseLink, (ou OpenJPA, Entity Framework, etc.) et une base de données comme Oracle (ou MySQL, SQL Server, PostgreSQL, etc.).

II - Présentation du point de départ de la migration de données (V1)

Notre point de départ est une application bancaire avec deux classes : User et Account. Un utilisateur (User) peut avoir plusieurs comptes bancaires (Account), mais un compte bancaire ne peut être associé qu'à un seul utilisateur.

Classe User

package com.example.myproject.server.domain;

import java.io.Serializable;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Version;

@Entity
public class User implements Serializable{
@Id
@GeneratedValue
private Long id;

@Version
private long version;
@Column(length=12,nullable=false)
private String fristName;
@Column(length=12,nullable=false)
private String lastName;
@Column(nullable=false)
private short age;

@OneToMany(mappedBy="user",cascade=CascadeType.ALL)
private Set<Account> accounts;
}

Classe Account

package com.example.myproject.server.domain;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Version;

@Entity
public class Account implements Serializable{
@Id
@GeneratedValue
private Long id;
@Version
private long version;
@Column(scale=2,nullable=false)
private double amount;
@ManyToOne(optional=false)
private User user;
}

En utilisant les outils de génération de schéma d'Hibernate on obtient le script suivant :

Script de la base de données v1

create table Account (
id bigint generated by default as identity (start with 1),
amount double not null,
version bigint not null,
user_id bigint not null,
primary key (id)
);

create table User (
id bigint generated by default as identity (start with 1),
age smallint not null,
fristName varchar(12) not null,
lastName varchar(12) not null,
version bigint not null,
primary key (id)
);

alter table Account
add constraint FK1D0C220D8CA544AB
foreign key (user_id)
references User;

Ce schéma a été livré au client, et la base de données de production repose le script SQL ci-dessus.

III - Présentation du point d'arrivée de la migration de données (V2)

Notre point d'arrivée se compose des mêmes classes que celles de la V1 :

Classe User

package com.example.myproject.server.domain;

import java.io.Serializable;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Version;

@Entity
public class User implements Serializable{
@Id
@GeneratedValue
private Long id;

@Version
private long version;

//modified
@Column(length=30,nullable=false)
private String firstName;

//modified
@Column(length=30,nullable=false)
private String lastName;

@Column(nullable=false)
private short age;

//added
@Column(length=10)
private String phoneNumber;

@OneToMany(mappedBy="user",cascade=CascadeType.ALL)
private Set<Account> accounts;
}

Classe Account

package com.example.myproject.server.domain;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Version;

@Entity
public class Account implements Serializable{

private static final long serialVersionUID = 8204815540254878355L;
@Id
@GeneratedValue
private Long id;

@Version
private long version;

@Column(scale=2,nullable=false)
private double amount;

@ManyToOne(optional=false)
private User user;

//added
@Column(nullable=false)
private boolean isActive;
}

Vous constaterez que les classes ont été modifiées.

En utilisant les outils de génération de schéma d'Hibernate on obtient le script suivant :

Script de la base de données v2

create table Account (
id bigint generated by default as identity (start with 1),
amount double not null,
isActive bit not null,
version bigint not null,
user_id bigint not null,
primary key (id)
);

create table User (
id bigint generated by default as identity (start with 1),
age smallint not null,
firstName varchar(30) not null,
lastName varchar(30) not null,
phoneNumber varchar(10),
version bigint not null,
primary key (id)
);

alter table Account
add constraint FK1D0C220D8CA544AB
foreign key (user_id)
references User;

On commence ici à cerner le problème de la migration de données. L'outil de génération d'Hibernate (tout comme celui d'EclipseLink) nous fournit un script permettant de générer le schéma V2 à partir de rien, mais pas de passer (de manière fiable) du schéma V1 au schéma V2.

Plutôt que d'effectuer un renommage, nous aurions pu nous contenter de rajouter de nouveaux champs et d'annoter les anciens en « Deprecated ». Ce procédé a au moins un avantage : on est sûr de ne perdre aucune donnée tant que l'on conserve le champ « Deprecated ». Un inconvénient est que l'on a du coup deux champs à gérer pour la lecture des données (le champ « Deprecated » pour les anciennes données, et l'autre pour les nouvelles données), et c'est dans l'hypothèse où l'on n'utilise que le nouveau champ pour l'écriture des données. Un autre inconvénient est que le fait de garder le champ « Deprecated » fait courir le risque que ce champ soit toujours utilisé. Un dernier inconvénient est que conserver un champ « Deprecated » ne fait que reculer l'échéance, puisque ces champs sont voués à être supprimés tôt ou tard.

IV - Passage du point de départ au point d'arrivée

Pour passer du schéma V1 au schéma V2 nous aurons besoin d'un outil supplémentaire : Liquibase (http://www.liquibase.org/). Mais qu'est-ce que Liquibase ? C'est un outil open source, multiplateforme et multiSGBDR (système de gestion de bases de données relationnelles) qui permet de gérer les évolutions des schémas des bases de données. Voyons comment il peut nous aider à résoudre notre problème.

Tout d'abord téléchargez Liquibase, installez-le où bon vous semble et référencez son répertoire dans la variable d'environnement « Path » (%Path% sous Windows, $Path sous Linux).

Ce que nous allons faire dans un premier temps, c'est générer un « diff » entre les deux schémas, c'est-à-dire un script SQL permettant de passer du schéma V2 à partir du schéma V1.
En supposant que :

- votre driver JDBC d'H2 se trouve dans le répertoire  D:/h2/bin/ ;

- que votre base (utilisée dans le développement du lot 1) V1 a pour URL JDBC jdbc:h2:tcp://localhost/file:D:/databases/liquibasev1 ;

- que votre base (utilisée dans le développement du lot 2) V2 a pour URL JDBC jdbc:h2:tcp://localhost/file:D:/databases/liquibasev2.

Voici la commande à saisir dans votre shell :

> liquibase --defaultsFile=liquibase.properties   diffChangeLog

Le fichier de propriétés « liquibase.properties » référencé dans la commande contient les informations permettant à Liquibase de se connecter à la base de données. Cela évite de les préciser à chaque ligne de commande.

Fichier liquibase.properties

#liquibase.properties
driver: org.h2.Driver
classpath: D:/h2/bin/h2-1.3.167.jar
url: jdbc:h2:tcp://localhost/file:D:/databases/liquibasev1
username: sa
password:
referenceUsername: sa
referencePassword:
referenceUrl: jdbc:h2:tcp://localhost/file:D:/databases/liquibasev2

La commande à proprement parler vous génère du XML, copiez la partie XML et mettez-le dans un fichier intitulé « changelog.xml ».

Fichier changelog.xml généré par Liquibase

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocat
ion="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-2.0.xsd">
<changeSet author="RonnyGuillaume (generated)" id="1354569877499-1">
<addColumn tableName="ACCOUNT">
<column name="ISACTIVE" type="BOOLEAN">
<constraints nullable="false"/>
</column>
</addColumn>
</changeSet>
<changeSet author="RonnyGuillaume (generated)" id="1354569877499-2">
<addColumn tableName="USER">
<column name="FIRSTNAME" type="VARCHAR(30)">
<constraints nullable="false"/>
</column>
</addColumn>
</changeSet>
<changeSet author="RonnyGuillaume (generated)" id="1354569877499-3">
<addColumn tableName="USER">
<column name="PHONENUMBER" type="VARCHAR(10)"/>
</addColumn>
</changeSet>
<changeSet author="RonnyGuillaume (generated)" id="1354569877499-4">
<modifyDataType columnName="LASTNAME" newDataType="VARCHAR(30)" tableName="USER"/>
</changeSet>
<changeSet author="RonnyGuillaume (generated)" id="1354569877499-5">
<dropColumn columnName="FRISTNAME" tableName="USER"/>
</changeSet>
</databaseChangeLog>

Ce fichier XML contient l'ensemble des modifications qui ont « été faites » entre le schéma de la V1 et celui de la V2.

On constate un problème : la suppression de la colonne « fristname » de la table « User ». Vous avez dans votre version V1 considéré que la colonne « fristname » de la table « User » contiendrait le prénom des utilisateurs. Dans la version V2, vous vous êtes rendu compte de votre faute de frappe et vous l'avez renommée en « firstname ». Liquibase a constaté que la colonne « fristname » a disparu, il a donc supposé que vous l'avez supprimée. Liquibase a constaté que la colonne « firstname » est apparue, il a donc supposé que vous l'avez rajoutée. Toutes les données qui étaient présentes dans la colonne « fristname » sont donc supprimées. C'est un problème majeur des diff de schémas, les renommages ne sont perçus que comme des ajouts/suppressions de colonnes pas comme des renommages de colonnes. Dans ce cas-là il n'y a pas de miracle, il faut remplacer ces ajouts/suppressions « par des renommages à la main » dans le fichier XML. Il y a d'autres cas de figure qui ne sont pas supportés par Liquibase, je vous conseille d'aller lire la documentation officielle pour avoir plus d'informations à ce sujet.

Renommage dans le fichier changelog.xml :

Fichier changelog modifié

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-2.0.xsd">
<changeSet author="RonnyGuillaume (generated)" id="1354569877499-1">
<addColumn tableName="ACCOUNT">
<column name="ISACTIVE" type="BOOLEAN">
<constraints nullable="false"/>
</column>
</addColumn>
</changeSet>
<changeSet author="RonnyGuillaume (generated)" id="1354569877499-2">
<!-- modified -->
<renameColumn tableName="USER" oldColumnName="FRISTNAME" newColumnName="FIRSTNAME"/>
</changeSet>
<changeSet author="RonnyGuillaume (generated)" id="1354569877499-3">
<addColumn tableName="USER">
<column name="PHONENUMBER" type="VARCHAR(10)"/>
</addColumn>
</changeSet>
<changeSet author="RonnyGuillaume (generated)" id="1354569877499-4">
<modifyDataType columnName="LASTNAME" newDataType="VARCHAR(30)" tableName="USER"/>
</changeSet>
<changeSet author="RonnyGuillaume (generated)" id="1354569877499-5">
<!-- modified -->
<modifyDataType columnName="FIRSTNAME" newDataType="VARCHAR(30)" tableName="USER"/>
</changeSet>
</databaseChangeLog>

Si vous n'êtes pas fan du XML, il est possible de faire ça en SQL. Il vous faut donc convertir le fichier XML en SQL :

>liquibase.bat --defaultsFile=liquibase.properties --changeLogFile=changelog.xml   updateSQL > changelog.sql

Voici le fichier SQL généré :

Fichier changelog généré par Liquibase

-- *********************************************************************
-- Update Database Script
-- *********************************************************************
-- Change Log: changelog.xml
-- Ran at: 03/12/12 22:36
-- Against: SA@jdbc:h2:tcp://localhost/file:D:/databases/liquibasev1
-- Liquibase version: 2.0.5
-- *********************************************************************

-- Create Database Lock Table
CREATE TABLE DATABASECHANGELOGLOCK (ID INT NOT NULL, LOCKED BOOLEAN NOT NULL, LOCKGRANTED TIMESTAMP, LOCKEDBY VARCHAR(255), CONSTRAINT PK_DATABASECHANGELOGLOCK PRIMARY KEY (ID));

INSERT INTO DATABASECHANGELOGLOCK (ID, LOCKED) VALUES (1, FALSE);

-- Lock Database
-- Create Database Change Log Table
CREATE TABLE DATABASECHANGELOG (ID VARCHAR(63) NOT NULL, AUTHOR VARCHAR(63) NOT NULL, FILENAME VARCHAR(200) NOT NULL, DATEEXECUTED TIMESTAMP NOT NULL, ORDEREXECUTED INT NOT NULL, EXECTYPE VARCHAR(10) NOT NULL, MD5SUM VARCHAR(35), DESCRIPTION VARCHAR(255), COMMENTS VARCHAR(255), TAG VARCHAR(255), LIQUIBASE VARCHAR(20), CONSTRAINT PK_DATABASECHANGELOG PRIMARY KEY (ID, AUTHOR, FILENAME));

-- Changeset changelog.xml::1354569877499-1::RonnyGuillaume (generated)::(Checksum: 3:b2c07909d6104b5e05f53b7ca97dd2f2)
ALTER TABLE ACCOUNT ADD ISACTIVE BOOLEAN NOT NULL;

INSERT INTO DATABASECHANGELOG (AUTHOR, COMMENTS, DATEEXECUTED, DESCRIPTION, EXECTYPE, FILENAME, ID, LIQUIBASE, MD5SUM, ORDEREXECUTED) VALUES ('RonnyGuillaume (generated)', '', NOW(), 'Add Column', 'EXECUTED', 'changelog.xml', '1354569877499-1', '2.0.5', '3:b2c07909d6104b5e05f53b7ca97dd2f2', 1);

-- Changeset changelog.xml::1354569877499-2::RonnyGuillaume (generated)::(Checksum: 3:8f11e99ddc30f9ac73330fed1cbb3dbb)
ALTER TABLE USER ALTER COLUMN FRISTNAME RENAME TO FIRSTNAME;

INSERT INTO DATABASECHANGELOG (AUTHOR, COMMENTS, DATEEXECUTED, DESCRIPTION, EXECTYPE, FILENAME, ID, LIQUIBASE, MD5SUM, ORDEREXECUTED) VALUES ('RonnyGuillaume (generated)', '', NOW(), 'Rename Column', 'EXECUTED', 'changelog.xml', '1354569877499-2', '2.0.5', '3:8f11e99ddc30f9ac73330fed1cbb3dbb', 2);

-- Changeset changelog.xml::1354569877499-3::RonnyGuillaume (generated)::(Checksum: 3:673c4fdfd2fb3be7b6b194ea045eaf5c)
ALTER TABLE USER ADD PHONENUMBER VARCHAR(10);

INSERT INTO DATABASECHANGELOG (AUTHOR, COMMENTS, DATEEXECUTED, DESCRIPTION, EXECTYPE, FILENAME, ID, LIQUIBASE, MD5SUM, ORDEREXECUTED) VALUES ('RonnyGuillaume (generated)', '', NOW(), 'Add Column', 'EXECUTED', 'changelog.xml', '1354569877499-3', '2.0.5', '3:673c4fdfd2fb3be7b6b194ea045eaf5c', 3);

-- Changeset changelog.xml::1354569877499-4::RonnyGuillaume (generated)::(Checksum: 3:bdafbf8bdf6583983df82fe7c772881f)
ALTER TABLE USER ALTER COLUMN LASTNAME VARCHAR(30);

INSERT INTO DATABASECHANGELOG (AUTHOR, COMMENTS, DATEEXECUTED, DESCRIPTION, EXECTYPE, FILENAME, ID, LIQUIBASE, MD5SUM, ORDEREXECUTED) VALUES ('RonnyGuillaume (generated)', '', NOW(), 'Modify data type', 'EXECUTED', 'changelog.xml', '1354569877499-4', '2.0.5', '3:bdafbf8bdf6583983df82fe7c772881f', 4);

-- Changeset changelog.xml::1354569877499-5::RonnyGuillaume (generated)::(Checksum: 3:5c740fc978ac3f362d111640113820cc)
ALTER TABLE USER ALTER COLUMN FIRSTNAME VARCHAR(30);

INSERT INTO DATABASECHANGELOG (AUTHOR, COMMENTS, DATEEXECUTED, DESCRIPTION, EXECTYPE, FILENAME, ID, LIQUIBASE, MD5SUM, ORDEREXECUTED) VALUES ('RonnyGuillaume (generated)', '', NOW(), 'Modify data type', 'EXECUTED', 'changelog.xml', '1354569877499-5', '2.0.5', '3:5c740fc978ac3f362d111640113820cc', 5);

Ce sont les commandes SQL qui auraient été exécutées par Liquibase si la ligne de commande était :

>liquibase.bat --defaultsFile=liquibase.properties --changeLogFile=changelog.xml   update

Vous constaterez qu'il y a des commandes SQL parasites concernant une certaine table « DATABASECHANGELOG », si vous vous contentez de faire du diff entre deux schémas, supprimez ces commandes, dans le cas contraire je vous conseille de lire la rubrique suivante.

Le fichier « changelog.sql » contient désormais toutes les commandes SQL pour passer d'un schéma V1 à un schéma V2.
Comme vous avez pu le constater, la génération d'un diff est simple mais nécessite quelques modifications manuelles :

- au minimum la suppression de tout ce qui concerne les changelog de Liquibase (DATABASECHANGELOG, etc.) ;

- dans le pire des cas, correction des renommages de colonnes et de tout ce qui n'est pas géré intelligemment par Liquibase.

V - Pour aller plus loin : gestion des schémas

Certes, Liquibase ne se contente pas de faire des diff. Il permet de gérer les versions de votre schéma. Hélas, il ne permet pas de migrer votre schéma d'une V1 à une V3 (version 3 découlant d'un lot 3) correctement : il le fait sans passer par la V2…
C'est pour cela que je vous conseille d'utiliser MyBatis Migrations (http://code.google.com/p/mybatis/wiki/Migration) pour la gestion des versions de schéma.
Sachez que l'utilisation de MyBatis Migrations n'oblige pas à utiliser le framework de persistance MyBatis. Je ne vais pas vous expliquer en détail comment utiliser MyBatis Migrations, la documentation officielle le fait très bien, je vais juste vous expliquer comment utiliser les scripts de diff générés par Liquibase avec MyBatis Migrations.

Installez MyBatis Migrations conformément à la documentation officielle :

- téléchargez l'archive de MyBatis Migrations et décompressez-la où bon vous semble ;

- créez une variable d'environnement « MIGRATIONS_HOME » qui a pour valeur l'emplacement du répertoire d'installation de MyBatis Migrations (exemple : « D:\migration_home ») ;

- rajoutez dans la variable Path, le répertoire « bin » du répertoire d'installation de MyBatis Migrations ;

- lancez un shell et créez votre « repository » en tapant la commande suivante :

> migrate --path=repertoire init

Où « repertoire » est un répertoire pris en compte par votre gestionnaire de versions (Git, SVN, CVS ou autre) .Ceci permettra à toute votre équipe de développement de partager le même repository ;

- mettez le driver JDBC (dans notre cas h2-1.3.167.jar) dans le répertoire « driver » du repository nouvellement créé ;

- allez dans le répertoire « environments » de votre repository, ouvrez le fichier « development.properties » et saisissez les informations permettant à MyBatis Migrations de se connecter à la base de données :

Fichier development.properties

## Base time zone to ensure times are consistent across machines
time_zone=GMT+0:00

## The character set that scripts are encoded with
# script_char_set=UTF-8

## JDBC connection properties.
driver=org.h2.Driver
url=jdbc:h2:tcp://localhost/file:D:/databases/liquibasev1
username=sa
password=

#
# A NOTE ON STORED PROCEDURES AND DELIMITERS
#
# Stored procedures and functions commonly have nested delimiters
# that conflict with the schema migration parsing.  If you tend
# to use procs, functions, triggers or anything that could create
# this situation, then you may want to experiment with
# send_full_script=true (preferred), or if you can't use
# send_full_script, then you may have to resort to a full
# line delimiter such as "GO" or "/" or "!RUN!".
#
# Also play with the autocommit settings, as some drivers
# or databases don't support creating procs, functions or
# even tables in a transaction, and others require it.
#

# This ignores the line delimiters and
# simply sends the entire script at once.
# Use with JDBC drivers that can accept large
# blocks of delimited text at once.
send_full_script=true

# This controls how statements are delimited.
# By default statements are delimited by an
# end of line semicolon.  Some databases may
# (e.g. MS SQL Server) may require a full line
# delimiter such as GO.
# These are ignored if send_full_script is true.
delimiter=;
full_line_delimiter=false

# If set to true, each statement is isolated
# in its own transaction.  Otherwise the entire
# script is executed in one transaction.
# Few databases should need this set to true,
# but some do.
auto_commit=false

# Custom driver path to allow you to centralize your driver files
# Default requires the drivers to be in the drivers directory of your
# initialized migration directory (created with "migrate init")
# driver_path=

# Name of the table that tracks changes to the database
changelog=CHANGELOG

# Migrations support variable substitutions in the form of ${variable}
# in the migration scripts.  All of the above properties will be ignored though,
# with the exception of changelog.
# Example: The following would be referenced in a migration file as ${ip_address}
# ip_address=192.168.0.1

> migrate new « v1 »

Ceci aura pour effet de créer un fichier SQL censé contenir tout ce qui est nécessaire pour créer le schéma V1.
Ce que vous aurez à mettre dans le script SQL qui porte un nom comme « 20121015120019_v1.sql » (présent dans le répertoire « scripts » de votre repository) est le schéma de création de la base de données du lot 1.

Lorsque vous livrerez votre version V2 vous aurez à saisir dans votre shell la commande suivante :

> migrate new « v2 »

Ceci aura pour effet de créer un fichier SQL censé contenir tout ce qui est nécessaire pour passer du schéma V1 au schéma V2.
Ce que vous aurez à mettre dans le script SQL qui porte un nom comme « 20121015122822_v2.sql » (présent dans le répertoire « scripts » de votre repository) est donc le script « changelog.sql ».

Étant donné que Liquibase et MyBatis Migrations sont deux outils de gestion de versions, pourquoi les utiliser conjointement ? Parce que d'une part comme je l'ai dit plus tôt les versions sont mal gérées avec Liquibase et d'autre part MyBatis Migrations ne gère pas les diff.

Je vous conseille donc d'appliquer le processus de gestion de schéma suivant dans vos projets :

1. Générez vos diff avec Liquibase ;

2. Corrigez le script généré en supprimant toute notion de versionnage crée par Liquibase (création de table DATABASECHANGELOG, etc.), réglez les problèmes de renommage, etc. ;

3. Créez une nouvelle version de votre schéma avec MyBatis Migrations via la commande ‘ migrate new « nom_de_la_version » ‘

Ainsi, si vous disposez d'un schéma (quelle que soit sa version) et d'un repository, la saisie dans un shell de la commande suivante :

> migrate up

vous permettra de le mettre à jour correctement. C'est-à-dire que seuls les scripts de diff (générés par Liquibase et corrigés par vous) nécessaires seront passés. Vous n'avez donc plus à vous préoccuper de la version de tel ou tel schéma en production pour savoir comment le mettre à jour.

VI - Conclusion

Avec cette méthode, les migrations de données prendront moins de temps, car une bonne partie du diff sera fait par Liquibase. Cela réduit d'autant le nombre d'erreurs possibles puisqu'une bonne partie du travail est désormais automatisée.

VII - Remerciements

Je tiens à remercier Keulkeul et Nemek pour leur relecture technique ainsi que ClaudeLELOUP pour sa relecture orthographique.