В процессе работы над очередным проектом в команде возникли споры по поводу использования формата XML или SQL в Liquibase. Естественно про Liquibase уже написано много статей, но как всегда хочется добавить свои наблюдения. В статье будет представлен маленький туториал по созданию простенького приложения с бд и рассмотрим разницу метаинформации по данным типам.

Liquibase — это независимая от базы данных библиотека для отслеживания, управления и применения изменений схем базы данных. Для того, чтобы внести изменения в БД, создается файл миграции (*changeset*), который подключается в главный файл (*changeLog*), который контролирует версии и управляет всеми изменениями. В качестве описания структуры и изменений базы данных используется XML, YAML, JSON и SQL форматы.

Основная концепция миграций БД, выглядит следующим образом:



Более подробную информацию по Liquibase можно узнать тут или тут. Надеюсь общая картинка ясна, так что перейдем к созданию проекта.

В тестовом проекте используется


  • Java 8
  • Spring-boot
  • Maven
  • H2
  • ну и сам liquibase

Создание проекта и зависимости


Использование Spring-boot здесь ничем не обусловлено, можно обойтись просто maven-plugin для накатывания скриптов. Итак, приступим.

1. Создаем в IDE maven-проект и в pom файл добавляем следующие зависимости:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.liquibase</groupId>
        <artifactId>liquibase-core</artifactId>
        <version>3.6.3</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
   <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
        </dependency>
</dependencies>

2. В папке resources создать файл application.yml и добавить следующие строчки:

spring:
  liquibase:
    change-log: classpath:/db/changelog/db.changelog-master.yaml
 datasource:
    url: jdbc:h2:mem:test;
    platform: h2
    username: sa
    password:
    driverClassName: org.h2.Driver
  h2:
    console:
      enabled: true

Строка liquibase:change-log: classpath:/db/changelog/db.changelog-master.yaml — сообщает нам, где находится файл со списком скриптов liquibase.

3. В папке resources по пути db.changelog-master создаем cследующие файлы:

  • xmlSchema.xml – скрипт изменений в формате xml
  • sqlSchema.sql — скрипт изменений в формате sql
  • data.xml – добавление данных в таблицу
  • db.changelog-master.yml – список changeSet –ов

4. Добавление данных в файлы:
Для теста необходимо создать две несвязанные т
аблицы и минимальный набор данных.
В файл sqlSchema.sql добавляем всеми известный sql синтаксис:

--liquibase formatted sql
--changeset TestUsers_sql:1

CREATE TABLE test_sql_table
(
    name VARCHAR NOT NULL,
    description VARCHAR
);

--changeset TestUsers_sql:2

CREATE TABLE test_sql_table_2
(
    name VARCHAR NOT NULL,
    description VARCHAR
);

Использование sql в качестве changeset-ов обуславливается легким написанием скриптов. В файлах всем понятный, обычный sql.

Для разделения changeset используется комментарий:
--changeset TestUsers_sql:1 с указанием номера изменений и фамилии
(параметры можно посмотреть тут.)

В файл xmlSchema.sql добавляем DSL, который предоставляет liquibase:


<?xml version="1.0" encoding="UTF-8"?>

<databaseChangeLog
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
        xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.6.xsd">

    <changeSet id="Create table test_xml_table" author="TestUsers_xml">

        <createTable tableName="test_xml_table">
            <column name="name" type="character varying">
                <constraints primaryKey="true" nullable="false"/>
            </column>
            <column name="description" type="character varying"/>
        </createTable>

    </changeSet>

    <changeSet id="Create table test_xml_table_2" author="TestUsers_xml">

        <createTable tableName="test_xml_table_2">
            <column name="name" type="character varying">
                <constraints primaryKey="true" nullable="false"/>
            </column>
            <column name="description" type="character varying"/>
        </createTable>

    </changeSet>
</databaseChangeLog>

Данный формат описания создания таблиц является универсальным для разных баз данных. Прям как лозунг джавы: «Написано однажды, работает везде». Liquibase использует xml-описание и компилирует его в определенных sql код, в зависимости от выбранной базы данных. Что очень удобно, для общих параметров.

Каждая операция выполняется в отдельном changeSet, с указанием id и имени автора. Думаю, язык, используемый в xml, очень прост к пониманию и даже не требует объяснения.

5. Загрузим в наши таблички данные, это делать не обязательно, но раз уж сделали таблички, надо в них чего-нить положить. Заполняем файл data.xml, следующими данными:

