← Назад к вопросам
Как происходит миграция схемы базы данных в Hibernate
2.2 Middle🔥 81 комментариев
#ORM и Hibernate
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Миграция схемы базы данных в Hibernate
Миграция БД - это процесс изменения структуры базы данных (таблицы, колонки, индексы) при сохранении данных. Hibernate предоставляет инструменты для этого, но важно понимать разные подходы.
Основные подходы к миграции в Hibernate
1. Hibernate Auto Schema Generation (разработка)
# application.properties
spring.jpa.hibernate.ddl-auto=update
# Опции:
# validate - проверяет схему, не меняет
# update - добавляет новые таблицы/колонки
# create - пересоздает все (удаляет данные!)
# create-drop - create на старте, drop на выходе
# none - ничего не делает (нужны вручную скрипты)
❌ ПРОБЛЕМЫ с auto-generation
❌ НЕ БЕЗОПАСНО для production:
- Может удалить важные данные
- Нет rollback если ошибка
- Нет истории изменений
- Не контролируемо и не предсказуемо
❌ Невозможно:
- Переименовать колонку (создаст новую, старую удалит)
- Изменить тип данных сложно
- Добавить custom логику (например индексы)
- Откатить миграцию если понадобилось
2. Liquibase (управляемые миграции)
<!-- pom.xml -->
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
<version>4.25.1</version>
</dependency>
# application.yml
spring:
liquibase:
change-log: classpath:db/changelog/db.changelog-master.yaml
Changelog файл
# db/changelog/db.changelog-master.yaml
databaseChangeLog:
- include:
file: db/changelog/changes/001_initial_schema.yaml
- include:
file: db/changelog/changes/002_add_user_email.yaml
- include:
file: db/changelog/changes/003_create_orders_table.yaml
Пример миграции
# db/changelog/changes/001_initial_schema.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(255)
constraints:
nullable: false
- column:
name: email
type: VARCHAR(255)
constraints:
unique: true
- column:
name: created_at
type: TIMESTAMP
defaultValue: CURRENT_TIMESTAMP
Добавить колонку
# db/changelog/changes/002_add_user_email.yaml
databaseChangeLog:
- changeSet:
id: 2
author: developer
changes:
- addColumn:
tableName: users
columns:
- column:
name: phone
type: VARCHAR(20)
constraints:
nullable: true
Переименовать колонку
# db/changelog/changes/003_rename_email_field.yaml
databaseChangeLog:
- changeSet:
id: 3
author: developer
changes:
- renameColumn:
tableName: users
oldColumnName: email_address
newColumnName: email
columnDataType: VARCHAR(255)
3. Flyway (альтернатива Liquibase)
<!-- pom.xml -->
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>9.22.3</version>
</dependency>
# application.yml
spring:
flyway:
locations: classpath:db/migration
baseline-on-migrate: true
Файлы миграций Flyway
db/migration/
V1__Initial_schema.sql
V2__Add_users_table.sql
V3__Add_orders_table.sql
V4__Add_email_column.sql
SQL миграция
-- db/migration/V1__Initial_schema.sql
CREATE TABLE users (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(255) NOT NULL,
email VARCHAR(255) UNIQUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE posts (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
title VARCHAR(255),
content TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
-- db/migration/V2__Add_phone_column.sql
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
Синхронизация с Hibernate моделями
Entity класс
import jakarta.persistence.*;
import java.time.LocalDateTime;
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "username", nullable = false)
private String username;
@Column(name = "email", unique = true)
private String email;
@Column(name = "phone")
private String phone;
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<Post> posts = new ArrayList<>();
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
}
// getters и setters
}
Best Practice для Production миграций
✅ ШАГ 1: Планирование
1. Определить что меняется в схеме
2. Написать SQL скрипт
3. Протестировать на копии БД
4. Обновить Entity классы в коде
5. Написать тесты для новой схемы
✅ ШАГ 2: Разработка миграции
-- db/migration/V5__Add_user_status.sql
-- Шаг 1: Добавить новую колонку с default значением
ALTER TABLE users ADD COLUMN status VARCHAR(50) DEFAULT 'ACTIVE';
-- Шаг 2: Создать индекс для производительности
CREATE INDEX idx_users_status ON users(status);
-- Шаг 3: Добавить constraint если нужно
ALTER TABLE users ADD CONSTRAINT chk_status
CHECK (status IN ('ACTIVE', 'INACTIVE', 'BANNED'));
✅ ШАГ 3: Тестирование
@SpringBootTest
@Transactional
class MigrationTest {
@Autowired
private JdbcTemplate jdbc;
@Test
void shouldMigrateSuccessfully() {
// Проверить что таблица существует
Integer columnCount = jdbc.queryForObject(
"SELECT COUNT(*) FROM information_schema.COLUMNS " +
"WHERE TABLE_NAME = 'users'",
Integer.class
);
assertThat(columnCount).isGreaterThanOrEqualTo(5);
// Проверить что new column существует
Integer hasStatus = jdbc.queryForObject(
"SELECT COUNT(*) FROM information_schema.COLUMNS " +
"WHERE TABLE_NAME = 'users' AND COLUMN_NAME = 'status'",
Integer.class
);
assertThat(hasStatus).isEqualTo(1);
}
@Test
void shouldPreserveExistingData() {
// Вставить данные до миграции (имитируем)
User user = new User();
user.setUsername("john");
user.setEmail("john@example.com");
// userRepository.save(user);
// После миграции данные должны быть в целости
// и иметь default значение для новой колонки
}
}
✅ ШАГ 4: Deployment
1. Синхронизировать код с миграцией
2. Deploy приложения
3. Flyway/Liquibase автоматически выполнит миграцию
4. Приложение работает с new schema
Откат миграции
Flyway - нет встроенного отката
-- Нужно писать V_R__Rollback_migration.sql
-- Но это антипаттерн, лучше forward-only
Liquibase - есть встроенный откат
changeSet:
id: 5
author: developer
changes:
- addColumn:
tableName: users
columns:
- column:
name: status
type: VARCHAR(50)
rollback:
- dropColumn:
tableName: users
columnName: status
# Откатить последнюю миграцию
liquibase rollback 1
Иерархия Entity ↔ Миграция
// 1. Меняешь Entity
@Entity
public class User {
@Column(name = "department_id")
private Long departmentId;
}
// 2. Создаешь миграцию
-- V6__Add_department_id.sql
ALTER TABLE users ADD COLUMN department_id BIGINT;
ALTER TABLE users ADD FOREIGN KEY (department_id) REFERENCES departments(id);
// 3. Запускаешь приложение
// Flyway выполнит миграцию при старте
// 4. В коде используешь
User user = new User();
user.setDepartmentId(1L);
userRepository.save(user);
Типичные ошибки
❌ Использовать update в production
# НИКОГДА не делай это!
spring.jpa.hibernate.ddl-auto=update
❌ Миграция без тестирования
Лучший способ потерять данные:
1. Написать миграцию не тестируя
2. Залить в production
3. "Ой, я забыл про cascade delete..."
❌ Миграция содержит hard-coded значения
-- ❌ ПЛОХО
UPDATE users SET status = 'ACTIVE' WHERE status IS NULL;
-- ✅ ХОРОШО (миграция + документация)
-- Установить default в новую колонку
ALTER TABLE users MODIFY COLUMN status VARCHAR(50) DEFAULT 'ACTIVE';
Рекомендуемый workflow
1. Разработка
- Используй hibernate.ddl-auto=create для тестов
- Entity first approach
2. Перед production
- Выключи auto-generation (ddl-auto=validate)
- Напиши SQL миграцию вручную
- Protectioned данные
- Протестируй миграцию
3. Deploy
- Flyway/Liquibase автоматически применит миграцию
- Нет ручных SQL скриптов
4. После deploy
- Обнови Entity классы
- Запусти приложение
- Приложение валидирует что схема соответствует Entity
Выбор между Flyway и Liquibase
| Критерий | Flyway | Liquibase |
|---|---|---|
| Синтаксис | SQL | YAML/XML/SQL |
| Сложность | Проще | Больше возможностей |
| Откат | Нет встроенного | Есть встроенный |
| История | Версионирование | Full history |
| Рекомендуется | Simple projects | Enterprise |
Итоговая best practice
✅ PRODUCTION:
- Используй Flyway или Liquibase
- Версионируй миграции как код
- Храни в git
- Каждая миграция = один файл
- Тестируй перед production
- Forward-only (не откатываешь в prod)
✅ DEVELOPMENT:
- Используй Entity first
- hibernate.ddl-auto=update
- Миграции пишешь только когда будешь готов к deployment
✅ ТЕСТИРОВАНИЕ:
- hibernate.ddl-auto=create для каждого теста
- Или TestContainers с реальной БД
Вывод: миграция БД в Hibernate - это critical процесс. Используй Flyway/Liquibase для production, Entity first для разработки, и всегда версионируй изменения.