← Назад к вопросам

Что такое Liquibase и для чего он используется в контексте миграций?

2.3 Middle🔥 151 комментариев
#ETL и качество данных

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

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Liquibase и его использование в миграциях

Что такое Liquibase

Liquibase — это tool для управления версионированием и миграциями схемы базы данных. Он позволяет отслеживать изменения БД в контроле версий (git) и автоматически применять их в production.

Это альтернатива Flyway, Alembic (Python), и Goose (Go).

Основные концепции

1. Changelog — журнал изменений

Описывает все изменения схемы в XML, YAML или JSON формате:

<!-- db/changelog/master.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
    xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
    http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.0.xsd">

    <include file="01_create_users_table.xml" relativeToChangelogFile="true"/>
    <include file="02_add_email_column.xml" relativeToChangelogFile="true"/>
    <include file="03_create_orders_table.xml" relativeToChangelogFile="true"/>

</databaseChangeLog>

2. Changeset — отдельное изменение

<!-- db/changelog/01_create_users_table.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
    xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
    http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.0.xsd">

    <changeSet id="1" author="johndoe">
        <createTable tableName="users">
            <column name="id" type="INT" autoIncrement="true">
                <constraints primaryKey="true" nullable="false"/>
            </column>
            <column name="username" type="VARCHAR(100)">
                <constraints nullable="false" unique="true"/>
            </column>
            <column name="email" type="VARCHAR(255)">
                <constraints nullable="false" unique="true"/>
            </column>
            <column name="created_at" type="TIMESTAMP" defaultValueDate="now()"/>
        </createTable>
    </changeSet>

</databaseChangeLog>

Или на YAML:

# db/changelog/01_create_users_table.yaml
databaseChangeLog:
  - changeSet:
      id: "1"
      author: "johndoe"
      changes:
        - createTable:
            tableName: users
            columns:
              - column:
                  name: id
                  type: INT
                  autoIncrement: true
                  constraints:
                    primaryKey: true
                    nullable: false
              - column:
                  name: username
                  type: VARCHAR(100)
                  constraints:
                    nullable: false
                    unique: true
              - column:
                  name: email
                  type: VARCHAR(255)
                  constraints:
                    nullable: false
                    unique: true
              - column:
                  name: created_at
                  type: TIMESTAMP
                  defaultValueDate: "now()"

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

1. При первом запуске создаются служебные таблицы:
   - DATABASECHANGELOG — список применённых changesets
   - DATABASECHANGELOGLOCK — блокировка при одновременных запусках

2. Liquibase читает changelog файл

3. Для каждого changeset:
   - Проверяет, был ли он уже применён (id + author)
   - Если нет — вычисляет checksum
   - Применяет изменения
   - Записывает запись в DATABASECHANGELOG

4. Если checksum не совпадает — ошибка (защита от случайных изменений)

Использование (CLI)

# Создать Liquibase проект
liquibase init project --project-dir=./db

# Применить миграции
liquibase update

# Откатить последнюю миграцию
liquibase rollback 1

# Откатить всё
liquibase rollback-to-date --date=2024-01-01

# Просмотреть статус
liquibase status

# Сгенерировать скрипт без применения
liquibase update-sql

# Проверить синтаксис
liquibase validate

Интеграция в Java приложение

Maven:

<!-- pom.xml -->
<dependencies>
    <dependency>
        <groupId>org.liquibase</groupId>
        <artifactId>liquibase-core</artifactId>
        <version>4.24.0</version>
    </dependency>
</dependencies>

<plugins>
    <plugin>
        <groupId>org.liquibase</groupId>
        <artifactId>liquibase-maven-plugin</artifactId>
        <version>4.24.0</version>
        <configuration>
            <changeLogFile>src/main/resources/db/changelog/db.changelog-master.xml</changeLogFile>
            <driver>org.postgresql.Driver</driver>
            <url>jdbc:postgresql://localhost:5432/mydb</url>
            <username>postgres</username>
            <password>${db.password}</password>
        </configuration>
    </plugin>
</plugins>

Spring Boot (автоматические миграции при старте):

# application.yml
spring:
  datasource:
    url: jdbc:postgresql://localhost/mydb
    username: postgres
    password: secret
  liquibase:
    change-log: classpath:/db/changelog/db.changelog-master.xml
    enabled: true
// Приложение автоматически применит миграции при старте
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Liquibase vs Альтернативы

FeatureLiquibaseFlywayAlembicGoose
ЯзыкJavaJavaPythonGo
ФорматыXML/YAML/JSON/SQLSQLPythonSQL
ОткатыПолная поддержкаОграниченоДаДа
ВесТяжелыйЛегкийЛегкийОчень легкий
Для JavaОтличноОтличноN/AN/A
Для PythonПлохоПлохоОтличноПлохо
Для GoПлохоПлохоПлохоОтлично

Реальный пример: E-commerce база данных

