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

  1. @Entity(name = "Customer")
  2. public class Customer {
  3. @Id
  4. private Integer id;
  5. private String name;
  6. @Basic( fetch = FetchType.LAZY )
  7. private UUID accountsPayableXrefId;
  8. @Lob
  9. @Basic( fetch = FetchType.LAZY )
  10. @LazyGroup( "lobs" )
  11. private Blob image;
  12. //Getters and setters are omitted for brevity
  13. }
  14. @Entity(name = "Person")
  15. public static class Person {
  16. @Id
  17. private Long id;
  18. private String name;
  19. @OneToMany(mappedBy = "author")
  20. private List<Book> books = new ArrayList<>();
  21. //Getters and setters are omitted for brevity
  22. }
  23. @Entity(name = "Book")
  24. public static class Book {
  25. @Id
  26. private Long id;
  27. private String title;
  28. @NaturalId
  29. private String isbn;
  30. @ManyToOne
  31. private Person author;
  32. //Getters and setters are omitted for brevity
  33. }

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

  1. create table Customer (
  2. id integer not null,
  3. accountsPayableXrefId binary,
  4. image blob,
  5. name varchar(255),
  6. primary key (id)
  7. )
  8. create table Book (
  9. id bigint not null,
  10. isbn varchar(255),
  11. title varchar(255),
  12. author_id bigint,
  13. primary key (id)
  14. )
  15. create table Person (
  16. id bigint not null,
  17. name varchar(255),
  18. primary key (id)
  19. )
  20. alter table Book
  21. add constraint UK_u31e1frmjp9mxf8k8tmp990i unique (isbn)
  22. alter table Book
  23. add constraint FKrxrgiajod1le3gii8whx2doie
  24. foreign key (author_id)
  25. 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

  1. 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

  1. <property
  2. name="hibernate.hbm2ddl.import_files"
  3. 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

  1. <?xml version="1.0"?>
  2. <!DOCTYPE hibernate-mapping PUBLIC
  3. "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
  4. "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd" >
  5. <hibernate-mapping>
  6. <database-object>
  7. <create>
  8. CREATE OR REPLACE FUNCTION sp_count_books(
  9. IN authorId bigint,
  10. OUT bookCount bigint)
  11. RETURNS bigint AS
  12. $BODY$
  13. BEGIN
  14. SELECT COUNT(*) INTO bookCount
  15. FROM book
  16. WHERE author_id = authorId;
  17. END;
  18. $BODY$
  19. LANGUAGE plpgsql;
  20. </create>
  21. <drop></drop>
  22. <dialect-scope name="org.hibernate.dialect.PostgreSQL95Dialect" />
  23. </database-object>
  24. </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

  1. @Entity(name = "Book")
  2. @Check( constraints = "CASE WHEN isbn IS NOT NULL THEN LENGTH(isbn) = 13 ELSE true END")
  3. public static class Book {
  4. @Id
  5. private Long id;
  6. private String title;
  7. @NaturalId
  8. private String isbn;
  9. private Double price;
  10. //Getters and setters omitted for brevity
  11. }

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

  1. Book book = new Book();
  2. book.setId( 1L );
  3. book.setPrice( 49.99d );
  4. book.setTitle( "High-Performance Java Persistence" );
  5. book.setIsbn( "11-11-2016" );
  6. entityManager.persist( book );
  1. INSERT INTO Book (isbn, price, title, id)
  2. VALUES ('11-11-2016', 49.99, 'High-Performance Java Persistence', 1)
  3. -- WARN SqlExceptionHelper:129 - SQL Error: 0, SQLState: 23514
  4. -- 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

  1. @Entity(name = "Person")
  2. @DynamicInsert
  3. public static class Person {
  4. @Id
  5. private Long id;
  6. @ColumnDefault("'N/A'")
  7. private String name;
  8. @ColumnDefault("-1")
  9. private Long clientId;
  10. //Getter and setters omitted for brevity
  11. }
  1. CREATE TABLE Person (
  2. id BIGINT NOT NULL,
  3. clientId BIGINT DEFAULT -1,
  4. name VARCHAR(255) DEFAULT 'N/A',
  5. PRIMARY KEY (id)
  6. )

In the mapping above, both the name and clientId table columns are going to use a DEFAULT value.

