Использование Liquibase для управления структурой БД в Spring Boot приложении. Часть 1

в 12:09, , рубрики: java, liquibase, liquibase-maven-plugin, rdbms, spring, Программирование

В этой статье я разберу использование утилиты Liquibase в Spring Boot приложениях для версионирования структуры реляционной БД и миграции этой структуры с одной версии на другую. В первой части разберем базовый пример, а во второй поговорим об использовании liquibase-mave-plugin для отката изменений и автоматической генерации скриптов через сравнение структур БД.

Начнем с того, что создадим простейшее приложение на Spring Boot + JPA (Hibernate). В этом нам поможет Spring Initializr. Из зависимостей выбираем JPA, MySQL и Web. Liquibase тоже можно подключить на этом шаге, но для лучшего понимания мы это сделаем далее вручную.

Создаем основу приложения

Добавим в наше приложение класс сущности, а также репозиторий и REST-контроллер для работы с ней. Для конкретности, будем хранить в создаваемой сущности информацию о пользователях.

@Entity
@Table(name = "users")
public class User implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long id;

    @Column(name = "username", unique = true, nullable = false)
    private String userName;

    @Column(name = "password", nullable = false)
    private String password;

    @Column(name = "first_name")
    private String firstName;

    @Column(name = "last_name")
    private String lastName;

    @Column(name = "email")
    private String email;

    // Далее конструктор по умолчанию, геттеры и сеттеры
    // Или можете использовать Lombok
}

Spring Data позволяет сделать код репозитория чрезвычайно кратким

public interface UserRepository extends JpaRepository<User, Long> {
}

REST-контроллер, который будет выдавать все содержимое таблицы пользователей

@RestController
public class UserController {
    
    private UserRepository userRepository;

    @Autowired
    public UserController(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @GetMapping("/user/all")
    public List<User> allUsers() {
        return userRepository.findAll();
    }
}

Настройки в файл application.properties

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/geek_db?createDatabaseIfNotExist=true&allowPublicKeyRetrieval=true&useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=dbuser
spring.datasource.password=dbpassword

spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect

Предполагается, что на вашем компьютере запущен сервер MySQL на стандартном порту. При необходимости, подкорректируйте URL сервера в строке соединения, а также логин и пароль. Также стоит обратить внимание на параметр createDatabaseIfNotExist. Благодаря нему мы будем при подключении создавать базу данных с именем geek_db, если её нет на сервере.

Добавляем Liquibase

Наверняка вы обратили внимание, что не хватает одной настройки для hibernate, а именно spring.jpa.hibernate.ddl-auto. В большинстве руководств для начинающих для нее указывают значение update, благодаря которому Hibernate будте сама создавать и корректировать структуру таблиц на сервере, основываясь на присутствующих в проекте классах сущностей. Такой подход вполне может быть использован, если схема данных очень простая или проект учебный, но при сколь-нибудь сложной схеме скорее всего начнутся проблемы хотя бы из-за того, что мы никак не можем контролировать процесс генерации Hibernate-ом DDL скриптов. Ещё одна проблемная состоит в том, что при таком подходе нет простого способа откатить сделанные Hibernate изменения в структуре БД.

Именно для решения выше описанных проблем мы и будем использовать утилиту Liquibase. На наше счастье, она отлично умеет интегрироваться со Spring Boot приложениями! Чтобы начать её использовать, необходимо выполнить следующие действия

Добавляем в файл application.properties настройку

spring.jpa.hibernate.ddl-auto=none

Это нужно для того, чтобы Hibernate не выполнял никаких действий по модификации схемы, т.к. теперь их будет делать Liquibase. Теоретически, тут можно использовать еще и значение validate для дополнительного контроля правильности структуры таблиц.

Добавляем в pom.xml зависимость

<dependency>
    <groupId>org.liquibase</groupId>
    <artifactId>liquibase-core</artifactId>
</dependency>

После её добавления Spring boot автоматически будет создавать специальный бин с именем liquibase, который при каждом запуске приложения будет выполнять все действия по настройке схемы БД на основе скриптов Liquibase.

Теперь необходимо добавить сам Liquibase скрипт, который будет создавать нужную нам таблицу. Создаем в папке /src/main/resources/db/changelog файл с именем db.changelog-master.yaml и добавляем в него следующее содержимое

databaseChangeLog:
  - logicalFilePath: db/changelog/db.changelog-lesson1.yaml
  - changeSet:
      id: 1
      author: your_liquibase_username
      changes:
        - createTable:
            tableName: users
            columns:
              - column:
                  name: id
                  type: int
                  autoIncrement: true
                  constraints:
                    primaryKey: true
                    nullable: false
              - column:
                  name: username
                  type: varchar(50)
                  constraints:
                    unique: true
                    nullable: false
              - column:
                  name: password
                  type: varchar(512)
                  constraints:
                    nullable: false
              - column:
                  name: first_name
                  type: varchar(50)
              - column:
                  name: last_name
                  type: varchar(50)
              - column:
                  name: email
                  type: varchar(50)

Разберем содержимое этого скрипта. Прежде всего, в нем содержится один changeSet. ChangeSet представляет из себя аналог коммита в системах контроля версий, таких как Git или SVN. По аналогии с коммитом, изменения, которые были внесены в рамках одного changeSet-а можно накатить или откатить (rollback) на сервер БД. У каждого changeSet-а должен быть уникальный идентификатор при помощи которого liquibase определяет был ли данный changeSet накачен на данную БД или нет.

ChangeSet содержит команду создания таблицы, причем структура таблицы описана средствами liquibase, а не SQL-скриптом. Благодаря этому данный файл становится кросплатформенным. Liquibase будет формировать SQL-скрипт в зависимости от используемого сервера БД. Кроме того, если нам будет нужно откатить данный changeSet, Liquibase сумеет автоматически создать скрипт для удаления данной таблицы. Если бы мы использовали SQL скрипты, то нам пришлось бы вручную писать скрипт для отката изменений. В разделе сhanges у нас всего одна команда и это считается хорошей практикой, не смотря на то, что команд в одном changeSet может быть сколько угодно.

Написанного кода вполне достаточно, чтобы запустить программу, но чтобы более наглядно видеть результаты её работы давайте добавим еще один changeSet, который заполнит таблицу данными.