# db/changelog/master.yaml
databaseChangeLog:
  - include:
      file: db/changelog/01_initial_schema.yaml
  - include:
      file: db/changelog/02_add_indexes.yaml
  - include:
      file: db/changelog/03_user_activity.yaml

---
# db/changelog/01_initial_schema.yaml
databaseChangeLog:
  - changeSet:
      id: "001"
      author: "architect"
      changes:
        - createTable:
            tableName: customers
            columns:
              - column:
                  name: id
                  type: BIGINT
                  autoIncrement: true
                  constraints:
                    primaryKey: true
              - column:
                  name: email
                  type: VARCHAR(255)
                  constraints:
                    nullable: false
                    unique: true
              - column:
                  name: full_name
                  type: VARCHAR(255)
              - column:
                  name: status
                  type: VARCHAR(50)
                  defaultValue: "ACTIVE"
              - column:
                  name: created_at
                  type: TIMESTAMP
                  defaultValueDate: "CURRENT_TIMESTAMP"

  - changeSet:
      id: "002"
      author: "architect"
      changes:
        - createTable:
            tableName: orders
            columns:
              - column:
                  name: id
                  type: BIGINT
                  autoIncrement: true
                  constraints:
                    primaryKey: true
              - column:
                  name: customer_id
                  type: BIGINT
                  constraints:
                    nullable: false
                    foreignKeyName: fk_orders_customers
                    references: customers(id)
              - column:
                  name: total_amount
                  type: DECIMAL(10,2)
              - column:
                  name: status
                  type: VARCHAR(50)
              - column:
                  name: created_at
                  type: TIMESTAMP
                  defaultValueDate: "CURRENT_TIMESTAMP"

---
# db/changelog/02_add_indexes.yaml
databaseChangeLog:
  - changeSet:
      id: "003"
      author: "performance_team"
      changes:
        - createIndex:
            indexName: idx_orders_customer_id
            tableName: orders
            columns:
              - column:
                  name: customer_id
        - createIndex:
            indexName: idx_orders_created_at
            tableName: orders
            columns:
              - column:
                  name: created_at

---
# db/changelog/03_user_activity.yaml
databaseChangeLog:
  - changeSet:
      id: "004"
      author: "analytics_team"
      changes:
        - createTable:
            tableName: user_activity
            columns:
              - column:
                  name: id
                  type: BIGINT
                  autoIncrement: true
                  constraints:
                    primaryKey: true
              - column:
                  name: customer_id
                  type: BIGINT
              - column:
                  name: activity_type
                  type: VARCHAR(100)
              - column:
                  name: created_at
                  type: TIMESTAMP

Лучшие практики

1. Всегда указывайте author и id:

changeSet:
  id: "001"  # Уникальный в файле
  author: "username"  # Кто создал

2. Один changeset = один логический элемент:

# Правильно
- changeSet:
    id: "1"
    changes:
      - addColumn:
          tableName: users
          columns:
            - column:
                name: phone
                type: VARCHAR(20)

# Неправильно: слишком много изменений
- changeSet:
    id: "1"
    changes:
      - createTable: ...
      - addColumn: ...
      - createIndex: ...
      - addForeignKey: ...

3. Тестируйте откаты:

liquibase update          # Применить
liquibase rollback 1      # Откатить
liquibase update          # Применить заново

4. Используйте preconditions для безопасности:

changeSet:
  id: "1"
  preConditions:
    - onFail: MARK_RAN  # Пропустить если условие не выполнено
    - columnExists:
        tableName: users
        columnName: email
  changes:
    - addUniqueConstraint:
        tableName: users
        columnNames: email

Интеграция в CI/CD

# GitLab CI
stages:
  - migrate

migrate_dev:
  stage: migrate
  image: liquibase:latest
  script:
    - liquibase update --url=$DEV_DB_URL --username=$DEV_DB_USER --password=$DEV_DB_PASS
  only:
    - develop

migrate_prod:
  stage: migrate
  image: liquibase:latest
  script:
    - liquibase update --url=$PROD_DB_URL --username=$PROD_DB_USER --password=$PROD_DB_PASS
  only:
    - main
  when: manual  # Ручное одобрение в production

Когда использовать Liquibase

Хорош для:

  • Java-приложений с Spring Boot
  • Сложных миграций с full rollback
  • Когда нужна историческая информация о всех изменениях
  • Когда разработчики не SQL-специалисты

Не очень хорош для:

  • Простых скриптов
  • Python/Go приложений (лучше использовать Alembic/Goose)
  • Когда хочется писать сырой SQL

Заключение

Liquibase — мощный tool для управления миграциями в enterprise приложениях. Он гарантирует:

  • Воспроизводимость — все изменения в контроле версий
  • Безопасность — checksum защита от случайных изменений
  • Откаты — легко откатить любое изменение
  • Аудит — полная история всех миграций

Сочетание с CI/CD гарантирует надежное управление схемой БД в production.