четверг, 16 августа 2012 г.

Работа с liquibase

Программы и приложения обладают одним интересным свойством склонностью к эволюционированию: появляются новые возможности, старые и ненужные фичи исчезают, меняются внешние интерфейсы и внутренние взаимосвязи. И если программа или приложение использует базу данных, то этот процесс неизменно сопровождается развитием и БД.
Для разработчиков развитие БД выливается в задачи по рефакторингу модели данных, отслеживанию изменений, синхронному обновлению баз и поддержанию совместимости версий; особенно это касается "боевых" приложений, для которых ошибка может стоит дорого. Как решать возникающие задачи? Можно полагаться на память. Можно связаться с текстовыми файлами, чтобы помечать в них, когда и какие поля в БД были изменены, и что нужно сделать на сервере БД заказчика, чтобы обновление версии программы прошло без проблем.
Механизмом для оптимизации этого сложного, но неизбежного процесса является инструмент liquibase, лозунг которого "Database Change Management" управление изменениями баз данных. Полное описание функциональности можно получить на сайте проекта; далее же в посте описываются некоторые конкретные возможности и процедуры использования данного инструмента с небольшими примерами.

Говорят, для quick startliquibase требуется всего 4 шага. Что ж, давайте шагать вместе.

QUICK START, шаг 1: файл изменений databasehangeLog

Основа liquibase файл изменений (changeLog). Это xml документ, описывающий все операции, которые необходимо выполнить над базой. Тэг databaseChangeLog исходная точка, в которой liquibase "понимает", что предложенный xml файл имеет нужный формат, и начинает этот файл парсить.
На практике бывает удобно поддерживать несколько файлов изменений например, по одному на каждый компонент/модуль приложения а затем сводить их воедино; для этого используется специальный тэг include.
Listing 1. Общая структура 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-2.0.xsd">

    <!-- что-то важное -->
   
</databaseChangeLog>
Далее я не буду приводить корневой тэг databaseChangeLog и все эти громоздкие подключения пространств имён; вместо этого я буду указывать только актуальное содержимое документа (то, что обозначено как "что-то важное").


QUICK START, шаг 2: набор изменений changeSet

<!-- что-то важное --> это то самое описание нужных изменений, ради которого инструмент и используется. Изменения описываются тэгами changeset. Фактически changeSet это набор изменений, способ группировки действий по рефакторингу.
В одном файле может располагаться любое количество changeSet'ов, в каждом из которых, в свою очередь, можно описывать любое число изменений. Тем не менее, liquibase best practise подсказывает, что каждый changeSet должен содержать минимально необходимое количество изменений. Дело в том, что liquibase пытается выполнять каждый набор в виде одной транзакции. Однако многие СУБД склонны по умолчанию подтверждать некоторые операции (create table или drop table, например). Поэтому в случае проблем большой changeSet может выполниться частично и оказаться в "подвешенном" состоянии.
Listing 2. Пример набора с изменениями
    <changeSet author="father" id="20120815-1">
        <createTable tableName="events">
            <column name="id" type="BIGINT" autoIncrement="true">
                <constraints primaryKey="true" nullable="false"/>
            </column>
            <column name="wascreated" type="DATETIME">
                <constraints nullable="false"/>
            </column>
            <column name="description" type="VARCHAR(255)"/>
        </createTable>
    </changeSet>

    <changeSet author="father" id="20120816-2">
        <comment>Of course we could use 'insert' tag instead of direct sql query</comment>
        <sql>insert into events values ('2012-08-15 00:00:00', 'It was cool!')</sql>
    </changeSet>
   
    <changeSet author="father" id="20120816-1">
        <comment>Next day we realized we should rename one column</comment>
        <renameColumn tableName="events" oldColumnName="wascreated" newColumnName="created_at"/>
    </changeSet>
