Версионирование структуры БД с помощью Liquibase

14-08-16 Разное Liquibase, MySql 3

При разработке большинства приложений используются разные системы контроля версий (Git, Mercurial и т.п.). А что насчет структуры баз данных? Очень часто изменения базы данных на тестовых и боевых серверах делаются вручную. Такой подход может не вызывать сложностей, если над проектом работает один или два разработчика, но если работает целая команда, становится трудно синхронизировать все изменения между разработчиками.

В этой статье мы рассмотрим Liquibase, открытую (open source) систему для управления миграциями БД. Liquibase помогает организовать процесс внесения изменений в схему БД, каждая миграция будет содержать описание изменений, необходимых для перехода от старой ревизии к новой.

Liquibase не единственная система для управления миграциями, существует много других, например, Doctrine 2 migrations, Rails AR migrations, DBDeploy и т.д. Но некоторые из них платформо-зависимые, некоторые не обладают таким широким функционалом. Также серьезный недостаток многих систем — невозможность применения некоторых изменений без потери данных, например, переименование столбца произойдет как две операции: drop + add, что приведет к потере данных.

Как работает Liquibase

Liquibase — кросс платформенное Java приложение, это значит, что вы можете скачать JAR файл и использовать его на Windows, Mac или Linux.

При использовании Liquibase изменения структуры базы данных будут храниться в отдельных файлах (changelogs), поддерживаются форматы XML, YAML, JSON или SQL, что очень удобно. Изменения можно хранить в одном или множестве файлов с последующем включением в основной файл. Второй вариант предпочтительнее, т.к. позволяет гибко организовать применение и хранение чейнджлогов.

В файлах-чейнджлогах изменения представляются в виде чейнджсетов (changesets). Чейнджсет может содержать одно или несколько изменений базы данных. Каждый changeset может быть уникально идентифицирован с помощью атрибутов id и author. Liquibase создает таблицу databasechangelog в базе данных для отслеживания примененных чейнджсетов. При каждом запуске Liquibase будет проверять хэш суммы чейнджлогов со значениями в таблице и изменения будут применяться, если еще не применялись или если используется параметр runAlways.

Начало работы

Для примера создадим базу данных application на локальном  MySql сервере, также создадим чейнджлог. Этот файл лучше хранить в папке проекта, т.к. он должен быть версионирован. Создадим первый пустой changelog:

databaseChangeLog:
  - preConditions:
    - runningAs:
        username: root

В качестве языка описания изменений будем использовать YAML, но вы можете использовать любой из доступных, большой разницы нет.

В командной строке перейдите в папку с Liquibase и введите следующую команду:

java -jar liquibase.jar --driver=com.mysql.jdbc.Driver--classpath=lib/mysql-connector-java-5.1.21-bin.jar --changeLogFile=/path/to/changelog.yaml --url="jdbc:mysql://localhost/application" --username=dbuser --password=secret update

В ответ вы увидите примерно следующее:

INFO 15.08.14 17:43: liquibase: Successfully acquired change log lock
INFO 15.08.14 17:43: liquibase: Reading from application.DATABASECHANGELOG
INFO 15.08.14 17:43: liquibase: Successfully released change log lock
Liquibase Update Successful

Все параметры в введенной нами команды, кроме classpath — обязательны. driver — определяет имя класса для дравера базы данных, которую мы используем. changeLogFile — путь к файлу чейнджлога. url — определяет строку для соединения JDBC с базой данных, она содержит тип сервера, хост, и имя базы данных. classpath — путь к дополнительным классам, например, драйвер для соединения с базой.

Вместо того, чтобы каждый раз использовать команду с таким большим количеством параметров, можно использовать специальный файл настроек Java — liquibase.properties. Далее вы можете просто использовать команды без параметров:

java -jar liquibase.jar <command>

Файл настроек будет выглядеть так:

#liquibase.properties
driver: com.mysql.jdbc.Driver
classpath: lib/mysql-connector-java-5.1.21-bin.jar
url: jdbc:mysql://localhost/application
changeLogFile: changelog.yaml
username: root
password:

Создадим таблицу users с полями id, name, email. Для этого добавим чейнджсет в файл changelog.yaml:

databaseChangeLog:
  - preConditions:
    - runningAs:
        username: root
  - changeSet:
      id: users-table-1
      author: root
      changes:
        - createTable:
            tableName: users
            columns:
              - column:
                  name: id
                  type: int
                  constraints:
                    primaryKey: true
                    nullable: false
              - column:
                  name: name
                  type: varchar(50)
                  constraints:
                    nullable: false
              - column:
                  name: email
                  type: varchar(128)
                  constraints:
                    nullable: false
                    unique: true

Тег createTable содержит атрибут tableName и список необходимых полей. Для описания полей используется отдельный тег column с обязательными атрибутами name и type. У тега column есть много полезных атрибутов, которые используются редко, например авто инкремент можно задать с помощью атрибута autoIncrement: true.

Ограничения можно создавать с помощью специального тега constraints. В нашем примере колонка id имеет ограничения первичного ключа и not null, поле email — уникальное.

Запустим пример и посмотрим на результат:

java -jar liquibase.jar update

INFO 15.08.14 23:30: liquibase: Successfully acquired change log lock
INFO 15.08.14 23:30: liquibase: Reading from application.DATABASECHANGELOG
INFO 15.08.14 23:30: liquibase: changelog.yaml: changelog.yaml::users-table-1::root: Table users created
INFO 15.08.14 23:30: liquibase: changelog.yaml: changelog.yaml::users-table-1::root: ChangeSet changelog.yaml::users-table-1::root ran successfully in 12ms
INFO 15.08.14 23:30: liquibase: Successfully released change log lock
Liquibase Update Successful

В результате в базе появится следующая таблица:

*************** 1. row ***************
	Field: id
	Type: int(11)
	Null: NO
	Key: PRI
	Default: NULL
	Extra: 
*************** 2. row ***************
	Field: name
	Type: varchar(50)
	Null: NO
	Key: 
	Default: NULL
	Extra: 
*************** 3. row ***************
	Field: email
	Type: varchar(128)
	Null: NO
	Key: UNI
	Default: NULL
	Extra: 

Вы наверняка обратили внимание, что  у поля id нет авто инкремента. Это можно исправить в следующем чейнджсете:

  • Добавим колонке id авто инкремент
  • Переименуем поле name на fullname
  • Добавим новую колонку age
  - changeSet:
      id: users-table-2
      author: root
      changes:
        - addAutoIncrement:
            columnDataType: int
            columnName: id
            tableName: users
        - addColumn:
            tableName: users
            columns:
              - column:
                  name: age
                  type: smallint
        - renameColumn:
            tableName: users
            oldColumnName: name
            newColumnName: fullname
            columnDataType: varchar(100)

Снова запустим Liquibase и взглянем на таблицу users. Вы увидите два измененных поля и новую колонку age.

Откат изменений (rollback)

До сих пор мы только изменяли схему нашей базы данных. Но что если нам понадобится отменить изменения? Это легко сделать с помощью команды rollback.

Многие изменения, такие как createTable, renameColumn, откатываются автоматически. Но вы можете написать свли сценарии для отката. Можно откатиться на определенное количество чейнджсетов или к определенному тегу.

liquibase rollbackCount 1

Liquibase позволяет создавать чекпоинты или теги для каждого состояния БД с помощью команды:

liquibase tag checkpoint_1

в дальнейшем вы сможете откатить базу командой:

liquibase rollback checkpoint_1

Если вам надо посмотреть на SQL, который был сгенерирован для отката, замените команды rollback на rollbackSql rollbackCount на rollbackCountSql. Результат будет выведен на экран, но вы можете указать файл для вывода.

  - changeSet:
      id: users-table-3
      author: root
      changes:
        - modifyDataType:
            tableName: users
            columnName: fullname
            newDataType: text
      rollback:
        - dropTable:
            tableName: users

Проверим что получилось:

java -jar liquibase.jar rollbackCountSql 1

INFO 16.08.14 16:09: liquibase: Successfully acquired change log lock
INFO 16.08.14 16:09: liquibase: Reading from application.DATABASECHANGELOG
INFO 16.08.14 16:09: liquibase: null: changelog.yaml::users-table-3::root: Rolling Back Changeset:changelog.yaml::users-table-3::root
INFO 16.08.14 16:09: liquibase: Successfully released change log lock
--  *********************************************************************
--  Rollback 1 Change(s) Script
--  *********************************************************************
--  Change Log: changelog.yaml
--  Ran at: 16.08.14 16:09
--  Against: root@localhost@jdbc:mysql://localhost/application
--  Liquibase version: 3.2.2
--  *********************************************************************

--  Lock Database
--  Rolling Back ChangeSet: changelog.yaml::users-table-3::root
DROP TABLE application.users;

DELETE FROM application.DATABASECHANGELOG  WHERE ID='users-table-3' AND AUTHOR='root' AND FILENAME='changelog.yaml';

--  Release Database Lock
Liquibase Rollback Successful

Проверка условий

Иногда перед применением изменений нужно проверить некоторые условия, например, проверить, что таблица пустая, перед её удалением. Для этого используется тег preConditions.

  - changeSet:
      id: users-table-4
      author: root
      preConditions:
        - sqlCheck:
            expectedResult: 0
            sql: select count(*) from users
      changes:
        - dropTable:
            tableName: users

В этом случае Liquibase сначала проверит пустоту таблицы и только потом её удалит.

Заключение

В этой статье мы рассмотрели Liquibase — инструмент для управления миграциями БД. Используйте его для версионирования структуры вашей базы данных, это очень удобно, если над проектом работает целая команда разработчиков.

Хочешь получать статьи на почту?

Подпишись на обновления!
* Ваш email не будет разглашен/продан. Вы сможете отписаться в любое время.

3 Комментария

  1. Briareos says:

    Я все не могу понять, каким именно образом создаются ченджлисты? Руками пишутся?

    1. Artem says:

      первый раз генерируются через generateChangeLog, а потом да, проще руками. Но зато один и тот же ЧанджЛог можно применить к разным базам (например Тест и Прод)

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *