пятница, 17 августа 2012 г.

liquibase servlet listener: пример и настройка

liquibase может быть запущен (среди прочих способов) с помощью servlet listener. А значит, можно автоматизировать запуск liquibase и приурочить проверку/обновление БД к моменту деплоя приложения. Далее в посте рассматривается пример того, как "подружить" liquibase и сервер приложений jboss, настроить эту связку и сделать так, чтобы миграция выполнялась при запуске приложения.


ЗАДАЧА

Итак, пусть у нас имеется приложение, стартуемое на сервере приложений jboss (именно этот сервер выбран исключительно в виде примера; настройка на других серверах ничем особенным не отличается). Для обеспечения миграции и обновлений базы данных применяется инструмент liquibase. Желательно настроить сервер так, чтобы процесс миграции выполнялся автоматически каждый раз при старте.

LIQUIBASE BEST PRACTICES

Предлагаемые  полезные практики по организации работы заключаются в следующем:
  • в проекте заводится отдельный модуль/пакет для хранения всех относящихся к liquibase артефактов (файлов изменений, классов с кастомной реализацией процедур миграции и пр.);
  • изменения группируются в пакеты; именование пакетов отражает содержимое (например, соответствует id заявок в таск-трекере); пакеты содержат файлы изменений, а также все необходимые классы, если используется CustomChange;
  • размещаемый в корне application-liquibase.xml не содержит тэгов changeSet, а только подключает все имеющиеся файлы изменений в правильном порядке (здесь утверждается, что изменения выполняются именно в том порядке, в котором подключены файлы).
Listing 1. Пример структуры пакета
by.father.examples
    liquibase
        change1
            changelog.xml
        change2
            changelog.xml
        change3
            changelog.xml
        application-liquibase.xml
Подобный подход облегчает процесс работы: выполнены доработки приложения, требующие изменений в БД -> формируется отдельный пакет (с id заявки) с changelog.xml и всеми относящимися к делу артефактами -> этот пакет включается в общий список в определённую последовательность. В результате всегда можно понять, кто, когда, почему и какие изменения внёс.
Listing 2. Содержимое application-liquibase.xml для данного примера
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog/2.0"
                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                   xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog/2.0  http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-2.0.xsd">

    <include file="by/father/examples/liquibase/change1/changelog.xml"/>
    <include file="by/father/examples/liquibase/change2/changelog.xml"/>
    <include file="by/father/examples/liquibase/change3/changelog.xml"/>

</databaseChangeLog>
PUT EVERYTHING IN ITS RIGHT PLACES
  • Компилируем и собираем наши артефакты (в данном примере пакет by.father.examples.liquibase заархивирован в application-liquibase.jar).
  • Собираем war-архив (в данном примере father-liquibase.war). В WEB-INF\lib\ помещаем наш application-liquibase.jar, а дескриптор развёртывания WEB-INF\web.xml настраиваем следующим образом:
Listing 3. Содержимое WEB-INF\web.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
    <distributable/>

    <context-param>
        <param-name>liquibase.changelog</param-name>
        <!-- размещение общего списка с файлами изменений -->
        <param-value>by/father/examples/liquibase/application-liquibase.xml</param-value>
    </context-param>

    <context-param>
        <param-name>liquibase.datasource</param-name>
        <!-- ссылка на настроенный датасурс -->
        <param-value>datasource</param-value>
    </context-param>

    <listener>
        <!-- класс сервлета -->
        <listener-class>liquibase.integration.servlet.LiquibaseServletListener</listener-class>
    </listener>   
</web-app>
  • Помещаем father-liquibase.war в каталог deploy\, а liquibase.jar к библиотекам в каталог lib\.
  • Стартуем сервис и ищем сообщения об успешной работе:
Listing 4. Записи в логе при успешном старте
INFO  [TomcatDeployer] deploy, ctxPath=/father-liquibase, warUrl=.../tmp/deploy/tmp6299631334826712061father-liquibase-exp.war/
ERROR [STDERR] INFO 16.08.12 13:46:liquibase: Successfully acquired change log lock
ERROR [STDERR] INFO 16.08.12 13:46:liquibase: Creating database history table with name: [dbo].[DATABASECHANGELOG]
ERROR [STDERR] INFO 16.08.12 13:46:liquibase: Reading from [dbo].[DATABASECHANGELOG]
ERROR [STDERR] INFO 16.08.12 13:46:liquibase: Reading from [dbo].[DATABASECHANGELOG]
ERROR [STDERR] INFO 16.08.12 13:46:liquibase: ChangeSet by/father/examples/liquibase/change1/changelog.xml::20120815-1::father ran successfully in 0ms
ERROR [STDERR] INFO 16.08.12 13:46:liquibase: ChangeSet by/father/examples/liquibase/change2/changelog.xml::20120815-2::father ran successfully in 0ms
ERROR [STDERR] INFO 16.08.12 13:46:liquibase: ChangeSet by/father/examples/liquibase/change3/changelog.xml::20120816-1::father ran successfully in 0ms
ERROR [STDERR] INFO 16.08.12 13:46:liquibase: Successfully released change log lock
ПРИМЕЧАНИЯ
  • По умолчанию liquibase логирует в stderr, т. ч. в логах приложения появляются строки вида "ERROR [STDERR] INFO ... :liquibase: ..." и т. д. Это мешает анализу логов и, например, может вызвать ложное срабатывание системы мониторинга (если она отслеживает логи). Для liquibase 2.x имеется расширение liquibase-slf4j (Slf4j Logging for Liquibase) - архив в 3 килобайта, который нужно просто добавить в classpath. Он перенаправляет вывод на slf4j. Для данного примера будет "INFO  [liquibase] ..." как нормальный вывод в лог, безо всякого ERROR.
  • Если окажется, что liquibase servlet listener подходит вам по концепции, но по каким-то причинам не подходит по реализации - всегда можно расширить его и сделать, например, class MyLiquibaseServletListener extends liquibase.integration.servlet.LiquibaseServletListener. В этом сервлете можно описать требуемое поведение. Скомплированный класс затем помещается в WEB-INF\classes\, а в дескрипторе web.xml указывается нужный сервлет:
    <listener-class>by.father.examples.liquibase.MyLiquibaseServletListener</listener-class>
  • В процессе миграции данных ряд задач может выполняться продолжительное время. Поэтому запуск таких задач при редеплойменте приложения недопустим в условиях его промышленного использования. Такие задачи должны выполняться в фоновом режиме без остановки сервера приложений в часы его наименьшей загрузки. Определение времени наименьшей загрузки системы и запуск миграции данных выполняется службой поддержки.

ЗАКЛЮЧЕНИЕ

Возможность запуска liquibase через сервлет позволяет автоматизировать и синхронизировать обновление БД с деплоем приложения на сервере, а реализуется это достаточно простыми и понятными настройками.

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

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