Entity Inheritance (Enterprise JavaBeans 3.1)

 

In order to be complete, an object-to-relational mapping engine must support inheritance hierarchies. The Java Persistence specification supports entity inheritance, polymorphic relationships/associations, and polymorphic queries. These features were completely missing in the older EJB CMP 2.1 specification.

In this topic, we’ll modify the Employee entity that we defined in earlier topics to make it fit into an inheritance hierarchy. We’ll have it extend a base class called Person and redefine the Employee class to extend a Customer class. Figure 12-1 shows this class hierarchy.

Customer class hierarchy

Figure 12-1. Customer class hierarchy

The Java Persistence specification provides three different ways to map an inheritance hierarchy to a relational database:

Single table per class hierarchy

One table will have all properties of every class in the hierarchy.

A table per concrete class

Each class will have a table dedicated to it, with all of its properties and the properties of its superclass mapped to this table.

A table per subclass

Each class will have its own table. Each table will have only the properties that are defined in that particular class. These tables will not have properties of any superclass or subclass.

In this topic, we use these three strategies to map the Employee class hierarchy defined in Figure 12-1.

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:

tmp9745_thumb_thumb

As you can see, all the properties for the Customer class hierarchy are held in one table, SINGLECLASS_PERSON. 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 SINGLECLASS_PERSON. Let’s look at how the classes will use annotations to map this inheritance strategy:

tmp9746_thumb_thumb

This is our base class for both Customers and Employees. It defines the discriminator column we’ve just seen as “DISCRIMINATOR”, where Person rows will have a value of “PERSON”.

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 Employee 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 @Discrimina torColumn entirely:

tmp9747_thumb_thumb

The @javax.persistence.Inheritance annotation is used to define the persistence strategy for the inheritance relationship:

tmp9748_thumb_thumb

tmp9749_thumb_thumb

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 will 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:

tmp9750_thumb_thumb

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.

We can now extend this base into a more specialized Customer:

tmp9751_thumb_thumb

Customer rows in the table will use a value of “CUSTOMER” in the discriminator column. Finally, we have Employee:

tmp9752_thumb_thumb

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.


Table per Concrete Class

In the table per concrete class strategy, a database table is defined for each concrete class in the hierarchy. Each table has columns representing its properties and all properties of any superclasses:

tmp9753_thumb1_thumb1

One major difference between this strategy and the SINGLE_TABLE strategy is that no discriminator column is needed in the database schema. Also notice that each table contains every persistent property in the hierarchy. Let’s now look at how we map this strategy with annotations:

tmp9754_thumb_thumbtmp9755_thumb_thumb

Notice that the only inheritance metadata required is the InheritanceType, and this is needed on only the base Person class.

Advantages

The advantage to this approach over the SINGLE_TABLE strategy is that you can define constraints on subclass properties. Another plus is that it might be easier to map a preexisting legacy schema using this strategy.

Disadvantages

This strategy is not normalized, as it has redundant columns in each of its tables for each of the base class’s properties. Also, to support this type of mapping, the persistence manager has to do some funky things. One way it could be implemented is for the container to use multiple queries when loading an entity or polymorphic relationship. This is a huge performance hit because the container has to do multiple round-trips to the database. Another way a container could implement this strategy is to use SQL UNIONs. This still would not be as fast as the SINGLE_TABLE strategy, but it would perform much better than a multiselect implementation. The downside to an SQL UNION is that not all relational databases support this SQL feature. It is probably not wise to pick this strategy when developing your entity beans, unless you have good reason (e.g., an existing legacy schema).

Table per Subclass

In the table per subclass mapping, each subclass has its own table, but this table contains only the properties that are defined on that particular class. In other words, it is similar to the TABLE_PER_CLASS strategy, except the schema is normalized. This is also called the JOINED strategy:

tmp9756_thumb1_thumb2

When the persistence manager loads an entity that is a subclass or traverses a polymorphic relationship, it does an SQL join on all the tables in the hierarchy. In this mapping, there must be a column in each table that can be used to join each table. In our example, the JOINED_EMPLOYEE, JOINED_CUSTOMER, and JOINED_PERSON tables share the same primary-key values. The annotation mapping is quite simple:

tmp9757_thumb_thumbtmp9758_thumb_thumb

The persistence manager needs to know which columns in each table will be used to perform a join when loading an entity with a JOINED inheritance strategy. The @javax.persistence.PrimaryKeyJoinColumn annotation can be used to describe this metadata:

tmp9759_thumb_thumb

The name() attribute refers to the column contained in the current table on which you will perform a join. It defaults to the primary-key column of the superclass’s table. The referencedColumnName() is the column that will be used to perform the join from the superclass’s table. It can be any column in the superclass’s table, but it defaults to its primary key. If the primary-key column names are identical between the base and subclasses, then this annotation is not needed. For instance, the Customer entity does not need the @PrimaryKeyJoinColumn annotation. The Employee class has a different primary-key column name than the tables of its superclasses, so the @PrimaryKeyJoinColumn annotation is required. If class hierarchy uses a composite key, there is a @javax.persistence.PrimaryKeyJoinColumns annotation that can describe multiple join columns:

tmp9760_thumb_thumb

Some persistence providers require a discriminator column for this mapping type. Most do not. Make sure to check your persistence provider implementation to see whether it is required.

Advantages

It is better to compare this mapping to other strategies to describe its advantages. Although it is not as fast as the SINGLE_TABLE strategy, you are able to define NOT NULL constraints on any column of any table, and your model is normalized.

This mapping is better than the TABLE_PER_CLASS strategy for two reasons. One, the relational database model is completely normalized. Two, it performs better than the TABLE_PER_CLASS strategy if SQL UNIONs are not supported.

Disadvantages

It does not perform as well as the SINGLE_TABLE strategy.

Mixing Strategies

The persistence specification currently makes mixing inheritance strategies optional. The rules for mixing strategies in an inheritance hierarchy may be defined in future versions of the spec.

Nonentity Base Classes

The inheritance mappings we described so far in this topic concerned a class hierarchy of entity beans. Sometimes, however, you need to inherit from a nonentity superclass. This superclass may be an existing class in your domain model that you do not want to make an entity. The @javax.persistence.MappedSuperclass annotation allows you to define this kind of mapping. Let’s modify our example class hierarchy and change Person into a mapped superclass:

tmp9761_thumb_thumbtmp9762_thumb_thumb

Since it is not an entity, the mapped superclass does not have an associated table. Any subclass inherits the persistence properties of the base class. You can override any mapped property of the mapped class by using the @javax.persistence.AttributeOverride annotation.

You can have @MappedSuperclass annotated classes in between two @Entity annotated classes in a given hierarchy. Also, nonannotated classes (i.e., not annotated with @Entity or @MappedSuperclass) are completely ignored by the persistence provider.

Next post:

Previous post: