DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Architecture and Code Design, Pt. 1: Relational Persistence Insights to Use Today and On the Upcoming Years
  • Simplify Java Persistence Using Quarkus and Hibernate Reactive
  • Architecture and Code Design, Pt. 2: Polyglot Persistence Insights To Use Today and in the Upcoming Years
  • How to Store Text in PostgreSQL: Tips, Tricks, and Traps

Trending

  • Context-Aware Authorization for AI Agents
  • How Rule Engines Transform Business Agility and Code Simplicity
  • The Vector Database Lie
  • Comparing Top Gen AI Frameworks for Java in 2026
  1. DZone
  2. Data Engineering
  3. Databases
  4. Mapping Java Entities for Persistence With Hibernate (Part 3)

Mapping Java Entities for Persistence With Hibernate (Part 3)

Learn more about mapping Java entities for persistence using Hibernate.

By 
Mahmoud Anouti user avatar
Mahmoud Anouti
·
Sep. 18, 19 · Tutorial
Likes (5)
Comment
Save
Tweet
Share
16.3K Views

Join the DZone community and get the full member experience.

Join For Free

Image title

Did someone say ''Hibernate?''

In part 2, we went over mapping collections such as a simple set of strings, as well as basic associations between entities. The Book entity was enriched with many-to-one associations to Author and Publisher. In this part, we'll explore how to map subclasses (inheritance) to tables using the various available strategies.

Inheritance is a common aspect of object-oriented languages like Java, but has no support in standard relational databases or SQL. So, Hibernate (and JPA) offers several ways to bridge this in its ORM implementation. Going back to our sample domain model, let's say that the class Book inherits from an abstract class Publication, along with another subclass Magazine:

public abstract class Publication {

    protected Long id;
    protected String title;
    protected Publisher publisher;
    protected Date publishingDate;

    ...
}

public class Book extends Publication {

    protected int volumes;
    protected Set<String> contentTags = new HashSet<>();
    protected Author author;

    ...
}

public class Magazine extends Publication {

    protected int issueNumber;
    protected Schedule schedule;

    ...
}

public enum Schedule {
    WEEKLY, MONTHLY, YEARLY
}


Using @MappedSuperclass

The first strategy to map this inheritance is to have two tables each for the subclasses Book and Magazine, where each table contains columns for attributes in the concrete class as well as those inherited from the parent class Publication. A simple way to do this is to add @MappedSuperclass to Publication and map the subclasses using @Entity just as before:

import javax.persistence.MappedSuperclass;

@MappedSuperclass
public abstract class Publication {

    @Id
    @GeneratedValue(generator = "idGenerator")
    ... // declare idGenerator here or in a shared package-info.java
    protected Long id;

    @Column(nullable = false)
    protected String title;

    @ManyToOne
    @JoinColumn(name = "PUBLISHER_ID", nullable = false)
    protected Publisher publisher;

    @Temporal(TemporalType.DATE)
    protected Date publishingDate;

    ...
}

@Entity
public class Book extends Publication {

    protected int volumes;

    @ElementCollection
    @CollectionTable(name = "CONTENT_TAG",
                 joinColumns = @JoinColumn(name = "BOOK_ID"))
    @Column(name = "TAG", nullable = false)
    protected Set<String> contentTags = new HashSet<>();

    @ManyToOne
    @JoinColumn(name = "AUTHOR_ID", nullable = false)
    protected Author author;

    ...
}

@Entity
public class Magazine extends Publication {

    protected int issueNumber;

    @Enumerated(EnumType.STRING)   // store the enum value as string
    protected Schedule schedule;

    ...
}


This results in two tables (ignoring those for Author and Publisher):

create table Book (
    id bigint not null,
    publishingDate date,
    title varchar(255) not null,
    volumes integer not null,
    PUBLISHER_ID bigint,
    AUTHOR_ID bigint not null,
    primary key (id)
)

create table Magazine (
    id bigint not null,
    publishingDate date,
    title varchar(255) not null,
    issueNumber integer not null,
    schedule varchar(255),
    PUBLISHER_ID bigint not null,
    primary key (id)
)


The main problem with this mapping strategy is that it doesn't easily support polymorphism in Java code. For example, we cannot execute a query on the superclass Publication so that it covers both tables of its subclasses. If the application does not really need such queries, and will explicitly query the specific subclasses, then this approach may be suitable.

Table Per Concrete Class Mapping Using Union for Polymorphic Queries

