Миграции баз данных в Kotlin: как управлять схемой без боли
При разработке на Kotlin, особенно в связке с JVM, рано или поздно возникает задача синхронизации структуры базы данных между разработчиками, тестовыми и боевыми серверами. Ручное выполнение SQL-скриптов приводит к рассинхронизации и ошибкам. Решение — миграции баз данных. Это автоматизированный способ версионировать изменения схемы: создание таблиц, добавление колонок, индексов и триггеров.
В этой статье разберём три популярных инструмента для Kotlin: Flyway, Liquibase и встроенный механизм Exposed. Вы узнаете, как интегрировать их в проект, писать миграции и избегать типовых проблем.
Flyway: простота и версионирование
Flyway — самый популярный инструмент для миграций в экосистеме JVM. Он поддерживает Kotlin, Java и Groovy. Принцип работы: вы пишете SQL-скрипты (или Java/Kotlin-классы), а Flyway отслеживает, какие из них уже применены, с помощью таблицы flyway_schema_history.
Подключение в Gradle (Kotlin DSL):
dependencies {
implementation("org.flywaydb:flyway-core:9.22.3")
// для PostgreSQL
implementation("org.flywaydb:flyway-database-postgresql:9.22.3")
}
Пример миграции V1__create_users.sql:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(100) NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_users_email ON users(email);
Запуск из кода Kotlin:
import org.flywaydb.core.Flyway
fun main() {
val flyway = Flyway.configure()
.dataSource("jdbc:postgresql://localhost:5432/mydb", "user", "pass")
.load()
flyway.migrate()
}
Преимущества:
- Минимальная конфигурация
- Поддержка откатов (undo) в платной версии
- Работает с любым SQL
Недостатки:
- Нет встроенной валидации схемы
- Сложно управлять ветвлением
Liquibase: гибкость и декларативность
Liquibase предлагает два подхода: изменения можно описывать в XML, YAML, JSON или прямо в SQL. Для Kotlin-проектов удобен YAML-формат — он читаем и легко версионируется.
Добавление в Gradle:
dependencies {
implementation("org.liquibase:liquibase-core:4.24.0")
}
Пример changelog-файла db.changelog-master.yaml:
databaseChangeLog:
- changeSet:
id: 1
author: developer
changes:
- createTable:
tableName: users
columns:
- column:
name: id
type: BIGINT
autoIncrement: true
constraints:
primaryKey: true
- column:
name: username
type: VARCHAR(50)
constraints:
nullable: false
unique: true
- column:
name: email
type: VARCHAR(100)
- createIndex:
indexName: idx_users_email
tableName: users
columns:
- column:
name: email
Запуск из Kotlin:
import liquibase.Liquibase
import liquibase.database.jvm.JdbcConnection
import liquibase.resource.ClassLoaderResourceAccessor
import java.sql.DriverManager
fun runMigrations() {
val connection = DriverManager.getConnection(
"jdbc:postgresql://localhost:5432/mydb", "user", "pass"
)
val database = DatabaseFactory.getInstance()
.findCorrectDatabaseImplementation(JdbcConnection(connection))
val liquibase = Liquibase("db.changelog-master.yaml", ClassLoaderResourceAccessor(), database)
liquibase.update("")
}
Плюсы:
- Поддержка rollback по умолчанию
- Контексты (dev, prod) и precondition
- Генерация diff между схемами
Минусы:
- Более сложный синтаксис
- Медленнее Flyway на больших проектах
Exposed: миграции без ORM-оверхеда
Exposed — это легковесная ORM для Kotlin от JetBrains. В её состав входит модуль exposed-migrations, который позволяет описывать изменения схемы прямо на Kotlin, без SQL.
Подключение:
dependencies {
implementation("org.jetbrains.exposed:exposed-core:0