Названия команд интуитивно понятны: createTable – создаёт таблицу, column – описывает создаваемые поля и т. д. В настоящее время доступны более 40 команд рефакторинга (линк на их полный список см. в разделе "ссылки").
Обратите внимание на тэг changeSet: в нём задается имя автора изменений и идентификатор изменений. Эти данные используются, чтобы однозначно идентифицировать набор (во-первых, это полезно разработчикам, а во-вторых, используется самим liquibase, но об этом позже). Если очень не хочется указывать имя автора - воспользуйтесь подстановкой UNKNOWN. В качестве id можно указывать какой-то удобный вам параметр (сам liquibase при автоматической генерации использует текущее системное время в миллисекундах, но это несколько неудобно для чтения человеком), например, дату и последовательный номер изменений в этот день.
Кроме набора изменений можно также определить условия, при которых эти изменений нужно (или не нужно) накатывать на БД, с помощью специального тэга preConditions. Можно проверить тип СУБД, выполняющего изменения пользователя, наличие таблиц/колонок/представлений/и пр., результат sql запроса, реализовать свой собственный механизм проверки и использовать для всего этого and/or/not логику.

QUICK START, шаг 3: запуск

Запуск созданного файла изменений возможен несколькими способами - через командную строку, инструменты ant/maven/grails, в виде сервлета, через плагин для intellij idea. Особенной разницы между ними нет - в библиотеке имеется один общий фасад (класс liquibase.Liquibase), а все остальные способы - фактически оболочки для него; впрочем, в зависимости от выбранного способа запуска могут быть доступны либо недоступны некоторые функции. В виде примера остановимся на запуске через cmd.
Listing 3. Запуск в командной строке
E:\liquibase\lib\liquibase>java -jar liquibase.jar --classpath="sqljdbc.jar" --databaseClass=liquibase.database.core.MSSQLDatabase --driver=com.microsoft.sqlserver.jdbc.SQLServerDriver --url=jdbc:sqlserver://test02:1433;databaseName=sandbox --username=sandbox_user --password=sandbox_pwd --changeLogFile=change1.xml --logLevel=INFO update

INFO 16.08.12 11:44:liquibase: Successfully acquired change log lock
INFO 16.08.12 11:44:liquibase: Creating database history table with name: [dbo].[DATABASECHANGELOG]
INFO 16.08.12 11:44:liquibase: Reading from [dbo].[DATABASECHANGELOG]
INFO 16.08.12 11:44:liquibase: Reading from [dbo].[DATABASECHANGELOG]
INFO 16.08.12 11:44:liquibase: ChangeSet change1.xml::20120815-1::father ran successfully in 0ms
INFO 16.08.12 11:44:liquibase: ChangeSet change1.xml::20120815-2::father ran successfully in 0ms
INFO 16.08.12 11:44:liquibase: ChangeSet change1.xml::20120816-1::father ran successfully in 0ms
INFO 16.08.12 11:44:liquibase: Successfully released change log lock
Liquibase Update Successful
Командная строка велика, но вполне читаема. Для того, чтобы liquibase мог подключиться к базе данных, необходимо указать путь к jar файлу с драйвером к БД (параметр --classpath). Затем указывается название драйвера (--driver) и строка адреса подключения (--url), а для аутентификации пользователя мы используем параметры --username и --password. Последний шаг – указать путь к xml файлу со сценарием изменения БД (--changeLogFile) и команду, которую должен выполнить liquibase (update).
Если писать столько разных параметров типа url, логин/пароль и пр. долго и нудно - выносите их в liquibase.properties. Файл должен быть в текущей рабочей директории (другое расположение можно задать через –defaultsFile). Для одинаковых ключей в файле и командной строке значение из cmd будет перекрывать значение из файла. Так что при условии вынесения настроек строка может выглядеть так:
java -jar liquibase.jar --changeLogFile=change1.xml update
Запустили, получили сообщение "Liquibase Update Successful"? Теперь запустите команду ещё раз. Запустили, получили то же сообщение, и никаких ошибок. Уже интересно.

QUICK START, шаг 4: проверка результата

