Case sensitivity issue with liquibase change log table names

While starting a SpringBoot/JHipster MariaDB application, I ran into this Liquibase database update error:

liquibase.exception.MigrationFailedException: Migration failed for changeset config/liquibase/changelog/00000000000000_initial_schema.xml

This was not a fresh database initialization, but an upgrade of an existing databse wich was recently moved to a new MariaDB server. Yet the error pointed to the initialization changeset.

The issue came from the liquibase change log tables, named DATABASECHANGELOG and DATABASECHANGELOGLOCK, uppercase. Our database naming convention required lower case table names, hence the conversion to lower case of the liquibase change log table names. On the previous MariaDB Server, table names where case insensitive, this was not the case anymore on the new server, leading to the creation by Liquibase of two empty DATABASECHANGELOG and DATABASECHANGELOGLOCK tables, next to the databasechangelog and databasechangeloglock existing ones. Consequence: Liquibase was trying to initialize an already initialized database.

Here are three ways to solve this problem

Option 1 : Switch back to Liquibase default change log table names

That may not be elegant, but switching back to the default Liquibase change log table names will fix the issue, and prevent similar future issues. For example, when using SpringBoot, I had to rename the tables in two places : in the SpringBoot “application.yml” file, and in the Maven “pom.xml” file. That wouldn’t have been required would I have stuck to the default table names.

Option 2 : Disable case sensitivity for MariaDB table names

Changing the database configuration ensures that you won’t encounter the issue with any Liquibase application. However, when ever you change server you’ll have to enable this setting, and this may be an issue in the unlikely case where a database has two tables with the same name but different case.

The MariaDB ‘lower_case_table_names’ setting in my.cnf enables table name case insensitivity, by forcing all table names to be lowercase. To check if it’s enabled, use :

SHOW GLOBAL VARIABLES LIKE ‘lower_case_table_names’;

SHOW GLOBAL VARIABLES LIKE 'lower_case_table_names';

If ‘lower_case_table_names’ = 0, follow this procedure to change the parameter value in the MariaDB configuration files :

[mariadb]
lower_case_table_names=1

Option 3 : Specify Liquibase change log table names in SpringBoot application.properties file

Eventually, you can specify your Liquibase change log table names in your application.properties or application.yml file, as explained here, using the spring.liquibase.database-change-log-lock-table and spring.liquibase.database-change-log-table properties.

For example, in an application.yml file :

spring:
  liquibase:
    database-change-log-lock-table: 'databasechangeloglock'
    database-change-log-table: 'databasechangelog'

Gérer les mises à jour de sa base données SQL avec Liquibase

Dans les années 2000, pour résoudre des problèmes de mise à jour de base de données avec des scripts SQL à appliquer manuellement, dans un ordre précis, pour un type de base de données spécifique, en fonction d’une version de départ variable, j’ai développé un outil en C++ qui automatisait tout ce processus. Et ce qui devait être un outil développé rapidement a été beaucoup plus complexe que prévu.

Quelques années plus tard, société et application différentes mais même problématique. Cette fois-ci, c’est en Delphi que j’ai traité le problème, plus rapidement que la première fois, mais moins rapidement que ce que j’aurais imaginé.

Un problème rencontré deux fois en quelques années signifie qu’il est probablement commun. Et les problèmes communs ont toujours une solution standardisée, qui évite de réinventer la roue. En l’occurrence, cette solution s’appelle Liquibase.

Liquibase résout tous les problèmes auxquels on ne pense pas forcément quand on commence à écrire un petit script de mise à jour de la BD. Quelle sera la version du moteur SQL cible ? Est ce que je dois supporter plusieurs moteurs SQL ? Quelle sera la version initiale de la base de données à mettre à jour ? Comment gérer la migration des données ? Que faire en cas d’erreur pendant la mise à jour ? Comment identifier et fusionner les différences entre des évolutions concurrentes de la BD ? Etc.

