Liqubase и Maven

в 16:41, , рубрики: java, sql, versioning

Введение

Liquibase представляет из себя систему управления версиями базы данных, в основном это касается структуры и в меньшей степени содержимого базы. При этом описание базы с одной стороны достаточно абстрактно и позволяет использовать на нижнем уровне различные СУБД, и с другой стороны всегда можно перейти на SQL-диалект конкретной СУБД, что достаточно гибко. Liquibase является устоявшимся проектом с открытым исходным кодом и активно используется за пределами своей родной Java среды и не требует глубоких знаний Java для работы. В качестве описания структуры базы и изменений базы исторически использовался XML формат, однако сейчас параллельно поддерживается YAML и JSON.

В данной статье мы немного обобщим опыт предыдущих поколений и сосредоточимся на работе с Liquibase с использованием Maven. В качестве тестовой операционной системы будем использовать Ubuntu.

Другие статьи о Liquibase

Настройка окружения

Запускать Liquibase можно несколькими способами, однако наиболее удобно использовать Maven — по сути Java Make.

sudo apt install maven
mvn -version

В качестве Makefile здесь выступает pom.xml — он уже содержит все необходимые зависимости, настройки и профили.

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.test.db</groupId>
    <artifactId>db</artifactId>
    <version>1.0.0</version>
    <name>db</name>
    <description>Test Database</description>
    <packaging>pom</packaging>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <slf4j.version>1.7.24</slf4j.version>
        <logback.version>1.2.3</logback.version>
        <liquibase.version>3.6.2</liquibase.version>
        <postgresql.version>42.2.5</postgresql.version>
        <snakeyaml.version>1.23</snakeyaml.version>
    </properties>
    <dependencies>
        <!--Logging-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>log4j-over-slf4j</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>${logback.version}</version>
        </dependency>

        <!--JDBC drivers-->
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <version>${postgresql.version}</version>
        </dependency>
        <dependency>
            <groupId>org.liquibase</groupId>
            <artifactId>liquibase-core</artifactId>
            <version>${liquibase.version}</version>
        </dependency>
        <dependency>
            <groupId>org.yaml</groupId>
            <artifactId>snakeyaml</artifactId>
            <version>${snakeyaml.version}</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.liquibase</groupId>
                <artifactId>liquibase-maven-plugin</artifactId>
                <version>${liquibase.version}</version>
                <configuration>
                    <propertyFile>${profile.propertyFile}</propertyFile>
                    <changeLogFile>${profile.changeLogFile}</changeLogFile>
                    <dataDir>${profile.dataDir}</dataDir>
                    <!-- log  -->
                    <verbose>${profile.verbose}</verbose>
                    <logging>${profile.logging}</logging>
                    <promptOnNonLocalDatabase>false</promptOnNonLocalDatabase>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <profiles>
      <!-- Development settings, -Denv=dev -->
      <profile>
        <id>dev</id>
        <activation>
          <property>
            <name>env</name>
            <value>dev</value>
          </property>
        </activation>
        <properties>
          <profile.propertyFile>dev/liquibase.properties</profile.propertyFile>
          <profile.changeLogFile>dev/master.xml</profile.changeLogFile>
          <profile.dataDir>dev/data</profile.dataDir>
          <profile.verbose>true</profile.verbose>
          <profile.logging>debug</profile.logging>
        </properties>
      </profile>
      <!-- Production settings, -Denv=prod -->
      <profile>
        <id>prod</id>
        <activation>
          <property>
            <name>env</name>
            <value>prod</value>
          </property>
        </activation>
        <properties>
          <profile.propertyFile>prod/liquibase.properties</profile.propertyFile>
          <profile.changeLogFile>prod/master.xml</profile.changeLogFile>
          <profile.dataDir>prod/data</profile.dataDir>
          <profile.verbose>false</profile.verbose>
          <profile.logging>info</profile.logging>
        </properties>
      </profile>
    </profiles>
</project>

Запускаем обновление

