Java Persistence/Inheritance
Inheritance is a fundamental concept of object-oriented programming and Java. Relational databases have no concept of inheritance, so persisting inheritance in a database can be tricky. Because relational databases have no concept of inheritance, there is no standard way of implementing inheritance in database, so the hardest part of persisting inheritance is choosing how to represent the inheritance in the database.
JPA defines several inheritance mechanisms, mainly defined though the @Inheritance
annotation or the <inheritance>
element. There are three inheritance strategies defined from the InheritanceType
enum, SINGLE_TABLE
, TABLE_PER_CLASS
and JOINED
.
Single table inheritance is the default, and table per class is an optional feature of the JPA spec, so not all providers may support it. JPA also defines a mapped superclass concept defined though the @MappedSuperclass
annotation or the <mapped-superclass>
element. A mapped superclass is not a persistent class, but allow common mappings to be defined for its subclasses.
Single Table Inheritance
editSingle table inheritance is the simplest and typically the best performing and best solution. In single table inheritance a single table is used to store all of the instances of the entire inheritance hierarchy. The table will have a column for every attribute of every class in the hierarchy. A discriminator column is used to determine which class the particular row belongs to, each class in the hierarchy defines its own unique discriminator value.
Example single table inheritance table in database
editPROJECT (table)
ID | PROJ_TYPE | NAME | BUDGET |
1 | L | Accounting | 50000 |
2 | S | Legal | null |
Example single table inheritance annotations
edit@Entity
@Inheritance
@DiscriminatorColumn(name="PROJ_TYPE")
@Table(name="PROJECT")
public class Project implements Serializable{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
...
}
@Entity
@DiscriminatorValue("L")
public class LargeProject extends Project {
private BigDecimal budget;
}
@Entity
@DiscriminatorValue("S")
public class SmallProject extends Project {
}
Example single table inheritance XML
edit<entity name="Project" class="org.acme.Project" access="FIELD">
<table name="PROJECT"/>
<inheritance/>
<discriminator-column name="PROJ_TYPE"/>
<attributes>
<id name="id"/>
...
</attributes>
</entity>
<entity name="LargeProject" class="org.acme.LargeProject" access="FIELD">
<discriminator-value>L</discriminator-value>
...
</entity>
<entity name="SmallProject" class="org.acme.SmallProject" access="FIELD">
<discriminator-value>S</discriminator-value>
</entity>
Common Problems
editNo class discriminator column
edit- If you are mapping to an existing database schema, your table may not have a class discriminator column. Some JPA providers do not require a class discriminator when using a joined inheritance strategy, so this may be one solution. Otherwise you need some way to determine the class for a row. Sometimes the inherited value can be computed from several columns, or there is an discriminator but not a one to one mapping from value to class. Some JPA providers provide extended support for this. Another option is to create a database view that manufactures the discriminator column, and then map your hierarchy to this view instead of the table. In general the best solution is just to add a discriminator column to the table (truth be told, ALTER TABLE is your best friend in ORM).
- TopLink / EclipseLink : Support computing the inheritance discriminator through Java code. This can be done through using a
DescriptorCustomizer
and theClassDescriptor
'sInheritancePolicy
'ssetClassExtractor()
method.
- TopLink / EclipseLink : Support computing the inheritance discriminator through Java code. This can be done through using a
- Hibernate : This can be accomplished through using the Hibernate
@DiscriminatorFormula
annotation. This allows database specific SQL or functions to be used to compute the discriminator value.
- Hibernate : This can be accomplished through using the Hibernate
Non nullable attributes
edit- Subclasses cannot define attributes as not allowing null, as the other subclasses must insert null into those columns. A workaround to this issue is instead of defining a not null constraint on the column, define a table constraint that check the discriminator value and the not nullable value. In general the best solution is to just live without the constraint (odds are you have enough constraints in your life to deal with as it is).
Joined, Multiple Table Inheritance
editJoined inheritance is the most logical inheritance solution because it mirrors the object model in the data model. In joined inheritance a table is defined for each class in the inheritance hierarchy to store only the local attributes of that class. Each table in the hierarchy must also store the object's id (primary key), which is only defined in the root class. All classes in the hierarchy must share the same id attribute. A discriminator column is used to determine which class the particular row belongs to, each class in the hierarchy defines its own unique discriminator value.
Some JPA providers support joined inheritance with or without a discriminator column, some required the discriminator column, and some do not support the discriminator column. So joined inheritance does not seem to be fully standardized yet.
Example joined inheritance tables in database
editPROJECT (table)
ID | PROJ_TYPE | NAME |
1 | L | Accounting |
2 | S | Legal |
SMALLPROJECT (table)
ID |
2 |
LARGEPROJECT (table)
ID | BUDGET |
1 | 50000 |
Example joined inheritance annotations
edit@Entity
@Inheritance(strategy=InheritanceType.JOINED)
@DiscriminatorColumn(name="PROJ_TYPE")
@Table(name="PROJECT")
public abstract class Project {
@Id
private long id;
private String name;
...
}
@Entity
@DiscriminatorValue("L")
@Table(name="LARGEPROJECT")
public class LargeProject extends Project {
private BigDecimal budget;
}
@Entity
@DiscriminatorValue("S")
@Table(name="SMALLPROJECT")
public class SmallProject extends Project {
}
Example joined inheritance XML
edit<entity name="Project" class="org.acme.Project" access="FIELD">
<table name="PROJECT"/>
<inheritance strategy="JOINED"/>
<discriminator-column name="PROJ_TYPE"/>
<attributes>
<id name="id"/>
...
</attributes>
</entity>
<entity name="LargeProject" class="org.acme.LargeProject" access="FIELD">
<table name="LARGEPROJECT"/>
<discriminator-value>L</discriminator-value>
...
</entity>
<entity name="SmallProject" class="org.acme.SmallProject" access="FIELD">
<table name="SMALLPROJECT"/>
<discriminator-value>S</discriminator-value>
</entity>
Common Problems
editPoor query performance
edit- The main disadvantage to the joined model is that to query any class join queries are required. Querying the root or branch classes is even more difficult as either multiple queries are required, or outer joins or unions are required. One solution is to use single table inheritance instead, this is good if the classes have a lot in common, but if it is a big hierarchy and the subclasses have little in common this may not be desirable. Another solution is to remove the inheritance and instead use a
MappedSuperclass
, but this means that you can no longer query or have relationships to the class.
- The poorest performing queries will be those to the root or branch classes. Avoiding queries and relationships to the root and branch classes will help to alleviate this burden. If you must query the root or branch classes there are two methods that JPA providers use, one is to outer join all of the subclass tables, the second is to first query the root table, then query only the required subclass table directly. The first method has the advantage of only requiring one query, the second has the advantage of avoiding outer joins which typically have poor performance in databases. You may wish to experiment with each to determine which mechanism is more efficient in your application and see if your JPA provider supports that mechanism. Typically the multiple query mechanism is more efficient, but this generally depends on the speed of your database connection.
- TopLink / EclipseLink : Support both querying mechanisms. The multiple query mechanism is used by default. Outer joins can be used instead through using a
DescriptorCustomizer
and theClassDescriptor
'sInheritancePolicy
'ssetShouldOuterJoinSubclasses()
method.
- TopLink / EclipseLink : Support both querying mechanisms. The multiple query mechanism is used by default. Outer joins can be used instead through using a
Do not have/want a table for every subclass
edit- Most inheritance hierarchies do not fit with either the joined or the single table inheritance strategy. Typically the desired strategy is somewhere in between, having joined tables in some subclasses and not in others. Unfortunately JPA does not directly support this. One workaround is to map your inheritance hierarchy as single table, but then add the additional tables in the subclasses, either through defining a
Table
orSecondaryTable
in each subclass as required. Depending on your JPA provider, this may work (don't forget to sacrifice the chicken). If it does not work, then you may need to use a JPA provider specific solution if one exists for your provider, otherwise live within the constraints of having either a single table or one per subclass. You could also change your inheritance hierarchy so it matches your data model, so if the subclass does not have a table, then collapse its class into its superclass.
No class discriminator column
edit- If you are mapping to an existing database schema, your table may not have a class discriminator column. Some JPA providers do not require a class discriminator when using a joined inheritance strategy, so this may be one solution. Otherwise you need some way to determine the class for a row. Sometimes the inherited value can be computed from several columns, or there is an discriminator but not a one to one mapping from value to class. Some JPA providers provide extended support for this. Another option is to create a database view that manufactures the discriminator column, and then map your hierarchy to this view instead of the table.
- TopLink / EclipseLink : Support computing the inheritance discriminator through Java code. This can be done through using a
DescriptorCustomizer
and theClassDescriptor
'sInheritancePolicy
'ssetClassExtractor()
method.
- TopLink / EclipseLink : Support computing the inheritance discriminator through Java code. This can be done through using a
- Hibernate : This can be accomplished through using the Hibernate
@DiscriminatorFormula
annotation. This allows database specific SQL or functions to be used to compute the discriminator value.
- Hibernate : This can be accomplished through using the Hibernate
Advanced
editTable Per Class Inheritance
editTable per class inheritance allows inheritance to be used in the object model, when it does not exist in the data model. In table per class inheritance a table is defined for each concrete class in the inheritance hierarchy to store all the attributes of that class and all of its superclasses. Be cautious using this strategy as it is optional in the JPA spec, and querying root or branch classes can be very difficult and inefficient.
Example table per class inheritance tables in database
editSMALLPROJECT (table)
ID | NAME |
2 | Legal |
LARGEPROJECT (table)
ID | NAME | BUDGET |
1 | Accounting | 50000 |
Example table per class inheritance annotations
edit@Entity
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public abstract class Project {
@Id
private long id;
...
}
@Entity
@Table(name="LARGEPROJECT")
public class LargeProject extends Project {
private BigDecimal budget;
}
@Entity
@Table(name="SMALLPROJECT")
public class SmallProject extends Project {
}
Example table per class inheritance XML
edit<entity name="Project" class="org.acme.Project" access="FIELD">
<inheritance strategy="TABLE_PER_CLASS"/>
<attributes>
<id name="id"/>
...
</attributes>
</entity>
<entity name="LargeProject" class="org.acme.LargeProject" access="FIELD">
<table name="LARGEPROJECT"/>
...
</entity>
<entity name="SmallProject" class="org.acme.SmallProject" access="FIELD">
<table name="SMALLPROJECT"/>
</entity>
Common Problems
editPoor query performance
edit- The main disadvantage to the table per class model is queries or relationships to the root or branch classes become expensive. Querying the root or branch classes require multiple queries, or unions. One solution is to use single table inheritance instead, this is good if the classes have a lot in common, but if it is a big hierarchy and the subclasses have little in common this may not be desirable. Another solution is to remove the table per class inheritance and instead use a
MappedSuperclass
, but this means that you can no longer query or have relationships to the class.
Issues with ordering and joins
edit- Because table per class inheritance requires multiple queries, or unions, you cannot join to, fetch join, or traverse them in queries. Also when ordering is used the results will be ordered by class, then by the ordering. These limitations depend on your JPA provider, some JPA provider may have other limitations, or not support table per class at all as it is optional in the JPA spec.
Mapped Superclasses
editMapped superclass inheritance allows inheritance to be used in the object model, when it does not exist in the data model. It is similar to table per class inheritance, but does not allow querying, persisting, or relationships to the superclass. Its main purpose is to allow mappings information to be inherited by its subclasses. The subclasses are responsible for defining the table, id and other information, and can modify any of the inherited mappings. A common usage of a mapped superclass is to define a common PersistentObject
for your application to define common behavior and mappings such as the id and version. A mapped superclass normally should be an abstract class. A mapped superclass is not an Entity
but is instead defined through the @MappedSuperclass
annotation or the <mapped-superclass>
element.
Example mapped superclass tables in database
editSMALLPROJECT (table)
ID | NAME |
2 | Legal |
LARGEPROJECT (table)
ID | PROJECT_NAME | BUDGET |
1 | Accounting | 50000 |
Example mapped superclass annotations
edit@MappedSuperclass
public abstract class Project {
@Id
private long id;
@Column(name="NAME")
private String name;
...
}
@Entity
@Table(name="LARGEPROJECT")
@AttributeOverride(name="NAME", column=@Column(name="PROJECT_NAME"))
public class LargeProject extends Project {
private BigDecimal budget;
}
@Entity
@Table("SMALLPROJECT")
public class SmallProject extends Project {
}
Example mapped superclass XML
edit<mapped-superclass class="org.acme.Project" access="FIELD">
<attributes>
<id name="id"/>
<basic name="name">
<column name="NAME"/>
</basic>
...
</attributes>
</mapped-superclass>
<entity name="LargeProject" class="org.acme.LargeProject" access="FIELD">
<table name="LARGEPROJECT"/>
<attribute-override name="name">
<column name="PROJECT_NAME"/>
</attribute-override>
...
</entity>
<entity name="SmallProject" class="org.acme.SmallProject" access="FIELD">
<table name="SMALLPROJECT"/>
</entity>
Common Problems
editCannot query, persist, or have relationships
edit- The main disadvantage of mapped superclasses is that they cannot be queried or persisted. You also cannot have a relationship to a mapped superclass. If you require any of these then you must use another inheritance model, such as table per class, which is virtually identical to a mapped superclass except it (may) not have these limitations. Another alternative is to change your model such that your classes do not have relationships to the superclass, such as changing the relationship to a subclass, or removing the relationship and instead querying for its value by querying each possible subclass and collecting the results in Java.
Subclass does not want to inherit mappings
edit- Sometimes you have a subclass that needs to be mapped differently than its parent, or is similar to its' parent but does not have one of the fields, or uses it very differently. Unfortunately it is very difficult not to inherit everything from your parent in JPA, you can override a mapping, but you cannot remove one, or change the type of mapping, or the target class. If you define your mappings as properties (get methods), or through XML, you may be able to attempt to override or mark the inherited mapping as
Transient
, this may work depending on your JPA provider (don't forget to sacrifice a chicken).
- Another solution is to actually fix your inheritance in your object model. If you inherit
foo
fromBar
but don't want to inherit it, then remove it fromBar
, if the other subclasses need it, either add it to each, or create aFooBar
subclass ofBar
that has thefoo
and have the other subclasses extend this.
- Some JPA providers may provide ways to be less stringent on inheritance.
- TopLink / EclipseLink : Allow a subclass remove a mapping, redefine a mapping, or be entirely independent of its superclass. This can be done through using a
DescriptorCustomizer
and removing theClassDescriptor
's mapping, or adding a mapping with the same attribute name, or removing theInheritancePolicy
.
- TopLink / EclipseLink : Allow a subclass remove a mapping, redefine a mapping, or be entirely independent of its superclass. This can be done through using a