If we instead map the parent class as an entity and specify InheritanceType.TABLE_PER_CLASS, then we'd end up with the same schema as before, but Hibernate would use UNION to run polymorphic queries:

import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Publication {

    @Id
    @GeneratedValue(generator = "idGenerator")
    ... // declare idGenerator here or in a shared package-info.java
    protected Long id;

    @Column(nullable = false)
    protected String title;

    @ManyToOne
    @JoinColumn(name = "PUBLISHER_ID", nullable = false)
    protected Publisher publisher;

    @Temporal(TemporalType.DATE)
    protected Date publishingDate;

    ...
}


Again, we have only two tables for the concrete classes Book and Magazine, as shown below. Note that if the parent class was not abstract, we would have a third table (remember that this a table-per-concrete-class strategy).

create table Book (
    id bigint not null,
    publishingDate date,
    title varchar(255) not null,
    PUBLISHER_ID bigint not null,
    volumes integer not null,
    AUTHOR_ID bigint not null,
    primary key (id)
)

create table Magazine (
    id bigint not null,
    publishingDate date,
    title varchar(255) not null,
    PUBLISHER_ID bigint not null,
    issueNumber integer not null,
    schedule varchar(255),
    primary key (id)
)


To demonstrate how a polymorphic query is executed by Hibernate, let's query a publication by its title, knowing that it's a book:

Query<Publication> query = session.createQuery("from Publication where title = :title", Publication.class);
query.setParameter("title", "The Lord of the Rings");
Book book = (Book) query.getSingleResult();
assertEquals("The Lord of the Rings", book.getTitle());
assertEquals("1954-07-29", book.getPublishingDate());
assertEquals("J. R. R. Tolkien", book.getAuthor().getName());
assertEquals("Allen & Unwin", book.getPublisher().getName());


If we turn on SQL logging, we see that it used the following query:

select publicatio0_.id as id1_4_, publicatio0_.PUBLISHER_ID as PUBLISHE4_4_,
       publicatio0_.publishingDate as publishi2_4_,
       publicatio0_.title as title3_4_, publicatio0_.AUTHOR_ID as AUTHOR_I2_1_,
       publicatio0_.volumes as volumes1_1_, publicatio0_.issueNumber as issueNum1_3_,
       publicatio0_.schedule as schedule2_3_, publicatio0_.clazz_ as clazz_
from ( select id, publishingDate, title, PUBLISHER_ID, volumes, AUTHOR_ID,
       null as issueNumber, null as schedule, 1 as clazz_
       from Book
       union
       all select id, publishingDate, title, PUBLISHER_ID, null as volumes,
           null as AUTHOR_ID, issueNumber, schedule, 2 as clazz_
       from Magazine ) publicatio0_
where publicatio0_.title=?


Single Table for Class Hierarchy

Another way to map an inheritance hierarchy is to use only one table for all classes. This table would include columns for all attributes of all classes. An additional column, called the discriminator column, tells Hibernate which type each row corresponds to. The advantage is performance since no joins or unions are needed and polymorphism is therefore fast at the level of the DB. The major disadvantage is that subclasses cannot have fields that are nullable=false, since the columns of these fields are also shared by other subclasses that do not have these fields. This leaves the responsibility of enforcing the data integrity to the programmer, using data validation logic in the business code.

import javax.persistence.DiscriminatorColumn;

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "PUBLICATION_TYPE")
public abstract class Publication {

    @Id
    @GeneratedValue(generator = "idGenerator")
    ... // declare idGenerator here or in a shared package-info.java
    protected Long id;

    @Column(nullable = false)
    protected String title;

    @ManyToOne
    @JoinColumn(name = "PUBLISHER_ID", nullable = false)
    protected Publisher publisher;

    @Temporal(TemporalType.DATE)
    protected Date publishingDate;

    ...
}

@Entity
public class Book extends Publication {

    @ManyToOne
    @JoinColumn(name = "AUTHOR_ID")  // must be nullable!
    protected Author author;

    ...
}

@Entity
public class Magazine extends Publication {
    ...
}


The resulting schema is a single table for all classes in the hierarchy:

create table Publication (
    PUBLICATION_TYPE varchar(31) not null,
    id bigint not null,
    publishingDate date,
    title varchar(255) not null,
    volumes integer,
    issueNumber integer,
    schedule varchar(255),
    PUBLISHER_ID bigint not null,
    AUTHOR_ID bigint,
    primary key (id)
)