После того как мы сделали pom.xml можно запускать обновление базы — команда liquibase:update.

Для этого нам потребуются:

  • liquibase.properties файл с настройками соединения к базе (логин/пароль и возможно другие параметры)
  • xml файл с изменениями базы
  • sh скрипт запуска обновления базы

Файл с настройками соединения к базе

liquibase.properties

username=test
password=test
referenceUsername=test
#можно задавать и другие параметры
#url=jdbc:postgresql://dev/test
#referenceUrl=jdbc:postgresql://dev/test_reference

Файл с изменениями базы

Основным понятием liquibase являются так называемые изменения базы (changesets). Они могут включать в себя как изменения структуры так и изменение данных. Для контроля примененных изменений liquibase использует таблицы databasechangelog и databasechangeloglock.

<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.6.xsd">

    <changeSet context="legacy" author="author (generated)" id="1">
        <createTable tableName="test">
            <column autoIncrement="true" name="id" type="SERIAL">
                <constraints nullable="false"/>
            </column>
            <column name="user_name" type="VARCHAR(255)"/>
            <column name="preferences" type="TEXT"/>
        </createTable>
    </changeSet>
</databaseChangeLog>

Скрипт запуска обновления базы

Здесь выполняется liquibase:update для профиля dev и базы из liquibase.url, которая указана в стандартном JDBC формате. После обновления в базе появляется указанная в changeSet таблица и две служебные таблицы databasechangelog и databasechangeloglock.

#!/usr/bin/env bash
mvn liquibase:update
    -Denv=dev
    -Dliquibase.url="jdbc:postgresql://dev/test?prepareThreshold=0&stringtype=unspecified"

Генерация SQL без обновления базы

Иногда перед запуском изменений требуется посмотреть содержимое создаваемых запросов. Для этого предназначены команды liquibase:updateSQL и liquibase:rollbackSQL.

#!/usr/bin/env bash
mvn liquibase:updateSQL
    -Denv=dev
    -Dliquibase.url="jdbc:postgresql://dev/test?prepareThreshold=0&stringtype=unspecified" > /tmp/script.sql

Подробнее о changeSet

Изменения могут быть в разных форматах, в том числе обычный sql или он же в отдельном файле.

Каждое изменение может включать секцие rollback позволяющую откатывать изменения командой liqubase:rollback. Кроме того для маркировки изменений, например для более удобного отката туда, можно использовать tagDatabase.

Обычный формат

<changeSet context="legacy" author="author (generated)" id="1">
    <createTable tableName="test">
        <column autoIncrement="true" name="id" type="SERIAL">
            <constraints primaryKey="true" primaryKeyName="test_pkey"/>
        </column>
        <column name="c1" type="VARCHAR(255)"/>
        <column name="c2" type="INTEGER"/>
        <column name="c3" type="SMALLINT"/>
        <column name="c4" type="VARCHAR(255)"/>
        <column name="c5" type="TEXT"/>
        <column name="c6" type="VARCHAR(255)"/>
    </createTable>
</changeSet>

Встроенный SQL

<changeSet context="legacy" author="author" id="1-domain-some-domain">
    <sql>
        CREATE DOMAIN public.some_domain AS bigint;
        ALTER DOMAIN public.some_domain OWNER TO test;
    </sql>
    <rollback>
        DROP DOMAIN public.some_domain;
    </rollback>
</changeSet>

Файл SQL

<changeSet context="legacy" author="author" id="1-user">
     <sqlFile dbms="postgresql" path="sql/some.sql" relativeToChangelogFile="true" />
     <rollback> delete from "some"; </rollback>
</changeSet>

Тэги

<changeSet context="legacy" author="author" id="1-initial-changeset">
    <tagDatabase tag="initial"/>
</changeSet>

Контексты запуска

Для более удобного управления различными конфигурациями, например development/production можно использовать контексты. Контекст указывается в changeSet аттрибуте context и затем запускается Maven параметром -Dcontexts.

Изменение с контекстом

