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.
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;
}
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 :
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 :
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;
}
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 =
8204815540254878355
L;
@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 :
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.
#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 ».
<?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 :
<?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é :
-- *********************************************************************
-- 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 :
## 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 :
- Générez vos diff avec Liquibase ;
- 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. ;
- 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.