The Person entity above is annotated with the @DynamicInsert annotation so that the INSERT statement does not include any entity attribute that does not contain a value.

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

  1. doInJPA( this::entityManagerFactory, entityManager -> {
  2. Person person = new Person();
  3. person.setId( 1L );
  4. entityManager.persist( person );
  5. } );
  6. doInJPA( this::entityManagerFactory, entityManager -> {
  7. Person person = entityManager.find( Person.class, 1L );
  8. assertEquals( "N/A", person.getName() );
  9. assertEquals( Long.valueOf( -1L ), person.getClientId() );
  10. } );
  1. 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

  1. @Entity
  2. @Table(
  3. name = "book",
  4. uniqueConstraints = @UniqueConstraint(
  5. name = "uk_book_title_author",
  6. columnNames = {
  7. "title",
  8. "author_id"
  9. }
  10. )
  11. )
  12. public static class Book {
  13. @Id
  14. @GeneratedValue
  15. private Long id;
  16. private String title;
  17. @ManyToOne(fetch = FetchType.LAZY)
  18. @JoinColumn(
  19. name = "author_id",
  20. foreignKey = @ForeignKey(name = "fk_book_author_id")
  21. )
  22. private Author author;
  23. //Getter and setters omitted for brevity
  24. }
  25. @Entity
  26. @Table(name = "author")
  27. public static class Author {
  28. @Id
  29. @GeneratedValue
  30. private Long id;
  31. @Column(name = "first_name")
  32. private String firstName;
  33. @Column(name = "last_name")
  34. private String lastName;
  35. //Getter and setters omitted for brevity
  36. }
  1. create table author (
  2. id bigint not null,
  3. first_name varchar(255),
  4. last_name varchar(255),
  5. primary key (id)
  6. )
  7. create table book (
  8. id bigint not null,
  9. title varchar(255),
  10. author_id bigint,
  11. primary key (id)
  12. )
  13. alter table book
  14. add constraint uk_book_title_author
  15. unique (title, author_id)
  16. alter table book
  17. add constraint fk_book_author_id
  18. foreign key (author_id)
  19. 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

  1. Author _author = doInJPA( this::entityManagerFactory, entityManager -> {
  2. Author author = new Author();
  3. author.setFirstName( "Vlad" );
  4. author.setLastName( "Mihalcea" );
  5. entityManager.persist( author );
  6. Book book = new Book();
  7. book.setTitle( "High-Performance Java Persistence" );
  8. book.setAuthor( author );
  9. entityManager.persist( book );
  10. return author;
  11. } );
  12. try {
  13. doInJPA( this::entityManagerFactory, entityManager -> {
  14. Book book = new Book();
  15. book.setTitle( "High-Performance Java Persistence" );
  16. book.setAuthor( _author );
  17. entityManager.persist( book );
  18. } );
  19. }
  20. catch (Exception expected) {
  21. assertNotNull( ExceptionUtil.findCause( expected, ConstraintViolationException.class ) );
  22. }
  1. insert
  2. into
  3. author
  4. (first_name, last_name, id)
  5. values
  6. (?, ?, ?)
  7. -- binding parameter [1] as [VARCHAR] - [Vlad]
  8. -- binding parameter [2] as [VARCHAR] - [Mihalcea]
  9. -- binding parameter [3] as [BIGINT] - [1]
  10. insert
  11. into
  12. book
  13. (author_id, title, id)
  14. values
  15. (?, ?, ?)
  16. -- binding parameter [1] as [BIGINT] - [1]
  17. -- binding parameter [2] as [VARCHAR] - [High-Performance Java Persistence]
  18. -- binding parameter [3] as [BIGINT] - [2]
  19. insert
  20. into
  21. book
  22. (author_id, title, id)
  23. values
  24. (?, ?, ?)
  25. -- binding parameter [1] as [BIGINT] - [1]
  26. -- binding parameter [2] as [VARCHAR] - [High-Performance Java Persistence]
  27. -- binding parameter [3] as [BIGINT] - [3]
  28. -- SQL Error: 23505, SQLState: 23505
  29. -- 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

  1. @Entity
  2. @Table(
  3. name = "author",
  4. indexes = @Index(
  5. name = "idx_author_first_last_name",
  6. columnList = "first_name, last_name",
  7. unique = false
  8. )
  9. )
  10. public static class Author {
  11. @Id
  12. @GeneratedValue
  13. private Long id;
  14. @Column(name = "first_name")
  15. private String firstName;
  16. @Column(name = "last_name")
  17. private String lastName;
  18. //Getter and setters omitted for brevity
  19. }
  1. create table author (
  2. id bigint not null,
  3. first_name varchar(255),
  4. last_name varchar(255),
  5. primary key (id)
  6. )
  7. create index idx_author_first_last_name
  8. on author (first_name, last_name)