Careeroza
Schema Generation with ddl-auto
What ddl-auto Does
spring.jpa.hibernate.ddl-auto tells Hibernate what to do with tables at startup:
| Value | At startup | At shutdown | Use it for |
|---|---|---|---|
none | Nothing | Nothing | Production (with Flyway/Liquibase) |
validate | Checks tables match entities; fails if not | Nothing | Production safety check |
update | Adds missing tables/columns (never drops) | Nothing | Local development |
create | Drops and recreates all tables | Nothing | Demos, experiments |
create-drop | Drops and recreates | Drops tables | Tests; default for embedded H2 |
Note: Never use create, create-drop or even update on a production database. create deletes all data on every restart, and update cannot rename or remove columns safely. Use schema migrations (Flyway) — covered in the advance level.
Generated DDL
Startup log
With ddl-auto: create and SQL logging, Hibernate prints the DDL it runs for the Product entity (MySQL):
spring.jpa.hibernate.ddl-auto=createExample output
Hibernate:
drop table if exists products
Hibernate:
create table products (
price decimal(10,2) not null,
stock integer not null,
id bigint not null auto_increment,
category varchar(50),
description varchar(2000),
name varchar(150) not null,
primary key (id)
) engine=InnoDBSchema Generation in Miniature
Example
Hibernate builds DDL from your class metadata and the dialect type mapping. This toy generator does the same for a record using reflection:
import java.lang.reflect.RecordComponent;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
public class Main {
static final Map<Class<?>, String> TYPES = Map.of(
Long.class, "bigint", String.class, "varchar(255)", Integer.class, "integer",
BigDecimal.class, "decimal(38,2)", LocalDateTime.class, "datetime(6)", Boolean.class, "bit");
public static void main(String[] args) {
System.out.println(createTable(Product.class));
System.out.println(createTable(Category.class));
}
static String createTable(Class<? extends Record> type) {
List<String> cols = new ArrayList<>();
for (RecordComponent rc : type.getRecordComponents()) {
String col = toSnakeCase(rc.getName()) + " " + TYPES.get(rc.getType());
if (rc.getName().equals("id")) col += " not null auto_increment";
cols.add(" " + col);
}
cols.add(" primary key (id)");
return "create table " + toSnakeCase(type.getSimpleName()) + " (" + System.lineSeparator()
+ String.join("," + System.lineSeparator(), cols) + System.lineSeparator() + ")";
}
// Spring Boot's naming strategy: camelCase -> snake_case
static String toSnakeCase(String s) {
StringBuilder sb = new StringBuilder();
for (char c : s.toCharArray()) {
if (Character.isUpperCase(c) && sb.length() > 0) sb.append('_');
sb.append(Character.toLowerCase(c));
}
return sb.toString();
}
}
record Product(Long id, String name, BigDecimal price, Integer stock, LocalDateTime createdAt) {}
record Category(Long id, String name, String slug, Boolean active) {}Output
create table product (
id bigint not null auto_increment,
name varchar(255),
price decimal(38,2),
stock integer,
created_at datetime(6),
primary key (id)
)
create table category (
id bigint not null auto_increment,
name varchar(255),
slug varchar(255),
active bit,
primary key (id)
)Notice createdAt became created_at. Spring Boot configures Hibernate with CamelCaseToUnderscoresNamingStrategy, so you rarely need @Column(name = ...) just for snake_case.