Single Table per Class Hierarchy
In the single table per class hierarchy
mapping strategy, one database table represents every class of a given hierarchy. In our example, the Person, Customer, and Employee entities are represented in the same table, as shown in the following code:
create table PERSON_HIERARCHY
(
id integer primary key not null,
firstName varchar(255),
lastName varchar(255),
street varchar(255),
city varchar(255),
state varchar(255),
zip varchar(255),
employeeId integer,
DISCRIMINATOR varchar(31) not null
);
As you can see, all the properties for the Customer class hierarchy are held in one table, PERSON_HIERARCHY. The single table per class hierarchy mapping also requires an additional discriminator column. This column identifies the type of entity being stored in a particular row of PERSON_HIERARCHY. Let's look at how the classes will use annotations to map this inheritance strategy:
@Entity
@Table(name="PERSON_HIERARCHY")
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="DISCRIMINATOR",
discriminatorType=DiscriminatorType.STRING)
@DiscriminatorValue("PERSON")
public class Person {
private int id;
private String firstName;
private String lastName;
@Id @GeneratedValue
public int getId( ) { return id; }
public void setId(int id) { this.id = id; }
public String getFirstName( ) { return firstName; }
public void setFirstName(String first) { this.firstName = first; }
public String getLastName( ) { return lastName; }
public void setLastName(String last) { this.lastName = last; }
}
The @javax.persistence.Inheritance
annotation is used to define the persistence strategy for the inheritance relationship:
package javax.persistence;
@Target(TYPE) @Retention(RUNTIME)
public @interface Inheritance {
InheritanceType strategy( ) default SINGLE_TABLE;
}
public enum InheritanceType {
SINGLE_TABLE, JOINED, TABLE_PER_CLASS
}
The strategy( ) attribute defines the inheritance mapping that we're using. Since we're using the single table per class hierarchy, the SINGLE_TABLE enum is applied. The @Inheritance
annotation only has to be placed on the root of the class hierarchy, unless you are changing the mapping strategy of a subclass.
package javax.persistence;
@Target(TYPE) @Retention(RUNTIME)
public @interface DiscriminatorColumn
String name( ) default "DTYPE";
DiscriminatorType discriminatorType( ) default STRING;
String columnDefinition( ) default "";
int length( ) default 10;
}
Since one table is representing the entire class hierarchy, the persistence provider needs some way to identify which class the row in the database maps to. It determines this by reading the value from the discriminator column. The @javax.persistence.DiscriminatorColumn
annotation identifies which column in our table will store the discriminator's value. The name( ) attribute identifies the name of the column, and the discriminatorType( ) attribute specifies what type the discriminator column will be. It can be a STRING, CHAR, or INTEGER
. For our Customer class hierarchy mapping, you do not have to specify the discriminatorType( ), as it defaults to being a STRING. If you're OK with the default column name, you can remove the @DiscriminatorColumn entirely.
package javax.persistence;
@Target(TYPE) @Retention(RUNTIME)
public @interface DiscriminatorValue {
String value( )
}
The @javax.persistence.DiscriminatorValue
annotation defines what value the discriminator column will take for rows that store an instance of a Person class. You can leave this attribute undefined if you want to. In that case, the persistence manager would generate a value for you automatically. This value would be vendor-specific if the DiscriminatorType
is CHAR or INTEGER
. The entity name is used by default when a type of STRING is specified. It is good practice to specify the value for CHAR and INTEGER values.
The rest of the class hierarchy is quite easy. The only specific inheritance metadata you have to specify is the discriminator's value, if you want a value other than the default:
@Entity
@DiscriminatorValue
("CUST")
public class Customer extends Person {
private String street;
private String city;
private String state;
private String zip;
public String getStreet( ) { return street; }
public void setStreet(String street) { this.street = street; }
...
}
// use the default discriminator value
@Entity
public class Employee extends Customer {
private int employeeId;
public int getEmployeeId( ) { return employeeId; }
public void setEmployeeId(int id) { employeeId = id; }
}
So, in this example, the Customer entity sets the discriminator column value to be CUST, using the @DiscriminatorValue annotation. For the Employee entity, the discriminator column's value defaults to Employee, which is the entity name of the Employee bean class.
Now that you have a good understanding of this mapping type, let's look at how we would map this using XML rather than annotations:
<entity-mappings>
<entity class="com.titan.domain.Person">
<inheritance strategy="SINGLE_TABLE"/>
<discriminator-column name="DISCRIMINATOR"
discriminator-type="STRING"/>
<discriminator-value>PERSON</discriminator-value>
<attributes>
<id>
<generated-value/>
</id>
</attributes>
</entity>
<entity class="com.titan.domain.Customer">
<discriminator-value>CUST</discriminator-value>
</entity>
<entity class="com.titan.domain.Employee"/>
</entity-mappings>
Advantages
The SINGLE_TABLE
mapping strategy is the simplest to implement and performs better than all the inheritance strategies. There is only one table to administer and deal with. The persistence engine does not have to do any complex joins, unions, or subselects when loading the entity or when traversing a polymorphic relationship, because all data is stored in one table.
Disadvantages
One huge disadvantage of this approach is that all columns of subclass properties must be nullable. So, if you need or want to have any NOT NULL
constraints defined on these columns, you cannot do so. Also, because subclass property columns may be unused, the SINGLE_TABLE strategy is not normalized.
|