Toutes ces réponses apportées à des questions auxquelle on n’a pas encore pensé quand on envisage de traiter le problème manuellement justifient la learning curve pour s’approprier Liquibase, en particulier l’organisation et le format des fichiers qui rendent les fichiers de mise à jour indépendants de la base de données cible.

Heureusement, il y a une forte communauté autour de l’outil, et des documentations claires et précises. En l’occurrence, il s’agissait d’adopter Liquibase pour un projet existant. Liquibase a une procédure pour cette situtation spécifique, ce qui ne dispense pas de lire au moins la présentation générale du fonctionnement de Liquibase

Mon environnement de dev s’appuie sur Maven, JAVA, SpringBoot. L’adoption de Liquibase se faisait dans un contexte plus général de migration de l’application vers le modèle JHipster. J’avais donc des scripts d’initialisation de la base de données générés pour le projet de demo JHipster.

Première étape, configurer le plugin Liquibase dans le POM.xml pour pointer vers la base de données à utiliser, et pour indiquer le package contenant toutes les entités :

<plugin>
	<groupId>org.liquibase</groupId>
	<artifactId>liquibase-maven-plugin</artifactId>
	<version>${liquibase.version}</version>
	<configuration>
		<changeLogFile>${project.basedir}/src/main/resources/config/liquibase/master.xml</changeLogFile>
		<outputChangeLogFile>${project.basedir}/src/main/resources/config/liquibase/changelog/00000000000000_initial_schema.xml</outputChangeLogFile>
		<diffChangeLogFile>${project.basedir}/src/main/resources/config/liquibase/changelog/${maven.build.timestamp}_changelog.xml</diffChangeLogFile>
		<driver>org.mariadb.jdbc.Driver</driver>
		<url>jdbc:mariadb://localhost:3310/bartleby</url>
		<defaultSchemaName></defaultSchemaName>
		<username>myUser</username>
		<password>myPassword</password>
		<referenceUrl>hibernate:spring:com.riousset.bartleby.persistence?dialect=org.hibernate.dialect.MariaDB103Dialect&amp;hibernate.physical_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy&amp;hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy</referenceUrl>
		<verbose>true</verbose>
		<logging>debug</logging>
		<contexts>!test</contexts>
	</configuration>
        ...

Les points clés : les propriétés driver, url, username, password concernent la base de données à mettre à jour. La propriété referenceUrl concerne le modèle de référence, en fonction duquel la BD devra être mise à jour. Dans l’exemple ci-dessous, je pointe vers le package contenant les entités JPA / Hibernate de mon application.

Seconde étape, générer le script d’initialisation de la base de données en fonction du schéma existant. Le changeLog sera généré dans le fichier indiqué par la propriété outputChangeLogFile :

./mvnw liquibase:generateChangeLog -Pprod

Troisième étape, maintenant que le changelog d’initialisation a été créé, il est possible d’initialiser un base de données vierge. Mais la mise à jour d’une base de données préexistante causerait des conflits. Liquibase offre la commande changeLogSync pour indiquer qu’un changeLog a déjà été exécuté sur un base. C’est un outil “bête”: changeLogSync ne vérifie pas que les scripts ont réellement été exécutés, il se contente d’indiquer dans la table DATABASECHANGELOG que tous les changeSet ont bien été exécutés :

./mvnw liquibase:changelogSync -Pprod -D"liquibase.changeLogFile=config/liquibase/changelog/00000000000000_initial_schema.xml"

Quatrième étape, on peut créer ou modifier des entités JPA, et laisser liquibase générer les scripts de mise à jour :

./mvnw liquibase:diff

Le fichier généré doit alors être ajouté au fichier master.xml. par exemple :