<changeSet context="legacy" author="author" id="1-initial-changeset">
    <tagDatabase tag="initial"/>
</changeSet>

Запуск изменений по контексту

#!/usr/bin/env bash
mvn liquibase:update
    -Denv=dev
    -Dliquibase.url="jdbc:postgresql://dev/test?prepareThreshold=0&stringtype=unspecified"
    -Dliquibase.contexts=non-legacy

Откат изменений

Операция обратная обновлению, в большинстве случаев поддерживается автоматически. Для прочих возможно задание через секцию rollback. Запускается командой liquibase:rollback.

Изменение с откатом

<changeSet context="legacy" author="author" id="1-domain-some-domain">
    <sql>
        CREATE DOMAIN public.some_domain AS bigint;
        ALTER DOMAIN public.some_domain OWNER TO test;
    </sql>
    <rollback>
        DROP DOMAIN public.some_domain;
    </rollback>
</changeSet>

Запуск отката

#!/usr/bin/env bash
mvn liquibase:update
    -Denv=dev
    -Dliquibase.url="jdbc:postgresql://dev/test?prepareThreshold=0&stringtype=unspecified"
    -Dliquibase.contexts=non-legacy

Сравнение

В разработке удобно использовать для сравнения двух существующих баз на предмет внесённых изменений. В настройки (или параметры запуска) потребуется добавить ссылку на референсную базу и данные для доступа к ней.

liquibase.properties

referenceUsername=test
referenceUrl=jdbc:postgresql://dev/test_reference

Сравнение схем

Сравнение схем url и referenceUrl.

#!/usr/bin/env bash
mvn liquibase:diff
    -Denv=dev
    -Dliquibase.referenceUrl="jdbc:postgresql://dev/test?prepareThreshold=0"
    -Dliquibase.url="jdbc:postgresql://dev/test_reference?prepareThreshold=0"
    -Dliquibase.diffChangeLogFile=dev/diff.xml

Сохранение схемы

Также бывает полезно сохранить текущую схему базы, с данными или без.

Сохранение схемы без учёта данных

Сохранение схемы существующей базы.

#!/usr/bin/env bash
mvn liquibase:generateChangeLog
    -Denv=dev
    -Dliquibase.url="jdbc:postgresql://dev/test_reference?prepareThreshold=0"
    -Dliquibase.outputChangeLogFile=dev/changelog.xml

Сохранение схемы с данными

Сохранение схемы существующей базы с данными.

#!/usr/bin/env bash
mvn liquibase:generateChangeLog
    -Denv=dev
    -Dliquibase.url="jdbc:postgresql://dev/test_reference?prepareThreshold=0"
    -Dliquibase.outputChangeLogFile=dev/changelog.xml

Ограничения и проблемы

Работа с бинарными данными в базе

Существуют отпределенные проблемы с выгрузкой, сравнением и применением бинарных данных, в частности проблема с генерацией изменений.

Исходный код

Альтернативные решения

Flyway

Наряду с Liquibase пользуется популярностью в Java сообществе — http://flywaydb.org/documentation

Sqitch

Аналог на Perl — http://sqitch.org

FluentMigrator

Аналог для .Net — https://github.com/schambers/fluentmigrator

DBGeni

Аналог для Ruby — http://dbgeni.appsintheopen.com/manual.html

Приложения

Структура проекта

pom.xml - maven makefile
  dev
    liquibase.properties - login/password etc
    master.xml - changesets

Как добавить liquibase в существующий проект

Как работают изменения базы

Больше о формате изменений

Больше про update

Больше о генерации изменений

Больше о кастомном SQL

Обработка типов данных специфичних для конкретной базы

<createTable tableName="t_name">
...
<column name="doubleArray" type="DOUBLE_ARRAY"/>
...
</createTable>
<modifySql dbms="postgresql">
<replace replace="DOUBLE_ARRAY" with="double precision[][]"/>
</modifySql>

Прочее

Автор: ki77roy

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js