4. Schema generation
Hibernate allows you to generate the database from the entity mappings.
Although the automatic schema generation is very useful for testing and prototyping purposes, in a production environment, it’s much more flexible to manage the schema using incremental migration scripts. |
Traditionally, the process of generating schema from entity mapping has been called HBM2DDL
. To get a list of Hibernate-native and JPA-specific configuration properties consider reading the Configurations section.
Considering the following Domain Model:
Example 282. Schema generation Domain Model
@Entity(name = "Customer")
public class Customer {
@Id
private Integer id;
private String name;
@Basic( fetch = FetchType.LAZY )
private UUID accountsPayableXrefId;
@Lob
@Basic( fetch = FetchType.LAZY )
@LazyGroup( "lobs" )
private Blob image;
//Getters and setters are omitted for brevity
}
@Entity(name = "Person")
public static class Person {
@Id
private Long id;
private String name;
@OneToMany(mappedBy = "author")
private List<Book> books = new ArrayList<>();
//Getters and setters are omitted for brevity
}
@Entity(name = "Book")
public static class Book {
@Id
private Long id;
private String title;
@NaturalId
private String isbn;
@ManyToOne
private Person author;
//Getters and setters are omitted for brevity
}
If the hibernate.hbm2ddl.auto
configuration is set to create
, Hibernate is going to generate the following database schema:
Example 283. Auto-generated database schema
create table Customer (
id integer not null,
accountsPayableXrefId binary,
image blob,
name varchar(255),
primary key (id)
)
create table Book (
id bigint not null,
isbn varchar(255),
title varchar(255),
author_id bigint,
primary key (id)
)
create table Person (
id bigint not null,
name varchar(255),
primary key (id)
)
alter table Book
add constraint UK_u31e1frmjp9mxf8k8tmp990i unique (isbn)
alter table Book
add constraint FKrxrgiajod1le3gii8whx2doie
foreign key (author_id)
references Person
4.1. Importing script files
To customize the schema generation process, the hibernate.hbm2ddl.import_files
configuration property must be used to provide other scripts files that Hibernate can use when the SessionFactory
is started.
For instance, considering the following schema-generation.sql
import file:
Example 284. Schema generation import file
create sequence book_sequence start with 1 increment by 1
If we configure Hibernate to import the script above:
Example 285. Enabling schema generation import file
<property
name="hibernate.hbm2ddl.import_files"
value="schema-generation.sql" />
Hibernate is going to execute the script file after the schema is automatically generated.
4.2. Database objects
Hibernate allows you to customize the schema generation process via the HBM database-object
element.
Considering the following HBM mapping:
Example 286. Schema generation HBM database-object
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd" >
<hibernate-mapping>
<database-object>
<create>
CREATE OR REPLACE FUNCTION sp_count_books(
IN authorId bigint,
OUT bookCount bigint)
RETURNS bigint AS
$BODY$
BEGIN
SELECT COUNT(*) INTO bookCount
FROM book
WHERE author_id = authorId;
END;
$BODY$
LANGUAGE plpgsql;
</create>
<drop></drop>
<dialect-scope name="org.hibernate.dialect.PostgreSQL95Dialect" />
</database-object>
</hibernate-mapping>
When the SessionFactory
is bootstrapped, Hibernate is going to execute the database-object
, therefore creating the sp_count_books
function.
4.3. Database-level checks
Hibernate offers the @Check
annotation so that you can specify an arbitrary SQL CHECK constraint which can be defined as follows:
Example 287. Database check entity mapping example
@Entity(name = "Book")
@Check( constraints = "CASE WHEN isbn IS NOT NULL THEN LENGTH(isbn) = 13 ELSE true END")
public static class Book {
@Id
private Long id;
private String title;
@NaturalId
private String isbn;
private Double price;
//Getters and setters omitted for brevity
}
Now, if you try to add a Book
entity with an isbn
attribute whose length is not 13 characters, a ConstraintViolationException
is going to be thrown.
Example 288. Database check failure example
Book book = new Book();
book.setId( 1L );
book.setPrice( 49.99d );
book.setTitle( "High-Performance Java Persistence" );
book.setIsbn( "11-11-2016" );
entityManager.persist( book );
INSERT INTO Book (isbn, price, title, id)
VALUES ('11-11-2016', 49.99, 'High-Performance Java Persistence', 1)
-- WARN SqlExceptionHelper:129 - SQL Error: 0, SQLState: 23514
-- ERROR SqlExceptionHelper:131 - ERROR: new row for relation "book" violates check constraint "book_isbn_check"
4.4. Default value for a database column
With Hibernate, you can specify a default value for a given database column using the @ColumnDefault
annotation.
Example 289. @ColumnDefault
mapping example
@Entity(name = "Person")
@DynamicInsert
public static class Person {
@Id
private Long id;
@ColumnDefault("'N/A'")
private String name;
@ColumnDefault("-1")
private Long clientId;
//Getter and setters omitted for brevity
}
CREATE TABLE Person (
id BIGINT NOT NULL,
clientId BIGINT DEFAULT -1,
name VARCHAR(255) DEFAULT 'N/A',
PRIMARY KEY (id)
)
In the mapping above, both the name
and clientId
table columns are going to use a DEFAULT
value.
The |
This way, when omitting the name
and the clientId
attribute, the database is going to set them according to their default values.
Example 290. @ColumnDefault
mapping example
doInJPA( this::entityManagerFactory, entityManager -> {
Person person = new Person();
person.setId( 1L );
entityManager.persist( person );
} );
doInJPA( this::entityManagerFactory, entityManager -> {
Person person = entityManager.find( Person.class, 1L );
assertEquals( "N/A", person.getName() );
assertEquals( Long.valueOf( -1L ), person.getClientId() );
} );
INSERT INTO Person (id) VALUES (?)
4.5. Columns unique constraint
The @UniqueConstraint
annotation is used to specify a unique constraint to be included by the automated schema generator for the primary or secondary table associated with the current annotated entity.
Considering the following entity mapping, Hibernate generates the unique constraint DDL when creating the database schema:
Example 291. @UniqueConstraint
mapping example
@Entity
@Table(
name = "book",
uniqueConstraints = @UniqueConstraint(
name = "uk_book_title_author",
columnNames = {
"title",
"author_id"
}
)
)
public static class Book {
@Id
@GeneratedValue
private Long id;
private String title;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(
name = "author_id",
foreignKey = @ForeignKey(name = "fk_book_author_id")
)
private Author author;
//Getter and setters omitted for brevity
}
@Entity
@Table(name = "author")
public static class Author {
@Id
@GeneratedValue
private Long id;
@Column(name = "first_name")
private String firstName;
@Column(name = "last_name")
private String lastName;
//Getter and setters omitted for brevity
}
create table author (
id bigint not null,
first_name varchar(255),
last_name varchar(255),
primary key (id)
)
create table book (
id bigint not null,
title varchar(255),
author_id bigint,
primary key (id)
)
alter table book
add constraint uk_book_title_author
unique (title, author_id)
alter table book
add constraint fk_book_author_id
foreign key (author_id)
references author
With the uk_book_title_author
unique constraint in place, it’s no longer possible to add two books with the same title and for the same author.
Example 292. @UniqueConstraintTest
persist example
Author _author = doInJPA( this::entityManagerFactory, entityManager -> {
Author author = new Author();
author.setFirstName( "Vlad" );
author.setLastName( "Mihalcea" );
entityManager.persist( author );
Book book = new Book();
book.setTitle( "High-Performance Java Persistence" );
book.setAuthor( author );
entityManager.persist( book );
return author;
} );
try {
doInJPA( this::entityManagerFactory, entityManager -> {
Book book = new Book();
book.setTitle( "High-Performance Java Persistence" );
book.setAuthor( _author );
entityManager.persist( book );
} );
}
catch (Exception expected) {
assertNotNull( ExceptionUtil.findCause( expected, ConstraintViolationException.class ) );
}
insert
into
author
(first_name, last_name, id)
values
(?, ?, ?)
-- binding parameter [1] as [VARCHAR] - [Vlad]
-- binding parameter [2] as [VARCHAR] - [Mihalcea]
-- binding parameter [3] as [BIGINT] - [1]
insert
into
book
(author_id, title, id)
values
(?, ?, ?)
-- binding parameter [1] as [BIGINT] - [1]
-- binding parameter [2] as [VARCHAR] - [High-Performance Java Persistence]
-- binding parameter [3] as [BIGINT] - [2]
insert
into
book
(author_id, title, id)
values
(?, ?, ?)
-- binding parameter [1] as [BIGINT] - [1]
-- binding parameter [2] as [VARCHAR] - [High-Performance Java Persistence]
-- binding parameter [3] as [BIGINT] - [3]
-- SQL Error: 23505, SQLState: 23505
-- Unique index or primary key violation: "UK_BOOK_TITLE_AUTHOR_INDEX_1 ON PUBLIC.BOOK(TITLE, AUTHOR_ID) VALUES ( /* key:1 */ 3, 'High-Performance Java Persistence', 1)";
The second INSERT statement fails because of the unique constraint violation.
4.6. Columns index
The @Index
annotation is used by the automated schema generation tool to create a database index.
Considering the following entity mapping. Hibernate generates the index when creating the database schema:
Example 293. @Index
mapping example
@Entity
@Table(
name = "author",
indexes = @Index(
name = "idx_author_first_last_name",
columnList = "first_name, last_name",
unique = false
)
)
public static class Author {
@Id
@GeneratedValue
private Long id;
@Column(name = "first_name")
private String firstName;
@Column(name = "last_name")
private String lastName;
//Getter and setters omitted for brevity
}
create table author (
id bigint not null,
first_name varchar(255),
last_name varchar(255),
primary key (id)
)
create index idx_author_first_last_name
on author (first_name, last_name)