Давайте посмотрим, что теперь находится в базе данных. Во-первых, создана таблица events с одной записью, причём с правильным именем колонки. Т. е. liquibase позволил нам в один день создать таблицу и наполнить её, а на следующий - изменить таблицу без потери данных! Во-вторых, оказывается, созданы две специальные таблицы.
Одна из них - databasechangelog (id, author, filename, dateexecuted, orderexecuted, exectype, md5sum, description, comments, tag, liquibase) - хранит историю всех изменений, сделанных в базе данных. Она позволяет отслеживать, кто, когда и какие изменения внёс в БД. Кроме того, именно эта таблица позволяет запускать один и тот же файл изменений многократно - будут выполнены только новые наборы. Реализуется это следующим образом. Как я уже рассказывал, каждый набор changeSet содержит данные об авторе и идентификатор набора. Эта информация сохраняется также в рассматриваемой таблице после успешного выполнения набора. Поэтому когда liquibase обрабатывает список наборов, он проверяет по "file+id+author", а выполнялся ли такой скрипт уже? Если нет - выполняет и сохраняет данные в своей таблице. Если совпадение найдено, проверяется контрольная сумма скрипта md5sum. Если она верная, значит, текущий набор не изменялся, и выполнять его больше не надо. А вот если неверная, значит, набор изменялся уже после его выполнения на данной БД, и гарантировать актуальность нельзя - liquibase сообщает об этом, формируя исключение (если вы точно уверены в том, что такое изменение набора оправдано, можете применить тэг validCheckSum).
Другая таблица - databasechangelock (id, locked, lockgranted, lockedby) - определяет пользователя, который заблокировал базу данных для внесения в неё изменений. Благодаря такой блокировке можно не опасаться того, что программа начнёт использовать БД до момента окончания её рефакторинга (например, в случае кластерного приложения, когда обновление запускается одним узлом, а второй узел может попытаться использовать БД).

РАЗНЫЕ ЗАМЕТКИ

Действительно, за 4 шага мы прошли от знакомства с liquibase до первого применения. Кроме операции update можно также:
  • автоматически сгенерировать наборы изменений по имеющейся БД - generateChangeLog;
  • создать документацию по базе данных с перечнем таблиц и их структурой, историей изменений в разрезе по таблицам и авторам, имеющимся changelogs и пр. - dbDoc;
  • контролировать изменения в скрипте - calculateCheckSum;
  • преобразовать xml представление liquibase'а в sql запросы - updateSQL;
  • выявить различия между БД - diff/diffChangeLog;
  • откатить выполненые изменения на определённую дату, на созданную ранее метку, на нужное количество выполненных изменений - rollback;
  • и др.
В общем, возможностей много - и уже реализованных/поддерживаемых инструментом, и тех, которые можно реализовать самостоятельно в соответствии со спецификой проекта. Обращайтесь к сайту проекта - и узнаете ещё много нового!

ОГРАНИЧЕНИЯ?
  • Невозможен экспорт (generateChangeLog) триггеров, хранимых процедур, функций.
  • Не всегда возможен автоматический откат изменений.
ССЫЛКИ
  • http://www.liquibase.org/  - стартовая страница.
  • Available database refactorings - полный перечень команд, существующих для рефакторинга - собственно, сама суть библиотеки, поэтому выношу в список ресурсов отдельно.
  • Hands-free database migration - статья уровня "introductory" для тех, кому удобнее знакомиться с инструментом на примерах (есть и переводная статья, правда, с достаточно плохим переводом). Статья объясняет, почему возникают проблемы при миграции БД, как помогает их решать liquibase, а также рассказывает основы рефакторинга с помощью этой библиотеки.
  • Database refactoring with liquibase и Liquibase - две небольшие презентации, которые помогут составить самое первое впечатление об инструменте всего за несколько минут.
  • liquibase в java с maven и Использование LiquiBase с Hibernate и Maven - примеры интеграции.
ЗАКЛЮЧЕНИЕ
  • Процесс разработки приложения неизменно сопровождается процессом развития базы данных.
  • Поэтому миграция БД, поддержание соответствия структуры БД логике приложения является, по сути, объективной задачей.
  • Ручная синхронизация БД, особенно при частых изменениях или необходимости сопровождения большого числа инстансов, требует значительных затрат времени, труда и средств.
  • liquibase - средство автоматизации таких операций, позволяющее создавать сценарии миграции баз данных и делать изменения частью автоматизированного процесса сборки. Является open-source проектом и поддерживает широкий перечень СУБД.
  • liquibase позволяет экспортировать структуру БД в файлы, рефакторить БД, вести историю сделанных изменений, откатывать внесённые изменения, работать с xml форматом либо с sql запросами (если это удобнее, например, DBA) и пр.
  • Существует интеграция liquibase с различными средствами интегрированной сборки и ide.
  • Инструмент умеет решать многие задачи, а также может быть достаточно просто расширен для решения кастомных проблем, что делает liquibase удобным в использовании.

Комментариев нет:

Отправить комментарий