Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Генерация схемы и миграции

До сих пор Hibernate сам создавал таблицы при каждом запуске через drop-and-create — и удалял все данные. Для учёбы и тестов это нормально, для настоящей базы — нет. Там схему меняют миграции: пронумерованные SQL-файлы, которые хранятся вместе с кодом и применяются по порядку. Hibernate при этом только проверяет, что таблицы соответствуют сущностям.

Настройки схемы

Значение hibernate.hbm2ddl.autoЧто происходит при запуске
createУдалить и создать таблицы
create-dropСоздать, а при закрытии фабрики удалить
updateДобавить недостающие таблицы и столбцы, ничего не удалять
validateТолько сравнить схему с сущностями; при расхождении — ошибка
noneНичего не делать

У стандартного свойства jakarta.persistence.schema-generation.database.action, которое используется в курсе, значения none, create, drop-and-create и drop. В Spring Boot та же настройка называется spring.jpa.hibernate.ddl-auto. update выглядит заманчиво, но не умеет переименовывать и удалять столбцы и не оставляет истории изменений, поэтому в рабочих проектах используют validate вместе с миграциями.

Генерация скрипта

Первую миграцию не обязательно писать вручную. Hibernate может записать DDL в файл вместо того, чтобы выполнять его:

// src/main/java/com/example/blog/GenerateSchema.java
package com.example.blog;
 
import jakarta.persistence.Persistence;
import java.util.Map;
 
public class GenerateSchema {
    public static void main(String[] args) {
        Persistence.generateSchema("blog", Map.of(
                "jakarta.persistence.schema-generation.database.action", "none",
                "jakarta.persistence.schema-generation.scripts.action", "create",
                "jakarta.persistence.schema-generation.scripts.create-target", "create.sql"));
        IO.println("Готово: create.sql");
    }
}

Запустите его командой mvn compile exec:java -Dexec.mainClass=com.example.blog.GenerateSchema. В папке проекта появится файл create.sql:

create sequence Post_SEQ start with 1 increment by 50;
create table Post (id bigint not null, body varchar(255), title varchar(255), primary key (id));

Просмотрите его и возьмите за основу первой миграции.

Flyway

Добавьте зависимость:

<!-- pom.xml -->
<dependency>
  <groupId>org.flywaydb</groupId>
  <artifactId>flyway-core</artifactId>
  <version>13.9.0</version>
</dependency>

Миграции лежат в src/main/resources/db/migration. Имя задаёт порядок: V1__, V2__ и описание после двух подчёркиваний.

-- src/main/resources/db/migration/V1__create_posts.sql
create sequence Post_SEQ start with 1 increment by 50;
 
create table Post (
    id bigint not null,
    title varchar(255),
    primary key (id)
);
-- src/main/resources/db/migration/V2__add_body.sql
alter table Post add column body varchar(255);

В persistence.xml замените свойство генерации на <property name="hibernate.hbm2ddl.auto" value="validate"/>, а к адресу добавьте ;DB_CLOSE_DELAY=-1, чтобы база в памяти дожила от Flyway до Hibernate. Запустите Flyway до старта Hibernate:

// src/main/java/com/example/blog/Main.java
Flyway flyway = Flyway.configure()
        .dataSource("jdbc:h2:mem:blog;DB_CLOSE_DELAY=-1", "sa", "")
        .load();
flyway.migrate();
 
try (EntityManagerFactory emf = Persistence.createEntityManagerFactory("blog")) {
    emf.runInTransaction(em -> em.persist(new Post("Миграции", "Таблицы создаёт Flyway")));
    // подсчёт статей
}
INFO: Migrating schema "PUBLIC" to version "1 - create posts"
INFO: Migrating schema "PUBLIC" to version "2 - add body"
INFO: Successfully applied 2 migrations to schema "PUBLIC", now at version v2 (execution time 00:00.011s)
Hibernate: insert into Post (body,title,id) values (?,?,?)
Статей: 1

Flyway записывает применённые миграции в таблицу flyway_schema_history, поэтому при следующем запуске выполнит только новые файлы. Spring Boot запускает Flyway автоматически при старте, если он есть в зависимостях.

Частая ошибка

Если у сущности появилось новое поле, а миграцию никто не написал, validate остановит приложение при запуске. Когда применена только V1:

Exception in thread "main" jakarta.persistence.PersistenceException: Unable to build Hibernate SessionFactory  [persistence unit: blog]
Caused by: org.hibernate.tool.schema.spi.SchemaManagementException: Schema validation: missing column [body] in table [Post]

Это хорошо: ошибка видна при запуске, а не посреди запроса пользователя. Каждое изменение сущностей требует нового файла миграции. Никогда не правьте уже применённую миграцию — Flyway заметит изменившуюся контрольную сумму.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.