Введение
Liquibase представляет из себя систему управления версиями базы данных, в основном это касается структуры и в меньшей степени содержимого базы. При этом описание базы с одной стороны достаточно абстрактно и позволяет использовать на нижнем уровне различные СУБД, и с другой стороны всегда можно перейти на SQL-диалект конкретной СУБД, что достаточно гибко. Liquibase является устоявшимся проектом с открытым исходным кодом и активно используется за пределами своей родной Java среды и не требует глубоких знаний Java для работы. В качестве описания структуры базы и изменений базы исторически использовался XML формат, однако сейчас параллельно поддерживается YAML и JSON.
В данной статье мы немного обобщим опыт предыдущих поколений и сосредоточимся на работе с Liquibase с использованием Maven. В качестве тестовой операционной системы будем использовать Ubuntu.
Другие статьи о Liquibase
- https://habr.com/en/post/179425/
- https://habr.com/en/post/178665/
- https://habr.com/en/post/333762/
- https://habr.com/en/post/251617/
- https://habr.com/en/post/251617/
Настройка окружения
Запускать Liquibase можно несколькими способами, однако наиболее удобно использовать Maven — по сути Java Make.
sudo apt install maven
mvn -version
В качестве Makefile здесь выступает 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 в существующий проект
- https://www.liquibase.org/documentation/existing_project.html
- https://www.liquibase.org/documentation/contexts.html
Как работают изменения базы
- https://www.liquibase.org/documentation/changeset.html
- https://www.liquibase.org/documentation/databasechangelog_table.html
Больше о формате изменений
- http://www.liquibase.org/documentation/json_format.html
- https://www.liquibase.org/documentation/changes/sql.html
- https://www.liquibase.org/documentation/changes/sql_file.html
- https://www.liquibase.org/documentation/column.html
Больше про update
Больше о генерации изменений
Больше о кастомном SQL
- http://www.liquibase.org/documentation/modify_sql.html
- https://stackoverflow.com/questions/28240068/create-column-of-type-double-precision-with-liquibase
Обработка типов данных специфичних для конкретной базы
<createTable tableName="t_name">
...
<column name="doubleArray" type="DOUBLE_ARRAY"/>
...
</createTable>
<modifySql dbms="postgresql">
<replace replace="DOUBLE_ARRAY" with="double precision[][]"/>
</modifySql>
Прочее
Автор: ki77roy