In this post I want to present four basic strategies for mapping class inheritance in Hibernate:
- No inheritance - just copy superclass properties to subclasses
- Table per class hierarchy
- Table per concrete class
- Table per every class
No inheritance
This strategy is used if we want to share Java code between entity classes.
An example will show us how it works.
Let’s say we want to avoid declaring id
and version
fields in every entity
class.
We can solve this by creating abstract superclass BaseEntity
that will hold common code
and annotating it with
@MappedSuperclass
to enable no inheritance strategy.
Here is BaseEntity
class code:
@MappedSuperclass
public abstract class BaseEntity {
@Id
@GeneratedValue
private Long id;
@Version
private long version;
// getters/setters etc.
}
Now we may create two independent entity classes User
and Product
that
will inherit id
and version
fields with mappings from BaseEntity
:
@Entity
@Table(name = "\"user\"")
public class User extends BaseEntity {
private String username;
@org.hibernate.annotations.Type(type = "yes_no")
private boolean isAdmin;
// constructor/getters/setters etc.
}
@Entity
public class Product extends BaseEntity {
public String name;
public BigDecimal price;
// constructor/getters/setters etc.
}
For given classes Hibernate will generate database schema:
Since this strategy is used to only share Java code we should not query database
for BaseEntity
instances. If we do Hibernate will execute many select statements -
one for every class inheriting from BaseEntity
.
For example:
entityManager.unwrap(Session.class)
.createCriteria(BaseEntity.class)
.list();
Will result in queries:
select
this_.id as id1_0_0_,
this_.version as version2_0_0_,
this_.name as name3_0_0_,
this_.price as price4_0_0_
from
product this_
select
this_.id as id1_1_0_,
this_.version as version2_1_0_,
this_.isAdmin as isAdmin3_1_0_,
this_.username as username4_1_0_
from
"user" this_
NOTE: Querying for BaseEntity
via JPA will throw exception with message
Not an entity: class BaseEntity
. We
can query for BaseEntity
only via Hibernate Session
object.
We will use our BaseEntity
class in the example code of the remaining strategies to show that
it can be mixed with “real” ORM inheritance.
Table per class hierarchy
In this strategy all subclasses data will be stored in single table. A special column called discriminator is added to that table to help Hibernate know which subclass is stored in given row.
An example will show how it works. We start by creating a superclass called Animal
:
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "animal_type")
public abstract class Animal extends BaseEntity {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
And two subclasses Cat
and Dog
:
@Entity
@DiscriminatorValue("cat")
public class Cat extends Animal {
private boolean isPurring;
// getters/setter etc.
}
@Entity
@DiscriminatorValue("dog")
public class Dog extends Animal {
private boolean isBarking;
// getters/setter etc.
}
To enable table per hierarchy strategy, superclass must be marked as @Entity
and must have
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
annotation.
We can choose discriminator column name and type using @DiscriminatorColumn
annotation.
Subclasses must be marked as @Entity
and can provide values for
discriminator column via @DiscriminatorValue
annotation (discriminator value defaults to class name).
For this example Hibernate will generate schema:
Let’s save some data:
Cat cat = new Cat();
cat.setName("kitty");
cat.setPurring(true);
entityManager.persist(cat);
Dog dog = new Dog();
dog.setName("barky");
dog.setBarking(false);
entityManager.persist(dog);
And check how they are stored in database:
Saving was easy, now let’s check querying. We start
by getting all Animal
instances from database:
entityManager.createQuery("from Animal")
.getResultList()
.forEach(a -> System.out.println(a));
This will result in SQL query:
select
animal0_.id as id2_0_,
animal0_.version as version3_0_,
animal0_.name as name4_0_,
animal0_.isBarking as isBarkin5_0_,
animal0_.isPurring as isPurrin6_0_,
animal0_.animal_type as animal_t1_0_
from
Animal animal0_
With this strategy we may also query specific animal types e.g. cats:
entityManager.createQuery(
"select c from Cat c where c.isPurring = true")
This will result in the following SQL:
select
cat0_.id as id2_0_,
cat0_.version as version3_0_,
cat0_.name as name4_0_,
cat0_.isPurring as isPurrin6_0_
from
Animal cat0_
where
cat0_.animal_type='cat'
and cat0_.isPurring=true
We can see that Hibernate added test for discriminator column animal_type='cat'
to
limit returned animals to cats only.
Before we move to next strategy let’s see what are pros and cons of table per hierarchy strategy:
pros |
---|
Fast - no joins are needed to retrieve data |
Simple - only single table is needed in database |
cons |
---|
Cannot create constrains in database - all columns representing subclass data must be nullable. This is serious drawback because without constrains data can be easily corrupted by application bug or by inattentive users |
Wasted space - when subclasses have many fields shared table will contain many columns most of which will contain NULL s |
Table per concrete class
This inheritance strategy will generate database table per each concrete class in
the hierarchy. Let’s demonstrate on example.
Given classes (abstract classes are in the blue boxes):
This strategy will generate tables for
Dog
, GrumpyCat
and Kitten
classes.
Here is code for all classes used in this example:
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Animal extends BaseEntity {
private String name;
// ...
}
@Entity
public class Dog extends Animal {
private boolean isBarking;
// ...
}
@Entity
public abstract class AbstractCat extends Animal {
private boolean isPurring;
// ...
}
@Entity
public class GrumpyCat extends AbstractCat {
private int grumpiness;
// ...
}
@Entity
public class Kitten extends AbstractCat {
public int sweetness;
// ...
}
To enable table per concrete class strategy, root of the inheritance hierarchy must
be marked as @Entity
and must have @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
annotation. All subclasses (even abstract ones) must be marked as @Entity
. Abstract classes
will not be represented in database.
For our Animal
example Hibernate will generate database schema:
When we query for all instances of Animal
:
entityManager.createQuery("from Animal")
.getResultList();
Hibernate uses SQL subquery and union all
operator to gather rows from
all tables containing subclasses data. To differentiate between subclasses
in the result set, Hibernate adds special column clazz_
that will contain different numbers
for different subclasses:
select
animal0_.id as id1_1_, animal0_.version as version2_1_, animal0_.name as name3_1_, animal0_.isBarking as isBarkin1_2_, animal0_.isPurring as isPurrin1_0_, animal0_.grumpiness as grumpine1_3_, animal0_.sweetness as sweetnes1_4_,
animal0_.clazz_ as clazz_
from
( select
id, version, name, null::boolean as isBarking, isPurring, grumpiness, null::int4 as sweetness,
3 as clazz_
from
GrumpyCat
union all
select
id, version, name, null::boolean as isBarking, isPurring, null::int4 as grumpiness, sweetness,
4 as clazz_
from
Kitten
union all
select
id, version, name, isBarking, null::boolean as isPurring, null::int4 as grumpiness, null::int4 as sweetness,
1 as clazz_
from
Dog
) animal0_
When we query for one of the concrete subclasses Hibernate will directly query table containing that subclass data. For example:
entityManager.createQuery(
"select gc from GrumpyCat gc where gc.grumpiness > 0"
).getResultList()
Will generate SQL query:
select
grumpycat0_.id as id1_1_,
grumpycat0_.version as version2_1_,
grumpycat0_.name as name3_1_,
grumpycat0_.isPurring as isPurrin1_0_,
grumpycat0_.grumpiness as grumpine1_3_
from
GrumpyCat grumpycat0_
where
grumpycat0_.grumpiness>0
Before we move to the next strategy let’s consider one more example. Say
we want to store photos of animals using Photo
entity:
@Entity
public class Photo extends BaseEntity {
@ManyToOne
private Animal animal;
private String photoFilename;
// ...
}
For this class Hibernate will generate table:
CREATE TABLE photo
(
id bigint NOT NULL,
version bigint NOT NULL,
photofilename character varying(255),
animal_id bigint,
CONSTRAINT photo_pkey PRIMARY KEY (id)
)
This time I used SQL instead of table picture to show an important fact.
Do you see animal_id
column in that table, it will be used to
connect photos to animals. Unfortunately because various types of animals are
stored in different tables we cannot create foreign key constraint on
that column, this is serious drawback of table per concrete class strategy.
When we query for all photos with animals:
entityManager.createQuery(
"select p from Photo p join fetch p.animal"
).getResultList()
Hibernate will execute this monstrous query:
select
photo0_.id as id1_5_0_, animal1_.id as id1_1_1_, photo0_.version as version2_5_0_, photo0_.animal_id as animal_i4_5_0_, photo0_.photoFilename as photoFil3_5_0_, animal1_.version as version2_1_1_, animal1_.name as name3_1_1_, animal1_.isBarking as isBarkin1_2_1_, animal1_.isPurring as isPurrin1_0_1_, animal1_.grumpiness as grumpine1_3_1_, animal1_.sweetness as sweetnes1_4_1_, animal1_.clazz_ as clazz_1_
from
Photo photo0_
inner join
(
select
id, version, name, null::boolean as isBarking, isPurring, grumpiness, null::int4 as sweetness, 3 as clazz_
from
GrumpyCat
union
all select
id, version, name, null::boolean as isBarking, isPurring, null::int4 as grumpiness, sweetness, 4 as clazz_
from
Kitten
union
all select
id, version, name, isBarking, null::boolean as isPurring, null::int4 as grumpiness, null::int4 as sweetness, 1 as clazz_
from
Dog
) animal1_
on photo0_.animal_id=animal1_.id
I only add that complicated queries like this may cause serious performance problems.
To sum up here are pros and cons of table per concrete class strategy:
pros |
---|
Constraint friendly - You can introduce separate database constrains for each concrete subclass |
Fast when querying concrete subclasses (queries directly access subclass table) |
cons |
---|
May be slow when you query/lazy load abstract superclasses (union all and subquery) |
Cannot introduce foreign key constrains for superclasses references (like in Photo example) |
As you see when you are not using references to superclasses in your model this is strategy to go. When you have many references to superclasses it is better to use single table or table per every class strategies.
Table per every class
To demonstrate table per every class strategy we’ll use the same example
that was used in the
description of table per concrete class strategy:
Here are Java classes annotated to use table per every class strategy:
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Animal extends BaseEntity {
private String name;
// ...
}
@Entity
@PrimaryKeyJoinColumn(name = "cat_id")
public abstract class AbstractCat extends Animal {
private boolean isPurring;
// ...
}
@Entity
public class GrumpyCat extends AbstractCat {
private int grumpiness;
// ...
}
@Entity
public class Kitten extends AbstractCat {
public int sweetness;
// ...
}
@Entity
@PrimaryKeyJoinColumn(name = "dog_id")
public class Dog extends Animal {
private boolean isBarking;
// ...
}
Notice that every class is marked as @Entity
and root of inheritance hierarchy is
annotated with @Inheritance(strategy = InheritanceType.JOINED)
. By default
primary keys in tables corresponding to mapped classes will be named id
. Sometimes
it is useful to change name of the primary key column, we can use
@PrimaryKeyJoinColumn(name = "primary_key_name")
to provide new primary key column name.
For given example Hibernate will generate database schema:
Notice that Hibernate generated table per every class in the hierarchy. Lines between tables
represent foreign key constrains inside database.
In table per every class strategy data from superclasses will be stored in dedicated tables, SQL joins will be used to gather entity data from all superclasses. When we add new entity to database Hibernate will split it according to inheritance hierarchy and will execute many inserts. An example will help us understand how it works.
First let’s try to insert some grumpy animals into database:
GrumpyCat cat = new GrumpyCat();
cat.setName("grumpy");
cat.setPurring(true);
cat.setGrumpiness(130);
entityManager.persist(cat);
To save single GrumpyCat
entity to database Hibernate must generate three inserts:
insert into
Animal
(version, name, id)
values
(0, 'grumpy', 1)
insert into
AbstractCat
(isPurring, cat_id)
values
(true, 1)
insert into
GrumpyCat
(grumpiness, cat_id)
values
(130, 1)
Let’s see what will happen when we try to load all GrupyCat
s from database:
entityManager
.createQuery("from GrumpyCat")
.getResultList()
Hibernate will execute following SQL query:
select
grumpycat0_.cat_id as id1_1_,
grumpycat0_2_.version as version2_1_,
grumpycat0_2_.name as name3_1_,
grumpycat0_1_.isPurring as isPurrin1_0_,
grumpycat0_.grumpiness as grumpine1_3_
from
GrumpyCat grumpycat0_
inner join
AbstractCat grumpycat0_1_
on grumpycat0_.cat_id=grumpycat0_1_.cat_id
inner join
Animal grumpycat0_2_
on grumpycat0_.cat_id=grumpycat0_2_.id
As we can see Hibernate used inner join
s to gather GrumpyCat
data
that was split into GrumpyCat
, AbstractCat
and Animal
tables.
If we would query for Animal
s there would be ever more join
s.
Now let’s see how storing/retrieving animal photos changed in table
per every class strategy. We will use the same Photo
class as in previous example:
@Entity
public class Photo extends BaseEntity {
@ManyToOne
private Animal animal;
private String photoFilename;
// ...
}
First we must notice that Photo
table now has a foreign key constrain to Animal
table:
CREATE TABLE photo
(
id bigint NOT NULL,
version bigint NOT NULL,
photofilename character varying(255),
animal_id bigint,
CONSTRAINT photo_pkey PRIMARY KEY (id),
CONSTRAINT fk6mbbc9717gifwpiqhd13t060r FOREIGN KEY (animal_id)
REFERENCES animal (id) MATCH SIMPLE
)
When we try to query database for all photos with animals:
entityManager.createQuery(
"select p from Photo p join fetch p.animal"
).getResultList()
Hibernate will execute query:
select
photo0_.id as id1_5_0_, animal1_.id as id1_1_1_, photo0_.version as version2_5_0_, photo0_.animal_id as animal_i4_5_0_, photo0_.photoFilename as photoFil3_5_0_, animal1_.version as version2_1_1_, animal1_.name as name3_1_1_, animal1_1_.isBarking as isBarkin1_2_1_, animal1_2_.isPurring as isPurrin1_0_1_, animal1_3_.grumpiness as grumpine1_3_1_, animal1_4_.sweetness as sweetnes1_4_1_,
case
when animal1_3_.cat_id is not null then 3
when animal1_4_.cat_id is not null then 4
when animal1_1_.dog_id is not null then 1
when animal1_2_.cat_id is not null then 2
when animal1_.id is not null then 0
end as clazz_1_
from
Photo photo0_
inner join
Animal animal1_
on photo0_.animal_id=animal1_.id
left outer join
Dog animal1_1_
on animal1_.id=animal1_1_.dog_id
left outer join
AbstractCat animal1_2_
on animal1_.id=animal1_2_.cat_id
left outer join
GrumpyCat animal1_3_
on animal1_.id=animal1_3_.cat_id
left outer join
Kitten animal1_4_
on animal1_.id=animal1_4_.cat_id
This is really heavy query if plenty of join
s, it may cause some performance problems.
Let’s end by presenting pros and cons of table per every class strategy:
pros |
---|
Constraint friendly - we may easily add constrains to database. References to superclasses are guarded by foreign key constrains. |
cons |
---|
Poor performance - simple operations like saving entity to database or reading entity from database often require many SQL statements or complicated SQL queries with joins |
Before using this strategy you should consider using simpler and faster table per hierarchy strategy. Use this strategy only if you have many subclasses that define many fields that cannot by shared using superclass.
The End
That was really long post, I hope it help you understand various inheritance strategies
that we can use in Hibernate. As always with ORM’s the key to master this material is
to spend few hours creating dummy models with mappings and checking what queries Hibernate
generate.