Java Persistence/Inheritance

From Wikibooks, open books for an open world
Jump to navigation Jump to search
An example of inheritance. SmallProject and LargeProject inherit the properties of their common parent, Project.

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[edit | edit source]

Single 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[edit | edit source]

PROJECT (table)

ID PROJ_TYPE NAME BUDGET
1 L Accounting 50000
2 S Legal null


Example single table inheritance annotations[edit | edit source]

@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 | edit source]

<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[edit | edit source]

No class discriminator column[edit | edit source]

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 the ClassDescriptor's InheritancePolicy's setClassExtractor() method.
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.

Non nullable attributes[edit | edit source]

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[edit | edit source]

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

Hibernate: A discriminator column on joined inheritance is supported but not required.[1]

Example joined inheritance tables in database[edit | edit source]

PROJECT (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 | edit source]

@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 | edit source]

<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[edit | edit source]

Poor query performance[edit | edit source]

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 the ClassDescriptor's InheritancePolicy's setShouldOuterJoinSubclasses() method.

Do not have/want a table for every subclass[edit | edit source]

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 or SecondaryTable 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 | edit source]

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 the ClassDescriptor's InheritancePolicy's setClassExtractor() method.
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.

Advanced[edit | edit source]

Table Per Class Inheritance[edit | edit source]

Table 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[edit | edit source]

SMALLPROJECT (table)

ID NAME
2 Legal

LARGEPROJECT (table)

ID NAME BUDGET
1 Accounting 50000

Example table per class inheritance annotations[edit | edit source]

@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 | edit source]

<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[edit | edit source]

Poor query performance[edit | edit source]

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 | edit source]

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[edit | edit source]

Mapped 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[edit | edit source]

SMALLPROJECT (table)

ID NAME
2 Legal

LARGEPROJECT (table)

ID PROJECT_NAME BUDGET
1 Accounting 50000

Example mapped superclass annotations[edit | edit source]

@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 | edit source]

<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[edit | edit source]

Cannot query, persist, or have relationships[edit | edit source]

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 | edit source]

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 from Bar but don't want to inherit it, then remove it from Bar, if the other subclasses need it, either add it to each, or create a FooBar subclass of Bar that has the foo 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 the ClassDescriptor's mapping, or adding a mapping with the same attribute name, or removing the InheritancePolicy.