Работа с Liquibase всегда приносит уверенность в том, что объекты БД находятся в том состоянии, в котором ты этого хочешь. Именно эта уверенность и делает системы миграций баз данных столь популярными.
Сегодня я хочу вам рассказать, как сделать работу с liquibase немного удобнее. Если вы пишите миграции вручную, а вашей целевой базой данных является PostgreSQL, то прочитайте эту статью, уверен, время потраченное на чтение статьи окупится с лихвой.

Я надеюсь, что вы уже знакомы с работой Liquibase, поэтому лишь в двух словах опишу как работает система. Для того, чтобы внести изменения в БД, вам необходимо создать файл миграции (changeset), ссылку на который нужно будет указать в файле журнала изменений (changelog), после чего миграция может быть успешно применена к целевой БД. Неоспоримым и очевидным преимуществом подобного подхода является возможность выполнения отката сделанных изменений.
На мой взгляд, у liquibase есть лишь один существенный недостаток — автоматический откат изменений работает лишь только в том случае, если миграция описана в виде файла XML. Этот момент омрачает головы как разработчиков выполняющих изменения в БД, так и администраторов баз данных выполняющих инспекцию изменений. Разметка XML далеко не так хороша для восприятия, в отличие старого и хорошо всем известного SQL. Liquibase, к счастью, позволяет писать миграции и на SQL, только лишь с одним досадным недостатком — автоматический откат теперь не работает и секцию отката нужно писать вручную.
Сегодня мы исправим это недоразумение и научимся создавать файлы миграций для liquibase, во-первых на SQL, во-вторых с секцией отката, в-третьих эти файлы будут формироваться автоматически. Не верите? Сейчас я всё подробно расскажу.
Создавать миграции мы будем следующим образом — у нас будут две базы данных, одна будет условно для разработки, другая боевая (стейдж, тест — выберите по вкусу). Мы будем вносить изменения в девелоперскую БД используя любимый инструмент, потом сравним базы данных, сформируем миграцию, которую и развернем в целевой базе данных (у нас мы её будем считать боевой).
Все команды ниже будут отображены с учетом работы в Linux shell. Для генерации секции файлов миграции нам потребуется pgCodeKeeper, а для формирования секции rollback наличие в системе утилиты sed.
Давайте условимся, что у нас есть две базы данных — dbdev и dbprod, в dbdev мы вносим изменения вручную, а в dbprod изменения попадают с помощью Liquibase.
Я создам в /tmp каталог migration в котором и буду выполнять всю работу, разумеется вы можете использовать тот каталог, который пожелаете. PostgreSQL у меня уже установлен на локальном компьютере, и именно с ним я и буду проводить работу.
Создаём базы данных для работы:
Загружаем драйвера JDBC для PostgreSQL, дистрибутивы Liquibase и pgCodeKeeper
Разархивируем liquibase и pgcodekeeper в текущий каталог, вы конечно же распаковать их в выделенные каталоги.
В текущем каталоге создайте файл настроек для liquibase — liquibase.properties со следующим содержимым:
Обращаю внимание, что это настройки для Liquibase, т.е. описывайте в нём соединение с БД dbprod, на которую мы будем развертывать наши миграции. Имя пользователя и пароль установите в свои значения, у меня имя пользователя — ags и в дальнейших сообщения вывода команд будет встречаться как раз это имя. В текущем каталоге создаем файл db.changelog.xml со следующим содержимым:
Проверяем, готов ли liquibase к работе:
Отлично, половина дела сделано, осталось совсем малость. Проверяем, работает ли pgCodeKeeper.
если команда не выдала никакого сообщения — значит всё хорошо. Обратите внимание, если ваш инстанс постгреса настроен на другом хосте или порту или используется доступ по паролю, то вы должны сформировать соот��етствующую JDBC URL для вашей базы данных. Как правильно его сформировать читаем здесь.
Подготовительный этап завершен, базы созданы, инструменты настроены — можно приступать к основной части.
Создадим таблицу в БД dbdev:
Проверяем, может ли pgCodeKeeper найти отличия.
Да, всё работает так как и ожидалось. Подготавливаем скрипт создания миграций migrate.sh
Скрипт принимает один параметр — имя выходного файла, если имя файла не задано, то по умолчанию принимается changeset.sql. Далее формируется заголовок файла SQL, который необходим Liquibase для сохранения информации о выполненных изменениях на целевой БД. Далее идёт формирование собственно файла миграции.
Команда:
находит различия между базами, формирует файл преобразования структуры объектов БД из dbdev в dbprod и сохраняет в выходном файле.
А команда:
ищет различия между этими же базами только в другом направлении, целевой БД теперь становится девелоперская, для того, чтобы можно было выполнить автоматический откат изменений. Секция отката в Liquibase помечается комментариями --rollback.
Проверяем, формирование миграции. Для теста, в качестве выходного файла, я использую /dev/stdout:
Отлично, сформирована как секция наката, так и секция отката. А нам не пришлось для этого написать ни одной строки SQL вручную! Вся работа по созданию файла миграции была сделана автоматически.
Формируем реальный файл миграции
Подключаем его к журналу миграций, отредактировав файл db.changelog.xml, добавив в него директиву include с файлом 001_users.sql:
И накатываем изменения на dbprod, перед накатом изменений давайте выставим тег в целевой БД, чтобы мы могли откатиться в дальнейшем.
Проверяем статус
Откатываемся на состояние перед накатом таблицы
Проверяем статус повторно
Возвращаем выполненный предварительно накат
Давайте проверим, отличаются ли структуры баз данных сейчас? Теперь, после того как мы «поигрались» с накатами и откатами состояние баз данных dbdev и dbprod должно быть одинаковым. Так ли это?
pgCodeKeeper говорит, что в dbdev нет двух таблиц — databasechangeloglock и databasechangelog. Эти таблицы были созданы автоматически liquibase и в них он сохраняет информацию о выполненных накатах. Без этих таблиц работа liquibase будет невозможна. Чтобы эти таблицы в дальнейшем не мешали нам при формировании миграций можно или скопировать структуру этих таблиц в девелоперскую базу или использовать возможность списка игнорирования для pgCodeKeeper.
Для того, чтобы задействовать возможность по игнорированию объектов создайте файл .pgcodekeeperignore в текущем каталоге со следующим содержимым:
Теперь, команда
не должна отображать никаких изменений.
Итак, что нужно сделать, чтобы внести изменения по предложенной схеме:
Создание файлов миграций Liquibase теперь стало возможно выполнять автоматически. Миграции описаны на языке SQL и что немаловажно, секция отката создается также без участия человека. Самое сложное что нужно сделать, это придумать имя файла миграции.
P.S.: У Liquibase есть встроенный механизм формирования чейнджсета различий между двумя БД, к сожалению, по своему опыту могу сказать, что он работает не всегда хорошо, заменив этот механизм на код формируемый pgCodeKeeper можно формировать миграции для более сложных изменений в БД.
P.P.S.: Со времени написания статьи, размещение релизов CLI изменилось, теперь они находятся здесь: github.com/pgcodekeeper/pgcodekeeper/releases
Сайт обновлений для Eclipse находится здесь: pgcodekeeper.org/update
Сегодня я хочу вам рассказать, как сделать работу с liquibase немного удобнее. Если вы пишите миграции вручную, а вашей целевой базой данных является PostgreSQL, то прочитайте эту статью, уверен, время потраченное на чтение статьи окупится с лихвой.

Я надеюсь, что вы уже знакомы с работой Liquibase, поэтому лишь в двух словах опишу как работает система. Для того, чтобы внести изменения в БД, вам необходимо создать файл миграции (changeset), ссылку на который нужно будет указать в файле журнала изменений (changelog), после чего миграция может быть успешно применена к целевой БД. Неоспоримым и очевидным преимуществом подобного подхода является возможность выполнения отката сделанных изменений.
На мой взгляд, у liquibase есть лишь один существенный недостаток — автоматический откат изменений работает лишь только в том случае, если миграция описана в виде файла XML. Этот момент омрачает головы как разработчиков выполняющих изменения в БД, так и администраторов баз данных выполняющих инспекцию изменений. Разметка XML далеко не так хороша для восприятия, в отличие старого и хорошо всем известного SQL. Liquibase, к счастью, позволяет писать миграции и на SQL, только лишь с одним досадным недостатком — автоматический откат теперь не работает и секцию отката нужно писать вручную.
Сегодня мы исправим это недоразумение и научимся создавать файлы миграций для liquibase, во-первых на SQL, во-вторых с секцией отката, в-третьих эти файлы будут формироваться автоматически. Не верите? Сейчас я всё подробно расскажу.
Создавать миграции мы будем следующим образом — у нас будут две базы данных, одна будет условно для разработки, другая боевая (стейдж, тест — выберите по вкусу). Мы будем вносить изменения в девелоперскую БД используя любимый инструмент, потом сравним базы данных, сформируем миграцию, которую и развернем в целевой базе данных (у нас мы её будем считать боевой).
Все команды ниже будут отображены с учетом работы в Linux shell. Для генерации секции файлов миграции нам потребуется pgCodeKeeper, а для формирования секции rollback наличие в системе утилиты sed.
Давайте условимся, что у нас есть две базы данных — dbdev и dbprod, в dbdev мы вносим изменения вручную, а в dbprod изменения попадают с помощью Liquibase.
Я создам в /tmp каталог migration в котором и буду выполнять всю работу, разумеется вы можете использовать тот каталог, который пожелаете. PostgreSQL у меня уже установлен на локальном компьютере, и именно с ним я и буду проводить работу.
Создаём базы данных для работы:
$ mkdir /tmp/migration
$ cd /tmp/migration/
$ createdb dbdev
$ createdb dbprod
Загружаем драйвера JDBC для PostgreSQL, дистрибутивы Liquibase и pgCodeKeeper
$ wget https://jdbc.postgresql.org/download/postgresql-42.1.3.jar
$ wget https://github.com/liquibase/liquibase/releases/download/liquibase-parent-3.4.2/liquibase-3.4.2-bin.tar.gz
$ wget http://pgcodekeeper.ru/cli/release/pgCodeKeeper-cli-3.11.4.201707170702.zip
Разархивируем liquibase и pgcodekeeper в текущий каталог, вы конечно же распаковать их в выделенные каталоги.
Внимание! С момента написания статьи произошли некоторые изменения. CLI версию pgCodeKeeper можно скачать здесь: github.com/pgcodekeeper/pgcodekeeper/releases
$ tar xzvf liquibase-3.4.2-bin.tar.gz
$ unzip pgCodeKeeper-cli-3.11.4.201707170702.zip
В текущем каталоге создайте файл настроек для liquibase — liquibase.properties со следующим содержимым:
driver: org.postgresql.Driver
classpath: ./postgresql-42.1.3.jar
url: jdbc:postgresql:dbprod
username: user
password: topsecret
changeLogFile: db.changelog.xml
Обращаю внимание, что это настройки для Liquibase, т.е. описывайте в нём соединение с БД dbprod, на которую мы будем развертывать наши миграции. Имя пользователя и пароль установите в свои значения, у меня имя пользователя — ags и в дальнейших сообщения вывода команд будет встречаться как раз это имя. В текущем каталоге создаем файл db.changelog.xml со следующим содержимым:
<?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-3.1.xsd">
</databaseChangeLog>
Проверяем, готов ли liquibase к работе:
$ ./liquibase status
ags@jdbc:postgresql:dbprod is up to date
Liquibase 'status' Successful
Отлично, половина дела сделано, осталось совсем малость. Проверяем, работает ли pgCodeKeeper.
$ ./pgcodekeeper-cli.sh jdbc:postgresql:dbdev jdbc:postgresql:dbprodесли команда не выдала никакого сообщения — значит всё хорошо. Обратите внимание, если ваш инстанс постгреса настроен на другом хосте или порту или используется доступ по паролю, то вы должны сформировать соот��етствующую JDBC URL для вашей базы данных. Как правильно его сформировать читаем здесь.
Подготовительный этап завершен, базы созданы, инструменты настроены — можно приступать к основной части.
Создадим таблицу в БД dbdev:
[ags@saushkin-ag:/tmp/migration] $ psql dbdev
psql (9.6.3, сервер 9.5.7)
Введите "help", чтобы получить справку.
(ags@[local]:5432) 16:08:43 [dbdev] =# create table users (id serial primary key, name text);
CREATE TABLE
Время: 20,708 мс
(ags@[local]:5432) 16:09:16 [dbdev] * =# commit;
COMMIT
Время: 6,913 мс
Проверяем, может ли pgCodeKeeper найти отличия.
$ ./pgcodekeeper-cli.sh jdbc:postgresql:dbdev jdbc:postgresql:dbprod
CREATE SEQUENCE users_id_seq
START WITH 1
INCREMENT BY 1
NO MAXVALUE
NO MINVALUE
CACHE 1;
ALTER SEQUENCE users_id_seq OWNER TO ags;
CREATE TABLE users (
id integer DEFAULT nextval('users_id_seq'::regclass) NOT NULL,
name text
);
ALTER TABLE users OWNER TO ags;
ALTER TABLE users
ADD CONSTRAINT users_pkey PRIMARY KEY (id);
ALTER SEQUENCE users_id_seq
OWNED BY users.id;
Да, всё работает так как и ожидалось. Подготавливаем скрипт создания миграций migrate.sh
#!/bin/bash
FILENAME=${1:-changeset.sql}
# Заголовок файла миграции
echo "--liquibase formatted sql" > $FILENAME
echo "--changeset $USER:$FILENAME" >> $FILENAME
echo "" >> $FILENAME
# Создаем секцию наката
./pgcodekeeper-cli.sh jdbc:postgresql:dbdev jdbc:postgresql:dbprod >> $FILENAME
echo "" >> $FILENAME
# Создаем секцию отката (базы данных идут в обратном порядке)
./pgcodekeeper-cli.sh jdbc:postgresql:dbprod jdbc:postgresql:dbdev | sed -e 's/^/--rollback /' >> $FILENAME
Скрипт принимает один параметр — имя выходного файла, если имя файла не задано, то по умолчанию принимается changeset.sql. Далее формируется заголовок файла SQL, который необходим Liquibase для сохранения информации о выполненных изменениях на целевой БД. Далее идёт формирование собственно файла миграции.
Команда:
./pgcodekeeper-cli.sh jdbc:postgresql:dbdev jdbc:postgresql:dbprod >> $FILENAMEнаходит различия между базами, формирует файл преобразования структуры объектов БД из dbdev в dbprod и сохраняет в выходном файле.
А команда:
./pgcodekeeper-cli.sh jdbc:postgresql:dbprod jdbc:postgresql:dbdev | sed -e 's/^/--rollback /' >> $FILENAME
ищет различия между этими же базами только в другом направлении, целевой БД теперь становится девелоперская, для того, чтобы можно было выполнить автоматический откат изменений. Секция отката в Liquibase помечается комментариями --rollback.
Проверяем, формирование миграции. Для теста, в качестве выходного файла, я использую /dev/stdout:
$ chmod +x ./migrate.sh
$ ./migrate.sh /dev/stdout
--liquibase formatted sql
--changeset ags:/dev/stdout
CREATE SEQUENCE users_id_seq
START WITH 1
INCREMENT BY 1
NO MAXVALUE
NO MINVALUE
CACHE 1;
ALTER SEQUENCE users_id_seq OWNER TO ags;
CREATE TABLE users (
id integer DEFAULT nextval('users_id_seq'::regclass) NOT NULL,
name text
);
ALTER TABLE users OWNER TO ags;
ALTER TABLE users
ADD CONSTRAINT users_pkey PRIMARY KEY (id);
ALTER SEQUENCE users_id_seq
OWNED BY users.id;
--rollback ALTER TABLE users
--rollback DROP CONSTRAINT users_pkey;
--rollback
--rollback DROP TABLE users;
--rollback
Отлично, сформирована как секция наката, так и секция отката. А нам не пришлось для этого написать ни одной строки SQL вручную! Вся работа по созданию файла миграции была сделана автоматически.
Формируем реальный файл миграции
$ ./migrate.sh 001_users.sqlПодключаем его к журналу миграций, отредактировав файл db.changelog.xml, добавив в него директиву include с файлом 001_users.sql:
<?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-3.1.xsd">
<include file="001_users.sql" />
</databaseChangeLog>
И накатываем изменения на dbprod, перед накатом изменений давайте выставим тег в целевой БД, чтобы мы могли откатиться в дальнейшем.
$ ./liquibase tag 001_before_users
Successfully tagged ags@jdbc:postgresql:dbprod
Liquibase 'tag' Successful
$ ./liquibase migrate
Liquibase Update Successful
Проверяем статус
$ ./liquibase status
ags@jdbc:postgresql:dbprod is up to date
Liquibase 'status' Successful
Откатываемся на состояние перед накатом таблицы
$ ./liquibase rollback 001_before_users
Liquibase Rollback Successful
Проверяем статус повторно
$ ./liquibase status
1 change sets have not been applied to ags@jdbc:postgresql:dbprod
Liquibase 'status' Successful
Возвращаем выполненный предварительно накат
$ ./liquibase migrate
Liquibase Update Successful
Давайте проверим, отличаются ли структуры баз данных сейчас? Теперь, после того как мы «поигрались» с накатами и откатами состояние баз данных dbdev и dbprod должно быть одинаковым. Так ли это?
$ ./pgcodekeeper-cli.sh jdbc:postgresql:dbdev jdbc:postgresql:dbprod
ALTER TABLE databasechangeloglock
DROP CONSTRAINT pk_databasechangeloglock;
DROP TABLE databasechangeloglock;
DROP TABLE databasechangelog;
pgCodeKeeper говорит, что в dbdev нет двух таблиц — databasechangeloglock и databasechangelog. Эти таблицы были созданы автоматически liquibase и в них он сохраняет информацию о выполненных накатах. Без этих таблиц работа liquibase будет невозможна. Чтобы эти таблицы в дальнейшем не мешали нам при формировании миграций можно или скопировать структуру этих таблиц в девелоперскую базу или использовать возможность списка игнорирования для pgCodeKeeper.
Для того, чтобы задействовать возможность по игнорированию объектов создайте файл .pgcodekeeperignore в текущем каталоге со следующим содержимым:
$ cat .pgcodekeeperignore
SHOW ALL
HIDE REGEX "databasechangelog.*"Теперь, команда
$ ./pgcodekeeper-cli.sh -I .pgcodekeeperignore jdbc:postgresql:dbdev jdbc:postgresql:dbprodне должна отображать никаких изменений.
Итак, что нужно сделать, чтобы внести изменения по предложенной схеме:
- Вносим изменения в девелоперскую БД
- С помощью нашего скрипта формируем миграцию
- Инспектируем миграцию и подключаем её в журнал миграций
- Деплоим изменения на боевую (тест, стейдж) БД
- В случае необходимости, делаем откат
Создание файлов миграций Liquibase теперь стало возможно выполнять автоматически. Миграции описаны на языке SQL и что немаловажно, секция отката создается также без участия человека. Самое сложное что нужно сделать, это придумать имя файла миграции.
P.S.: У Liquibase есть встроенный механизм формирования чейнджсета различий между двумя БД, к сожалению, по своему опыту могу сказать, что он работает не всегда хорошо, заменив этот механизм на код формируемый pgCodeKeeper можно формировать миграции для более сложных изменений в БД.
P.P.S.: Со времени написания статьи, размещение релизов CLI изменилось, теперь они находятся здесь: github.com/pgcodekeeper/pgcodekeeper/releases
Сайт обновлений для Eclipse находится здесь: pgcodekeeper.org/update