<?xml version="1.0" encoding="utf-8"?>
<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-latest.xsd">
    <property name="now" value="now()" dbms="h2"/>
    <property name="floatType" value="float4" dbms="h2"/>
    <property name="uuidType" value="varchar(36)" dbms="h2"/>
    <property name="datetimeType" value="datetime" dbms="h2"/>
    <property name="clobType" value="clob" dbms="h2"/>
    <property name="blobType" value="blob" dbms="h2"/>
    <property name="now" value="now()" dbms="mariadb"/>
    <property name="floatType" value="float" dbms="mariadb"/>
    <property name="clobType" value="clob" dbms="mariadb"/>
    <property name="blobType" value="longblob" dbms="mariadb"/>
    <property name="uuidType" value="varchar(36)" dbms="mariadb"/>
    <property name="datetimeType" value="datetime(6)" dbms="mariadb"/>

    <include file="config/liquibase/changelog/00000000000000_initial_schema.xml" relativeToChangelogFile="false"/>
	<include file="config/liquibase/changelog/20221004092650_changelog.xml" relativeToChangelogFile="false"/>
    <!-- jhipster-needle-liquibase-add-changelog - JHipster will add liquibase changelogs here -->
    <!-- jhipster-needle-liquibase-add-constraints-changelog - JHipster will add liquibase constraints changelogs here -->
    <!-- jhipster-needle-liquibase-add-incremental-changelog - JHipster will add incremental liquibase changelogs here -->
</databaseChangeLog>

Enfin, l’application des changements à une base de données se fait avec la commande :

./mvnw liquibase:update -Pprod

APIs de recherche et de consolidation d’adresses postales

La saisie d’adresses postales est problématique quand la détection des doublons est nécessaire. Dans le cadre d’une gestion de parc immobilier par exemple, il est indispensable de pouvoir détecter qu’un immeuble donné a déjà été renseigné dans l’application. Mais détecter les variations et surtout les erreurs de saisie (ex : “St Rémy” au lieu de “Saint Rémy de Provence”, “Av” au lieu de “Avenue”) est un problème beaucoup plus complexe que ce que l’intuition pourrait le laisser penser. Il est plus prudent de s’appuyer sur des outils dédiés à ces problématiques.

Dans le cadre de la solution NeoLegal, nous avons testé deux APIs :

  1. L’API Adresse du gouvernement français
  2. L’API Google Places

Les deux API sont simples d’utilisation et donnent des résultats très satisfaisants. Voici les principaux critères orientant le choix de l’une ou l’autre de ces solutions :

  1. L’API du gouvernement est limitée à des adresses françaises alors que celle de Google couvre la majorité des pays.
  2. L’API Google demande une authentification et est payante au delà d’un certain volume de données alors que celle du gouvernement est gratuite et ne demande pas d’authentification.
  3. L’API du gouvernement est une API seule, alors que celle de Google a déjà tout un écosystème de composants HTML prêts à être utilisés

Gérer les versions concurrentes de Node.JS avec NVM

Je ne suis pas un expert frontend, j’ai longtemps rechigné à me plonger dans cet aspect du développement parce que c’était le farwest, que je ne retrouvais pas le cadre, les structures, les patterns que j’apprécie côté backend.

Et puis est arrivé Node.JS, et Angular, et TypeScript, et j’ai réalisé que même avec du JavaScript sous le capot, on pouvait faire des applications riches et propres d’un point de vue code. (ceci-dit, je reste convaincu que le navigateur ne deviendra le nouvel OS que quand il y aura autre chose que du JavaScript sous le capot. A quand la généralisation du WebAssembly ?).

Mais après cette réconciliation sont venus les problèmes, et particulièrement celui de la gestion des versions de Node, de @Angular/cli, des librairies, quand on travaille sur plusieurs projets en simultanées, sur différentes branches. Les conflits de versions arrivent rapidement, on peut perdre des heures à résoudre des messages cryptiques et des problèmes de compatibilités.

Pour maîtriser la version de Node déployée pour un projet donné, sur Windows, la solution la plus simple est Node Version Manager (NVM) for Windows. L’outil permet de basculer d’une version de Node vers une autre en deux lignes de commandes :

nvm install 16.10.0 // pour installer la version de node
nvm use 16.10.0 // pour utiliser une version spécifique