Версионирование структуры БД с помощью Liquibase
При разработке большинства приложений используются разные системы контроля версий (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 — инструмент для управления миграциями БД. Используйте его для версионирования структуры вашей базы данных, это очень удобно, если над проектом работает целая команда разработчиков.
Я все не могу понять, каким именно образом создаются ченджлисты? Руками пишутся?
+1
первый раз генерируются через generateChangeLog, а потом да, проще руками. Но зато один и тот же ЧанджЛог можно применить к разным базам (например Тест и Прод)