<?xml version="1.0" encoding="UTF-8"?>

<databaseChangeLog
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
        xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.6.xsd">

    <changeSet id="insert data to test_xml_table" author="TestUsers">
        <insert tableName="test_xml_table">
            <column name="name" value="model"/>
            <column name="description" value="какая-либо модель"/>
        </insert>
    </changeSet>

    <changeSet id="insert data to test_xml_table_2" author="TestUsers">
        <insert tableName="test_xml_table_2">
            <column name="name" value="model"/>
            <column name="description" value="какая-либо модель"/>
        </insert>
    </changeSet>

    <changeSet id="insert data to test_sql_table" author="TestUsers">
        <insert tableName="test_sql_table">
            <column name="name" value="model"/>
            <column name="description" value="какая-либо модель"/>
        </insert>
    </changeSet>

    <changeSet id="insert data to test_sql_table_2" author="TestUsers">
        <insert tableName="test_sql_table_2">
            <column name="name" value="model"/>
            <column name="description" value="какая-либо модель"/>
        </insert>
    </changeSet>

</databaseChangeLog>

Файлы для накатывания таблиц созданы, данные для таблиц созданы. Пришло время объединить все это в общий порядок накатывания и запустить наше приложение.

В файл db.changelog-master.yml добавляем наши sql и xml файлы:

databaseChangeLog:
  - include:
      # schema
      file: db/changelog/xmlSchema.xml
  - include:
      file: db/changelog/sqlSchema.sql
      # data
  - include:
      file: db/changelog/data.xml

И теперь, когда у нас все создано. Просто запустим наше приложение. Для запуска можно использовать командную строку или плагин, но мы создадим просто main метод и запустим наше SpringApplication.

Просмотр метаинформации


Теперь, когда мы запустили наши два скриптика на создание и заполнение таблиц, мы можем посмотреть таблицу databaseChangeLog и увидеть, что накатилось.



Результат накатывания xml:

  1. В поле id от файлов xml попадает заголовок, который разработчик указывает в changeSet, каждый отдельный changeSet является отдельной строчкой в базе с указанием заголовка и описания.
  2. Указывается автор каждого изменения.

Результат накатывания sql:

  1. В поле id от файлов sql нет подробной информации об changeSet.
  2. Не указывается автор каждого изменения.

Еще одно важное заключение в сторону использования xml – это rollback. Такие команды как create table, alter table, add column имеют автоматический откат, при использовании xml. Для sql файлов каждый rollback необходимо писать вручную.

Вывод


Каждый выбирает для себя сам, что ему использовать. Но наш выбор пал все таки на сторону xml. Подробная метаинформация и легкий переход на другие БД, перевесили чашу весов от всеми любимого sql-формата.