Queries against the parent type are straightforward — Hibernate queries the single table and uses the specified criteria as the WHERE clause. If the application queries against a specific subclass, Hibernate further uses the discriminator column PUBLICATION_TYPE to filter only the rows for that particular subclass. For example, the JPA query from Book where title = :title would generate the following SQL:

select book0_.id as id2_2_, book0_.PUBLISHER_ID as PUBLISHE8_2_,
       book0_.publishingDate as publishi3_2_, book0_.title as title4_2_,
       book0_.AUTHOR_ID as AUTHOR_I9_2_, book0_.volumes as volumes5_2_
from Publication book0_
where
    book0_.PUBLICATION_TYPE='Book'
    and book0_.title=?


Joined Tables

The fourth mapping strategy is to map each subclass to a table containing columns mapped only to the properties declared in the subclass. The tables are joined via foreign key constraints to the table of their superclass, which contains columns for the inherited properties. Polymorphic queries would use JOIN in the SQL statements using the foreign key columns.

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Publication {

    @Id
    @GeneratedValue(generator = "idGenerator")
    ... // declare idGenerator here or in a shared package-info.java
    protected Long id;

    @Column(nullable = false)
    protected String title;

    @ManyToOne
    @JoinColumn(name = "PUBLISHER_ID", nullable = false)
    protected Publisher publisher;

    @Temporal(TemporalType.DATE)
    protected Date publishingDate;

    ...
}

@Entity
public class Book extends Publication {

    protected int volumes;

    @ElementCollection
    @CollectionTable(name = "CONTENT_TAG",
                 joinColumns = @JoinColumn(name = "BOOK_ID"))
    @Column(name = "TAG", nullable = false)
    protected Set<String> contentTags = new HashSet<>();

    @ManyToOne
    @JoinColumn(name = "AUTHOR_ID", nullable = false)
    protected Author author;

    ...
}

@Entity
public class Magazine extends Publication {

    protected int issueNumber;

    @Enumerated(EnumType.STRING)
    protected Schedule schedule;

    ...
}


The resulting schema is two tables for the subclasses Book and Magazine, joined with a table for the parent class Publication:

create table Book (
    volumes integer not null,
    id bigint not null,
    AUTHOR_ID bigint not null,
    primary key (id)
)

create table Magazine (
    issueNumber integer not null,
    schedule varchar(255),
    id bigint not null,
    primary key (id)
)

create table Publication (
    id bigint not null,
    publishingDate date,
    title varchar(255) not null,
    PUBLISHER_ID bigint not null,
    primary key (id)
)

alter table Book 
    add constraint FKh7kfm44rlyes4hoxg70sw2v7k 
    foreign key (id) 
    references Publication

alter table Magazine 
    add constraint FKg27disaxrv1fevl8118yt1ejr 
    foreign key (id) 
    references Publication


Queries would use JOIN in order to retrieve all columns for an entity (both those for the subclass fields and those inherited). As an example, the query from Publication where title = :title would result in the following SQL:

select publicatio0_.id as id1_4_, publicatio0_.PUBLISHER_ID as PUBLISHE4_4_,
       publicatio0_.publishingDate as publishi2_4_, publicatio0_.title as title3_4_,
       publicatio0_1_.AUTHOR_ID as AUTHOR_I3_1_, publicatio0_1_.volumes as volumes1_1_,
       publicatio0_2_.issueNumber as issueNum1_3_, publicatio0_2_.schedule as schedule2_3_,
       case
           when publicatio0_1_.id is not null then 1
           when publicatio0_2_.id is not null then 2
           when publicatio0_.id is not null then 0
       end as clazz_
from Publication publicatio0_
left outer join
    Book publicatio0_1_
    on publicatio0_.id=publicatio0_1_.id
left outer join
    Magazine publicatio0_2_
    on publicatio0_.id=publicatio0_2_.id
where publicatio0_.title=?


Hibernate used a case clause to determine the correct class type based on the presence of the id column.

Stay tuned for part 4...

Further Reading

Mapping Java Entities for Persistence in Hibernate (Part 1)

Hibernate Mapping

Relational database Database Hibernate sql Java (programming language) Persistence (computer science)

Published at DZone with permission of Mahmoud Anouti. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Architecture and Code Design, Pt. 1: Relational Persistence Insights to Use Today and On the Upcoming Years
  • Simplify Java Persistence Using Quarkus and Hibernate Reactive
  • Architecture and Code Design, Pt. 2: Polyglot Persistence Insights To Use Today and in the Upcoming Years
  • How to Store Text in PostgreSQL: Tips, Tricks, and Traps

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook