Работа с 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