Комментарии (11)


  1. puyol_dev2
    08.09.2019 22:14

    Свойство nullable для primaryKey=«true» — это прекрасно. Вообще в xml варианте явно избыточность информации. За это его очень не любят


  1. gyesa
    08.09.2019 23:50
    +1

    Хм, в SQL указываются те же атрибуты, что и для XML: и автора можно, и что там будет ещё нужно. Так что не аргумент :)


  1. solairerove
    09.09.2019 12:37

    Между XML и SQL в ликвибейзе выберу YAML


  1. TheSlavuti4
    09.09.2019 12:38

    Активно используем liquibase в своем проекте (крупный enterprise). Могу сказать, что использование changeset-ов с XML возможно только для небольших приложений с довольно простой структурой бд. Как только появляется необходимость сложной миграции с кучей таблиц, вьюх, запросами к другим бд через dblink — без нативного SQL не обойтись.
    Самая главная проблема — отладка. Гораздо легче руками написать в каком нибудь GUI запрос, проверить и добавить в changeset, чем мучаться с XML, возможности которого сильно ограничены.
    Единственный минус — все rollback нужно писать руками, а это не всегда возможно. Но у нас редко возникает необходимость откатить состояние бд до прошлых версий


  1. Slavuti4
    09.09.2019 12:47

    Активно используем liquibase в своем проекте (крупный enterprise). Могу сказать, что использование changeset-ов с XML возможно только для небольших приложений с довольно простой структурой бд. Как только появляется необходимость сложной миграции с кучей таблиц, вьюх, запросами к другим бд через dblink — без нативного SQL не обойтись.
    Самая главная проблема — отладка. Гораздо легче руками написать в каком нибудь GUI запрос, проверить и добавить в changeset, чем мучаться с XML, возможности которого сильно ограничены.
    Единственный минус — все rollback нужно писать руками, а это не всегда возможно. Но у нас редко возникает необходимость откатить состояние бд до прошлых версий


    1. iFebrity Автор
      09.09.2019 13:00

      чем мучаться с XML, возможности которого сильно ограничены — нуу не совсем, у него огромная поддержка инструкций. На мой взгляд, даже намного читабельные, чем просто файл с SQL


  1. dporollo
    09.09.2019 12:50

    Для инита достаточно data.sql Древний и проверенный способ. А в статье извините описан мегакостыль, к которому еще вопросы есть. Что мешает создать схему, и заполнять ее с ORM? Зачем создавать себе сложности, и героически их решать?


    1. iFebrity Автор
      09.09.2019 13:01

      data.sql — ну такое. Это если проект у тебя домашний какой-нибудь)


    1. Borz
      09.09.2019 15:19

      тогда уж schema.sql+data.sql


      но Liquibase не про инициирование БД, а про её миграции


      1. iFebrity Автор
        10.09.2019 15:49

        Да нее, ну надо же было что-нибудь положить в бд) Вот dporollo и не понравился мой подход


  1. pserega
    11.09.2019 12:16

    Спасибо за статью.
    Но мне кажется лучше использовать гибрид (xml/yaml) + чистый sql в файлах (mtm_property_tickler.sql)
    SCDB — супер-пользователь для запуска (знает только ответственный DevOps)
    MOBILE — премонтированная папка со скриптами для каждой схемы и нужной структурой с которой работает разработчик DB.
    Вот пример

    databaseChangeLog:  
      - changeSet:
          id: tag-7.1
          author: sergey_p
          context : dev,preprod
          changes:
            - tagDatabase:
                  tag: 7.1
      - changeSet:
          id: sql_recomp_rollback
          author: sergey_p
          context : dev,preprod,prod
          changes:
          rollback:
            - sqlFile:
                  dbms: oracle
                  encoding: utf8
                  path: MOBILE/recomp.sql
                  relativeToChangelogFile: true
                  splitStatements: false
                  stripComments: false
      - changeSet:
          id: sql_file_insert
          author: sergey_p
          context : dev,preprod,prod
          changes:
            - sql:
                  dbms: oracle
                  sql: ALTER SESSION SET CURRENT_SCHEMA = MOBILE
            - sqlFile:
                  dbms: oracle
                  encoding: cp1251
                  path: MOBILE/data/mtm_property_tickler.sql
                  relativeToChangelogFile: true
                  splitStatements: true
                  stripComments: false
          rollback:
            - sql:
                  dbms: oracle
                  sql: ALTER SESSION SET CURRENT_SCHEMA = MOBILE
            - sqlFile:
                  dbms: oracle
                  encoding: cp1251
                  path: MOBILE/data/rollback/mtm_property_tickler.sql
                  relativeToChangelogFile: true
                  splitStatements: true
                  stripComments: false
            - sql:
                  dbms: oracle
                  sql: ALTER SESSION SET CURRENT_SCHEMA = SCDB
      - changeSet:
          id: sql_file_body
          author: sergey_p
          context : dev,preprod,prod
          changes:
            - sql:
                  dbms: oracle
                  sql: ALTER SESSION SET CURRENT_SCHEMA = MOBILE
            - sqlFile:
                  dbms: oracle
                  encoding: cp1251
                  path: MOBILE/packages/body/sale_to_billing.sql
                  relativeToChangelogFile: true
                  splitStatements: false
                  stripComments: false
          rollback:
            - sql:
                  dbms: oracle
                  sql: ALTER SESSION SET CURRENT_SCHEMA = MOBILE
            - sqlFile:
                  dbms: oracle
                  encoding: cp1251
                  path: MOBILE/packages/body/rollback/sale_to_billing.sql
                  relativeToChangelogFile: true
                  splitStatements: false
                  stripComments: false
            - sql:
                  dbms: oracle
                  sql: ALTER SESSION SET CURRENT_SCHEMA = SCDB
      - changeSet:
          id: sql_recomp
          author: sergey_p
          context : dev,preprod,prod
          changes:
            - sqlFile:
                  dbms: oracle
                  encoding: utf8
                  path: MOBILE/recomp.sql
                  relativeToChangelogFile: true
                  splitStatements: false
                  stripComments: false
          rollback: