22 ноября 2021
Тэги: gradle, Kotlin, PostgreSQL, Spring Boot, SQL, yaml.
Liquibase позволяет автоматизировать внесение обновлений в структуру БД. Каждое изменение описывается в декларативном стиле и версионируется. Обновления накатываются в заранее определённом порядке на данную БД, если они ещё не накатывались. Автоматизация процесса наката изменений на базу данных особенно важна, если у вас несколько различных экземпляров приложений и для каждого из них требуется поддерживать свою БД.
Данный материал также доступен в формате видео на YouTube.
Рассмотрим работу с Liquibase на конкретном примере. С помощью Spring Initializr создадим заготовку нашего Spring Boot приложения (выбираем в качестве языка kotlin, а в качестве сборщика – gradle). В dependencies выберем компоненты Spring Web (функциональность rest-контроллеров), Spring Data JDBC (работа с БД), PostgreSQL Driver (драйвер нашей СУБД) и сам Liquibase Migration. В итоге файл build.gradle.kts в секции dependencies должен содержать следующие зависимости:
Обратите внимание, что драйвер postgres добавился в gradle как runtimeOnly – это означает, что для компиляции он не требуется, т.к. мы лишь пропишем его в файле настроек.
Для подключения к БД добавим следующие настройки в файл application.yml (не забудьте подставить свои параметры):
Параметр spring.liquibase.change-log в явном виде указывает расположение файла изменений liquibase. В данном случае он должен лежать в папке resources/liquibase/. Можно и не указывать этот параметр в настройках. Тогда изменения надо будет хранить в файле resources/db/changelog/db.changelog-master.yaml.
Предположим, наше приложение является блогом, в котором основная сущность – это статья. У каждой статьи должен быть уникальный идентификатор, url, по которому можно найти её в блоге, заголовок, признак видимости для пользователей и дата создания. Статьи будут храниться в таблице article в БД. Для создания таблиц будем использовать liquibase. Можно было бы сделать и наоборот: сначала создать таблицу в БД вручную, а затем импортировать её структуру в liquibase. Но я рекомендую делать создание именно через liquibase – так у вас будет больше контроля над всеми нюансами.
Изменения, описанные в файле liquibase, будут накатываться при старте приложения. Если изменение уже накатывалось, повторного наката не будет. Историю изменений конкретной БД liquibase хранит в служебной таблице databasechangelog, которая будет создана при первом запуске приложения. Работа с этой таблицей происходит автоматически.
Для внесения изменения в БД требуется добавить новый changeSet в файл liquibase, который в декларативном стиле описывает необходимые изменения.
Теперь давайте создадим таблицу article. Добавим в файл changelog.yml первый набор изменений (changeSet) в формате yaml. Помимо yaml liquibase также поддерживает формат xml и json. Но мы будем рассматривать именно yaml как наиболее современный и компактный. В нём важно соблюдать правильное количество пробелов в отступах и не забывать про пробелы перед значениями после двоеточий.
Файл changelog начинается с корневого элемента databaseChangeLog. Затем мы создаём changeSet, у которого есть текстовый идентификатор (id), автор (author) и список изменений (changes). В качестве идентификатора можно указывать любую произвольную строку, но если вы работаете, например, с Jira, то таким идентификатором может быть номер задачи. В качестве автора можете указать ваш логин или email.
Инструкция createTable содержит название таблицы (tableName), описание таблицы (remarks) и набор колонок (columns). Для каждой колонки нужно обязательно указывать имя (name) и тип (type). При необходимости описание колонки также можно указывать через remarks (см. пример для поля url), а ограничения можно указать в constraints. Название типа указывается аналогично таковому названию в sql. Параметр autoIncrement позволяет создавать идентификаторы, значение которых увеличивается на 1 при добавлении каждой новой записи в таблицу. Флаг primaryKey позволяет указать, что именно это поле вы хотите сделать первичным ключом в таблице. Параметр primaryKeyName позволяет в явном виде задать имя данного ограничения. Делать это необязательно, но я настоятельно рекомендую давать имена для любых ограничений по определённым правилам.
Параметр nullable отвечает за допустимость значений null в данной колонке. Если nullable=false, то колонка в БД получит модификатор not null. По умолчанию nullable=true, т.е. null в ячейке допускается.
У каждой статьи должен быть уникальный url, по которому её можно найти в нашем блоге. Поэтому для url добавим специальное ограничение unique=true, а также дадим этому ограничению имя с помощью параметра uniqueConstraintName.
Статья может быть ещё не до конца написана, тогда её не следует отображать посетителям нашего блога. Поэтому добавим в таблицу поле is_visible и по умолчанию оно будет равно false. Значение по умолчанию позволяет задавать параметр defaultValue.
Наконец, у статьи есть время создания. По умолчанию при вставке будем проставлять текущее время. Для этого в качестве значения defaultValue укажем функцию now().
Теперь при запуске приложения liquibase создаст в нашей БД таблицу article, DDL которой приведён ниже:
Допустим, через некоторое время мы захотели навести порядок в нашем блоге и каждую статью поместить в какой-нибудь раздел. Для этого потребуется:
Добавим в файл changelog.yml второй changeSet:
Для создания таблицы chapter используем уже знакомый нам тип изменений createTable. В этой таблице разделов будет всего два поля: id и title. Также определяем тут primary key, как и в первой таблице.
После создания этой таблицы нам нужно наполнить её названиями разделов. Для этого будем использовать инструкцию insert, которая на уровне БД будет преобразована в обычный insert into.
Для вставки данных мы указываем имя таблицы и колонки, для каждой из которых указывается её имя и значение. В данном примере две инструкции insert дадут нам две строки в таблице chapter.
Наконец, свяжем таблицу article с таблицей chapter через внешний ключ (foreign key).
Инструкция addColumn добавляет новое поле chapter_id в таблицу article. Инструкция addForeignKeyConstraint вешает на это поле ограничение типа «внешний ключ». baseTableName – таблица, в которой находится внешний ключ. baseColumnNames – имя колонки для внешнего ключа. referencedTableName – имя таблицы, на которую ссылается таблица с внешним ключом. referencedColumnNames – имя колонки, на которую ссылается внешний ключ (чаще всего это первичный ключ id). Наконец, constraintName позволяет указать имя для внешнего ключа.
Теперь, если мы снова запустим приложение, то будет создана вторая таблица chapter. В неё будут вставлены две записи, а затем в таблицу article будет добавлен первичный ключ chapter_id.
В процессе описания изменений легко ошибиться или что-то забыть. В случае ошибки на каком-либо шаге все изменения в этом changeSet будут отменены, так что можно не опасаться за сломанную базу. Если же вы написали скрипт правильно, но про что-то забыли, то лучше отменить («откатить») изменения. Для этого просто аккуратно вручную отмените ВСЕ внесенные в данном changeSet изменения (например, удалите только что созданную таблицу или поле). Затем удалите соответствующую запись из таблицы databasechangelog, которая связана с этими изменениями. После этого вы можете повторно накатить изменения, как будто они накатываются в первый раз.
Как вы сами убедились, liquibase позволяет в декларативном стиле описывать все изменения, которые вы хотите произвести со структурой вашей БД. Liquibase абстрагируется от синтаксиса конкретной СУБД и потому может работать с любой из них (главное предоставить нужный драйвер). При этом синтаксис выглядит несколько многословным, но вы можете выбрать тот формат, который вам больше всего подходит: xml, json или yaml.
Но самое главное преимущество liquibase – это автоматизация процесса наката изменений в БД. С увеличением количества экземпляров вашего приложения и серверов для них это становится особенно актуально.
Kotlin, Java, Spring, Spring Boot, Spring Data, SQL, PostgreSQL, Oracle, Linux, Hibernate, Collections, Stream API, многопоточность, файлы, Nginx, Apache, maven, gradle, JUnit, YouTube, новости, руководство, ООП, алгоритмы, головоломки, rest, GraphQL, Excel, XML, json, yaml.
26.08.2022 12:17 Stanislav
Спасибо!
17.10.2023 06:12 Nur
Спасибо! очень полезный гайд
06.04.2024 11:31 мяу
спасибо