Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Schema generation and migrations

So far Hibernate has created the tables itself on every start with drop-and-create — and deleted all the data. That is fine for learning and tests, but not for a real database. There the schema is changed by migrations: numbered SQL files stored with the code and applied in order. Hibernate then only checks that the tables match the entities.

Schema settings

Value of hibernate.hbm2ddl.autoWhat happens at start
createDrop and create the tables
create-dropCreate, and drop when the factory closes
updateAdd missing tables and columns, never remove anything
validateOnly compare the schema with the entities; fail on differences
noneDo nothing

The standard property jakarta.persistence.schema-generation.database.action used in this course has the values none, create, drop-and-create and drop. In Spring Boot the same setting is spring.jpa.hibernate.ddl-auto. update looks tempting, but it cannot rename or drop columns and leaves no history of changes, so production projects use validate together with migrations.

Generating a script

You do not have to write the first migration by hand. Hibernate can write the DDL to a file instead of running it:

// 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("Done: create.sql");
    }
}

Run it with mvn compile exec:java -Dexec.mainClass=com.example.blog.GenerateSchema. The file create.sql appears in the project folder:

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));

Review it and use it as the starting point for the first migration.

Flyway

Add the dependency:

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

Migrations live in src/main/resources/db/migration. The name sets the order: V1__, V2__ and a description after two underscores.

-- 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);

In persistence.xml replace the generation property with <property name="hibernate.hbm2ddl.auto" value="validate"/>, and add ;DB_CLOSE_DELAY=-1 to the URL so the in-memory database survives between Flyway and Hibernate. Run Flyway before Hibernate starts:

// 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("Migrations", "Flyway creates the tables")));
    // count the posts
}
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 (?,?,?)
Posts: 1

Flyway records applied migrations in the flyway_schema_history table, so on the next start it runs only new files. Spring Boot runs Flyway automatically at start when it is on the classpath.

Common mistake

If an entity gets a new field but nobody writes a migration, validate stops the application at start. With only V1 applied:

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]

This is good: the error appears at start, not in the middle of a user request. Every change to entities needs a new migration file. Never edit a migration that has already been applied — Flyway will detect the changed checksum.

Thanks for reading!

Donate to the site — we work hard for you.