  - changeSet:
      id: 2
      author: your_liquibase_username
      comment: "Create admin user"
      changes:
        - insert:
            tableName: users
            columns:
              - column:
                  name: username
                  value: "admin"
              - column:
                  name: password
                  value: "admin"
              - column:
                  name: email
                  value: "admin@server.com"
        - insert:
            tableName: users
            columns:
              - column:
                  name: username
                  value: "guest"
              - column:
                  name: password
                  value: "guest"
              - column:
                  name: email
                  value: "guest@server.com"
      rollback:
        - delete:
            tableName: users
            where: username in ('admin', 'guest')

В данном случае нам уже пришлось написать вручную блок для rollback операций, т.к. Liquibase не умеет автоматически создавать rollback SQL при работе с данными. Вообще работа с данными в БД не входит в число ключевых фич Liquibase и ограничивается лишь простейшими операциями вставки и удаления или изменения. К слову, если нужно больше, тот тут можно воспользоваться инструментами от фирмы Red Gate.

И так, давайте запустим наше приложение и попробуем перейти по ссылке http://localhost:8080/user/all. Если приложение запустилась, то вы увидите JSON-ответ с информацией о двух пользователях, которые были добавлены в таблицу. Также стоит взглянуть на логи запуска приложения, в которых можно видеть скрипты, которые Liquibase выполняет для инициализации БД. Особое внимание стоит обратить на таблицу DATABASECHANGELOG. Именно в ней Liquibase хранит лог изменений, внесенных в базу.

На этом пока все. Через некоторое время планирую опубликовать продолжение об использовании liquibase-maven-plugin для автоматической генерации скриптов через сравнение структур БД и отката внесенных изменений.

Буду благодарен за любые дополнения и замечания!

Автор: usharik

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js