Программы и приложения обладают одним интересным свойством – склонностью к эволюционированию: появляются новые возможности, старые и ненужные фичи исчезают, меняются внешние интерфейсы и внутренние взаимосвязи. И если программа или приложение использует базу данных, то этот процесс неизменно сопровождается развитием и БД.
Для разработчиков развитие БД выливается в задачи по рефакторингу модели данных, отслеживанию изменений, синхронному обновлению баз и поддержанию совместимости версий; особенно это касается "боевых" приложений, для которых ошибка может стоит дорого. Как решать возникающие задачи? Можно полагаться на память. Можно связаться с текстовыми файлами, чтобы помечать в них, когда и какие поля в БД были изменены, и что нужно сделать на сервере БД заказчика, чтобы обновление версии программы прошло без проблем.
Механизмом для оптимизации этого сложного, но неизбежного процесса является инструмент liquibase, лозунг которого – "Database Change Management" – управление изменениями баз данных. Полное описание функциональности можно получить на сайте проекта; далее же в посте описываются некоторые конкретные возможности и процедуры использования данного инструмента с небольшими примерами.
QUICK START, шаг 1: файл изменений databasehangeLog
Основа liquibase – файл изменений (changeLog). Это xml документ, описывающий все операции, которые необходимо выполнить над базой. Тэг databaseChangeLog – исходная точка, в которой liquibase "понимает", что предложенный xml файл имеет нужный формат, и начинает этот файл парсить.
На практике бывает удобно поддерживать несколько файлов изменений – например, по одному на каждый компонент/модуль приложения – а затем сводить их воедино; для этого используется специальный тэг include.
На практике бывает удобно поддерживать несколько файлов изменений – например, по одному на каждый компонент/модуль приложения – а затем сводить их воедино; для этого используется специальный тэг 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 может выполниться частично и оказаться в "подвешенном" состоянии.
В одном файле может располагаться любое количество 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 логику.
Обратите внимание на тэг 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 будет перекрывать значение из файла. Так что при условии вынесения настроек строка может выглядеть так:
Если писать столько разных параметров типа 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) - определяет пользователя, который заблокировал базу данных для внесения в неё изменений. Благодаря такой блокировке можно не опасаться того, что программа начнёт использовать БД до момента окончания её рефакторинга (например, в случае кластерного приложения, когда обновление запускается одним узлом, а второй узел может попытаться использовать БД).
Одна из них - 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 удобным в использовании.
Комментариев нет:
Отправить комментарий