Chapter 6. Mapping inheritance

In this chapter

  • Inheritance-mapping strategies
  • Polymorphic associations

We deliberately haven’t talked much about inheritance mapping so far. Mapping a hierarchy of classes to tables can be a complex issue, and we present various strategies in this chapter.

A basic strategy for mapping classes to database tables might be “one table for every entity persistent class.” This approach sounds simple enough and indeed works well, until we encounter inheritance.

Inheritance is such a visible structural mismatch between the object-oriented and relational worlds because object-oriented systems model both is a and has a relationships. SQL-based models provide only has a relationships; SQL database management systems don’t support type inheritance—and even when it’s available, it’s usually proprietary or incomplete.

There are four different strategies for representing an inheritance hierarchy:

  • Use one table per concrete class and default runtime polymorphic behavior.
  • Use one table per concrete class but discard polymorphism and inheritance relationships completely from the SQL schema. Use SQL UNION queries for runtime polymorphic behavior.
  • Use one table per class hierarchy: enable polymorphism by denormalizing the SQL schema and relying on row-based discrimination to determine super/subtypes.
  • Use one table per subclass: represent is a (inheritance) relationships as has a (foreign key) relationships, and use SQL JOIN operations.

This chapter takes a top-down approach, assuming that you’re starting with a domain model and trying to derive a new SQL schema. The mapping strategies described are just as relevant if you’re working bottom-up, starting with existing database tables. We show some tricks along the way to help you deal with imperfect table layouts.

6.1. Table per concrete class with implicit polymorphism

Suppose we stick with the simplest approach suggested: exactly one table for each concrete class. You can map all properties of a class, including inherited properties, to columns of this table, as shown in figure 6.1.

Figure 6.1. Mapping all concrete classes to an independent table

If you’re relying on this implicit polymorphism, you map concrete classes with @Entity, as usual. By default, properties of the superclass are ignored and not persistent! You have to annotate the superclass with @MappedSuperclass to enable embedding of its properties in the concrete subclass tables; see the following listing.

Listing 6.1. Mapping BillingDetails (abstract superclass) with implicit polymorphism

Path: /model/src/main/java/org/jpwh/model/inheritance/mappedsuperclass/BillingDetails.java

@MappedSuperclass
public abstract class BillingDetails {
<enter/>
    @NotNull
    protected String owner;
<enter/>
    // ...
}
<enter/>

Now map the concrete subclasses.

Listing 6.2. Mapping CreditCard (concrete subclass)

Path: /model/src/main/java/org/jpwh/model/inheritance/mappedsuperclass/CreditCard.java

@Entity
@AttributeOverride(
        name = "owner",
        column = @Column(name = "CC_OWNER", nullable = false))
public class CreditCard extends BillingDetails {
<enter/>
    @Id
    @GeneratedValue(generator = Constants.ID_GENERATOR)
    protected Long id;
<enter/>
    @NotNull
    protected String cardNumber;
<enter/>
    @NotNull
    protected String expMonth;
<enter/>
    @NotNull
    protected String expYear;
<enter/>
    // ...
}

The mapping for the BankAccount class looks the same, so we won’t show it here.

You can override column mappings from the superclass in a subclass with the @AttributeOverride annotation or several with @AttributeOverrides. The previous example renamed the OWNER column to CC_OWNER in the CREDITCARD table.

You could declare the identifier property in the superclass, with a shared column name and generator strategy for all subclasses, so you wouldn’t have to repeat it. We haven’t done this in the examples to show you that it’s optional.

The main problem with implicit inheritance mapping is that it doesn’t support polymorphic associations very well. In the database, you usually represent associations as foreign key relationships. In the schema shown in figure 6.1, if the subclasses are all mapped to different tables, a polymorphic association to their superclass (abstract BillingDetails) can’t be represented as a simple foreign key relationship. You can’t have another entity mapped with a foreign key “referencing BILLINGDETAILS”—there is no such table. This would be problematic in the domain model, because BillingDetails is associated with User; both the CREDITCARD and BANKACCOUNT tables would need a foreign key reference to the USERS table. None of these issues can be easily resolved, so you should consider an alternative mapping strategy.

Polymorphic queries that return instances of all classes that match the interface of the queried class are also problematic. Hibernate must execute a query against the superclass as several SQL SELECTs, one for each concrete subclass. The JPA query select bd from BillingDetails bd requires two SQL statements:

select
    ID, OWNER, ACCOUNT, BANKNAME, SWIFT
from
    BANKACCOUNT
select
    ID, CC_OWNER, CARDNUMBER, EXPMONTH, EXPYEAR
from
    CREDITCARD

Hibernate uses a separate SQL query for each concrete subclass. On the other hand, queries against the concrete classes are trivial and perform well—Hibernate uses only one of the statements.

A further conceptual problem with this mapping strategy is that several different columns, of different tables, share exactly the same semantics. This makes schema evolution more complex. For example, renaming or changing the type of a superclass property results in changes to multiple columns in multiple tables. Many of the standard refactoring operations offered by your IDE would require manual adjustments, because the automatic procedures usually don’t account for things like @Attribute-Overrides. It also makes it much more difficult to implement database integrity constraints that apply to all subclasses.

We recommend this approach (only) for the top level of your class hierarchy, where polymorphism isn’t usually required, and when modification of the superclass in the future is unlikely. It isn’t a good fit for the CaveatEmptor domain model, where queries and other entities refer to BillingDetails.

With the help of the SQL UNION operation, you can eliminate most of the issues with polymorphic queries and associations.

6.2. Table per concrete class with unions

First, let’s consider a union subclass mapping with BillingDetails as an abstract class (or interface), as in the previous section. In this situation, you again have two tables and duplicate superclass columns in both: CREDITCARD and BANKACCOUNT. What’s new is an inheritance strategy known as TABLE_PER_CLASS, declared on the superclass, as shown next.

Listing 6.3. Mapping BillingDetails with TABLE_PER_CLASS

Path: /model/src/main/java/org/jpwh/model/inheritance/tableperclass/BillingDetails.java

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class BillingDetails {
<enter/>
    @Id
    @GeneratedValue(generator = Constants.ID_GENERATOR)
    protected Long id;
<enter/>
    @NotNull

    protected String owner;
<enter/>
    // ...
}

The database identifier and its mapping have to be present in the superclass, to share it in all subclasses and their tables. This is no longer optional, as it was for the previous mapping strategy. The CREDITCARD and BANKACCOUNT tables both have an ID primary key column. All concrete class mappings inherit persistent properties from the superclass (or interface). An @Entity annotation on each subclass is all that is required.

Listing 6.4. Mapping CreditCard

Path: /model/src/main/java/org/jpwh/model/inheritance/tableperclass/CreditCard.java

@Entity
public class CreditCard extends BillingDetails {
<enter/>
    @NotNull
    protected String cardNumber;
<enter/>
    @NotNull
    protected String expMonth;
<enter/>
    @NotNull
    protected String expYear;
<enter/>
    // ...
}

Keep in mind that the SQL schema still isn’t aware of the inheritance; the tables look exactly alike, as shown in figure 6.1.

Note that the JPA standard specifies that TABLE_PER_CLASS is optional, so not all JPA implementations may support it. The implementation is also vendor dependent—in Hibernate, it’s equivalent to a <union-subclass> mapping in the old native Hibernate XML metadata (don’t worry about this if you’ve never used native Hibernate XML files).

If BillingDetails were concrete, you’d need an additional table to hold instances. We have to emphasize again that there is still no relationship between the database tables, except for the fact that they have some (many) similar columns.

The advantages of this mapping strategy are clearer if we examine polymorphic queries. For example, the query select bd from BillingDetails bd generates the following SQL statement:

select
    ID, OWNER, EXPMONTH, EXPYEAR, CARDNUMBER,
    ACCOUNT, BANKNAME, SWIFT, CLAZZ_
 from
    ( select
          ID, OWNER, EXPMONTH, EXPYEAR, CARDNUMBER,
          null as ACCOUNT,
          null as BANKNAME,

          null as SWIFT,
          1 as CLAZZ_
      from
          CREDITCARD
      union all
      select
          id, OWNER,
          null as EXPMONTH,
          null as EXPYEAR,
          null as CARDNUMBER,
          ACCOUNT, BANKNAME, SWIFT,
          2 as CLAZZ_
      from
          BANKACCOUNT
    ) as BILLINGDETAILS

This SELECT uses a FROM-clause subquery to retrieve all instances of BillingDetails from all concrete class tables. The tables are combined with a UNION operator, and a literal (in this case, 1 and 2) is inserted into the intermediate result; Hibernate reads this to instantiate the correct class given the data from a particular row. A union requires that the queries that are combined project over the same columns; hence, you have to pad and fill nonexistent columns with NULL. You may ask whether this query will really perform better than two separate statements. Here you can let the database optimizer find the best execution plan to combine rows from several tables, instead of merging two result sets in memory as Hibernate’s polymorphic loader engine would do.

Another much more important advantage is the ability to handle polymorphic associations; for example, an association mapping from User to BillingDetails would now be possible. Hibernate can use a UNION query to simulate a single table as the target of the association mapping. We cover this topic in detail later in this chapter.

So far, the inheritance-mapping strategies we’ve discussed don’t require extra consideration with regard to the SQL schema. This situation changes with the next strategy.

6.3. Table per class hierarchy

You can map an entire class hierarchy to a single table. This table includes columns for all properties of all classes in the hierarchy. The value of an extra type discriminator column or formula identifies the concrete subclass represented by a particular row. Figure 6.2 shows this approach.

Figure 6.2. Mapping an entire class hierarchy to a single table

This mapping strategy is a winner in terms of both performance and simplicity. It’s the best-performing way to represent polymorphism—both polymorphic and non-polymorphic queries perform well—and it’s even easy to write queries by hand. Ad hoc reporting is possible without complex joins or unions. Schema evolution is straightforward.

There is one major problem: data integrity. You must declare columns for properties declared by subclasses to be nullable. If your subclasses each define several non-nullable properties, the loss of NOT NULL constraints may be a serious problem from the point of view of data correctness. Imagine that an expiration date for credit cards is required, but your database schema can’t enforce this rule because all columns of the table can be NULL. A simple application programming error can lead to invalid data.

Another important issue is normalization. You’ve created functional dependencies between non-key columns, violating the third normal form. As always, denormalization for performance reasons can be misleading, because it sacrifices long-term stability, maintainability, and the integrity of data for immediate gains that may be also achieved by proper optimization of the SQL execution plans (in other words, ask your DBA).

Use the SINGLE_TABLE inheritance strategy to create a table-per-class hierarchy mapping, as shown in the following listing.

Listing 6.5. Mapping BillingDetails with SINGLE_TABLE

Path: /model/src/main/java/org/jpwh/model/inheritance/singletable/BillingDetails.java

The root class BillingDetails of the inheritance hierarchy is mapped to the table BILLINGDETAILS automatically. Shared properties of the superclass can be NOT NULL in the schema; every subclass instance must have a value. An implementation quirk of Hibernate requires that you declare nullability with @Column because Hibernate ignores Bean Validation’s @NotNull when it generates the database schema.

You have to add a special discriminator column to distinguish what each row represents. This isn’t a property of the entity; it’s used internally by Hibernate. The column name is BD_TYPE, and the values are strings—in this case, "CC" or "BA". Hibernate automatically sets and retrieves the discriminator values.

If you don’t specify a discriminator column in the superclass, its name defaults to DTYPE and the value are strings. All concrete classes in the inheritance hierarchy can have a discriminator value, such as CreditCard.

Listing 6.6. Mapping CreditCard

Path: /model/src/main/java/org/jpwh/model/inheritance/singletable/CreditCard.java

Hibernate Feature

Without an explicit discriminator value, Hibernate defaults to the fully qualified class name if you use Hibernate XML files and the simple entity name if you use annotations or JPA XML files. Note that JPA doesn’t specify a default for non-string discriminator types; each persistence provider can have different defaults. Therefore, you should always specify discriminator values for your concrete classes.

Annotate every subclass with @Entity, and then map properties of a subclass to columns in the BILLINGDETAILS table. Remember that NOT NULL constraints aren’t allowed in the schema, because a BankAccount instance won’t have an expMonth property, and the EXPMONTH column must be NULL for that row. Hibernate ignores the @NotNull for schema DDL generation, but it observes it at runtime, before inserting a row. This helps you avoid programming errors; you don’t want to accidentally save credit card data without its expiration date. (Other, less well-behaved applications can of course still store incorrect data in this database.)

Hibernate generates the following SQL for select bd from BillingDetails bd:

select
    ID, OWNER, EXPMONTH, EXPYEAR, CARDNUMBER,
    ACCOUNT, BANKNAME, SWIFT, BD_TYPE
from
    BILLINGDETAILS

To query the CreditCard subclass, Hibernate adds a restriction on the discriminator column:

select
    ID, OWNER, EXPMONTH, EXPYEAR, CARDNUMBER
from
    BILLINGDETAILS
where
    BD_TYPE='CC'

Hibernate Feature

Sometimes, especially in legacy schemas, you don’t have the freedom to include an extra discriminator column in your entity tables. In this case, you can apply an expression to calculate a discriminator value for each row. Formulas for discrimination aren’t part of the JPA specification, but Hibernate has an extension annotation, @DiscriminatorFormula.

Listing 6.7. Mapping BillingDetails with a @DiscriminatorFormula

Path: /model/src/main/java/org/jpwh/model/inheritance/singletableformula/BillingDetails.java

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@org.hibernate.annotations.DiscriminatorFormula(
        "case when CARDNUMBER is not null then 'CC' else 'BA' end"
)
public abstract class BillingDetails {
    // ...
}

There is no discriminator column in the schema, so this mapping relies on an SQL CASE/WHEN expression to determine whether a particular row represents a credit card or a bank account (many developers have never used this kind of SQL expression; check the ANSI standard if you aren’t familiar with it). The result of the expression is a literal, CC or BA, which you declare on the subclass mappings.

The disadvantages of the table-per-class hierarchy strategy may be too serious for your design—considering denormalized schemas can become a major burden in the long term. Your DBA may not like it at all. The next inheritance-mapping strategy doesn’t expose you to this problem.

6.4. Table per subclass with joins

The fourth option is to represent inheritance relationships as SQL foreign key associations. Every class/subclass that declares persistent properties—including abstract classes and even interfaces—has its own table.

Unlike the table-per-concrete-class strategy we mapped first, the table of a concrete @Entity here contains columns only for each non-inherited property, declared by the subclass itself, along with a primary key that is also a foreign key of the superclass table. This is easier than it sounds; have a look at figure 6.3.

Figure 6.3. Mapping all classes of the hierarchy to their own table

If you make an instance of the CreditCard subclass persistent, Hibernate inserts two rows. The values of properties declared by the BillingDetails superclass are stored in a new row of the BILLINGDETAILS table. Only the values of properties declared by the subclass are stored in a new row of the CREDITCARD table. The primary key shared by the two rows links them together. Later, the subclass instance may be retrieved from the database by joining the subclass table with the superclass table.

The primary advantage of this strategy is that it normalizes the SQL schema. Schema evolution and integrity-constraint definition are straightforward. A foreign key referencing the table of a particular subclass may represent a polymorphic association to that particular subclass. Use the JOINED inheritance strategy to create a table-per-subclass hierarchy mapping.

Listing 6.8. Mapping BillingDetails with JOINED

Path: /model/src/main/java/org/jpwh/model/inheritance/joined/BillingDetails.java

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class BillingDetails {
<enter/>
    @Id
    @GeneratedValue(generator = Constants.ID_GENERATOR)
    protected Long id;
<enter/>
    @NotNull
    protected String owner;
<enter/>
    // ...
}

The root class BillingDetails is mapped to the table BILLINGDETAILS. Note that no discriminator is required with this strategy.

In subclasses, you don’t need to specify the join column if the primary key column of the subclass table has (or is supposed to have) the same name as the primary key column of the superclass table.

Listing 6.9. Mapping BankAccount (concrete class)

Path: /model/src/main/java/org/jpwh/model/inheritance/joined/BankAccount.java

@Entity
public class BankAccount extends BillingDetails {
<enter/>
    @NotNull
    protected String account;
<enter/>
    @NotNull
    protected String bankname;
<enter/>
    @NotNull
    protected String swift;
<enter/>
    // ...
}

This entity has no identifier property; it automatically inherits the ID property and column from the superclass, and Hibernate knows how to join the tables if you want to retrieve instances of BankAccount. Of course, you can specify the column name explicitly.

Listing 6.10. Mapping CreditCard

Path: /model/src/main/java/org/jpwh/model/inheritance/joined/CreditCard.java

@Entity
@PrimaryKeyJoinColumn(name = "CREDITCARD_ID")
public class CreditCard extends BillingDetails {
<enter/>
    @NotNull
    protected String cardNumber;
<enter/>
    @NotNull
    protected String expMonth;
<enter/>
    @NotNull
    protected String expYear;
<enter/>
    // ...
}

The primary key columns of the BANKACCOUNT and CREDITCARD tables each also have a foreign key constraint referencing the primary key of the BILLINGDETAILS table.

Hibernate relies on an SQL outer join for select bd from BillingDetails bd:

select
    BD.ID, BD.OWNER,
    CC.EXPMONTH, CC.EXPYEAR, CC.CARDNUMBER,
    BA.ACCOUNT, BA.BANKNAME, BA.SWIFT,
    case
        when CC.CREDITCARD_ID is not null then 1
        when BA.ID is not null then 2

        when BD.ID is not null then 0
    end
from
    BILLINGDETAILS BD
    left outer join CREDITCARD CC on BD.ID=CC.CREDITCARD_ID
    left outer join BANKACCOUNT BA on BD.ID=BA.ID

The SQL CASE ... WHEN clause detects the existence (or absence) of rows in the subclass tables CREDITCARD and BANKACCOUNT, so Hibernate can determine the concrete subclass for a particular row of the BILLINGDETAILS table.

For a narrow subclass query like select cc from CreditCard cc, Hibernate uses an inner join:

select
    CREDITCARD_ID, OWNER, EXPMONTH, EXPYEAR, CARDNUMBER
from
    CREDITCARD
    inner join BILLINGDETAILS on CREDITCARD_ID=ID

As you can see, this mapping strategy is more difficult to implement by hand—even ad hoc reporting is more complex. This is an important consideration if you plan to mix Hibernate code with handwritten SQL.

Furthermore, even though this mapping strategy is deceptively simple, our experience is that performance can be unacceptable for complex class hierarchies. Queries always require a join across many tables, or many sequential reads.

Inheritance with joins and discriminator

Hibernate doesn’t need a special discriminator database column to implement the InheritanceType.JOINED strategy, and the JPA specification doesn’t contain any requirements either. The CASE ... WHEN clause in the SQL SELECT statement is a smart way to distinguish the entity type of each retrieved row. Some JPA examples you might find elsewhere, however, use InheritanceType.JOINED and an @DiscriminatorColumn mapping. Apparently some other JPA providers don’t use CASE ... WHEN clauses and rely only on a discriminator value, even for the InheritanceType.JOINED strategy. Hibernate doesn’t need the discriminator but uses a declared @DiscriminatorColumn, even with a JOINED mapping strategy. If you prefer to ignore the discriminator mapping with JOINED (it was ignored in older Hibernate versions), enable the configuration property hibernate.discriminator.ignore_explicit_for_joined.

Before we show you when to choose which strategy, let’s consider mixing inheritance-mapping strategies in a single class hierarchy.

6.5. Mixing inheritance strategies

You can map an entire inheritance hierarchy with the TABLE_PER_CLASS, SINGLE_TABLE, or JOINED strategy. You can’t mix them—for example, to switch from a table-per-class hierarchy with a discriminator to a normalized table-per-subclass strategy. Once you’ve made a decision for an inheritance strategy, you have to stick with it.

This isn’t completely true, however. By using some tricks, you can switch the mapping strategy for a particular subclass. For example, you can map a class hierarchy to a single table, but, for a particular subclass, switch to a separate table with a foreign key–mapping strategy, just as with table-per-subclass. Look at the schema in figure 6.4.

Figure 6.4. Breaking out a subclass to its own secondary table

Map the superclass BillingDetails with InheritanceType.SINGLE_TABLE, as you did before. Now map the subclass you want to break out of the single table to a secondary table.

Listing 6.11. Mapping CreditCard

Path: /model/src/main/java/org/jpwh/model/inheritance/mixed/CreditCard.java

The @SecondaryTable and @Column annotations group some properties and tell Hibernate to get them from a secondary table. You map all properties that you moved into the secondary table with the name of that secondary table. This is done with the table parameter of @Column, which we haven’t shown before. This mapping has many uses, and you’ll see it again later in this book. In this example, it separates the CreditCard properties from the single table strategy into the CREDITCARD table.

The CREDITCARD_ID column of this table is at the same time the primary key, and it has a foreign key constraint referencing the ID of the single hierarchy table. If you don’t specify a primary key join column for the secondary table, the name of the primary key of the single inheritance table is used—in this case, ID.

Remember that InheritanceType.SINGLE_TABLE enforces all columns of subclasses to be nullable. One of the benefits of this mapping is that you can now declare columns of the CREDITCARD table as NOT NULL, guaranteeing data integrity.

At runtime, Hibernate executes an outer join to fetch BillingDetails and all subclass instances polymorphically:

select
    ID, OWNER, ACCOUNT, BANKNAME, SWIFT,
    EXPMONTH, EXPYEAR, CARDNUMBER,
    BD_TYPE
from
    BILLINGDETAILS
    left outer join CREDITCARD on ID=CREDITCARD_ID

You can also use this trick for other subclasses in your class hierarchy. If you have an exceptionally wide class hierarchy, the outer join can become a problem. Some database systems (Oracle, for example) limit the number of tables in an outer join operation. For a wide hierarchy, you may want to switch to a different fetching strategy that executes an immediate second SQL select instead of an outer join.

Hibernate Feature

Switching the fetching strategy for this mapping isn’t available in JPA or Hibernate annotations at the time of writing, so you have to map the class in a native Hibernate XML file:

Path: /model/src/main/resources/inheritance/mixed/FetchSelect.hbm.xml

<subclass name="CreditCard"
          discriminator-value="CC">
    <join table="CREDITCARD" fetch="select">
        ...

    </join>
</subclass>

So far, we’ve only been talking about entity inheritance. Although the JPA specification is silent about inheritance and polymorphism of @Embeddable classes, Hibernate offers a mapping strategy for component types.

6.6. Inheritance of embeddable classes

Hibernate Feature

An embeddable class is a component of its owning entity; hence, the normal entity inheritance rules presented in this chapter don’t apply. As a Hibernate extension, you can map an embeddable class that inherits some persistent properties from a superclass (or interface). Consider these two new attributes of an auction item: dimensions and weight.

An item’s dimensions are its width, height, and depth, expressed in a given unit and its symbol: for example, inches (“) or centimeters (cm). An item’s weight also carries a unit of measurement: for example, pounds (lbs) or kilograms (kg). To capture the common attributes (name and symbol) of measurement, you define a superclass for Dimension and Weight called Measurement.

Listing 6.12. Mapping the Measurement abstract embeddable superclass

Path: /model/src/main/java/org/jpwh/model/inheritance/embeddable/Measurement.java

@MappedSuperclass
public abstract class Measurement {
<enter/>
    @NotNull
    protected String name;
<enter/>
    @NotNull
    protected String symbol;
<enter/>
    // ...
}

Use the @MappedSuperclass annotation on the superclass of the embeddable class you’re mapping just like you would for an entity. Subclasses will inherit the properties of this class as persistent properties.

You define the Dimensions and Weight subclasses as @Embeddable. For Dimensions, override all the superclass attributes and add a column-name prefix.

Listing 6.13. Mapping the Dimensions class

Path: /model/src/main/java/org/jpwh/model/inheritance/embeddable/Dimensions.java

@Embeddable
@AttributeOverrides({
        @AttributeOverride(name = "name",
                column = @Column(name = "DIMENSIONS_NAME")),

        @AttributeOverride(name = "symbol",
                column = @Column(name = "DIMENSIONS_SYMBOL"))
})
public class Dimensions extends Measurement {
<enter/>
    @NotNull
    protected BigDecimal depth;
<enter/>
    @NotNull
    protected BigDecimal height;
<enter/>
    @NotNull
    protected BigDecimal width;
<enter/>
    // ...
}

Without this override, an Item embedding both Dimension and Weight would map to a table with conflicting column names. Following is the Weight class; its mapping also overrides the column names with a prefix (for uniformity, we avoid the conflict with the previous override).

Listing 6.14. Mapping the Weight class

Path: /model/src/main/java/org/jpwh/model/inheritance/embeddable/Weight.java

@Embeddable
@AttributeOverrides({
        @AttributeOverride(name = "name",
                column = @Column(name = "WEIGHT_NAME")),
        @AttributeOverride(name = "symbol",
                column = @Column(name = "WEIGHT_SYMBOL"))
})
public class Weight extends Measurement {
<enter/>
    @NotNull
    @Column(name = "WEIGHT")
    protected BigDecimal value;
<enter/>
    // ...
}

The owning entity Item defines two regular persistent embedded properties.

Listing 6.15. Mapping the Item class

Path: /model/src/main/java/org/jpwh/model/inheritance/embeddable/Item.java

@Entity
public class Item {
<enter/>
    protected Dimensions dimensions;
<enter/>
    protected Weight weight;
<enter/>
    // ...
}

Figure 6.5 illustrates this mapping. Alternatively, you could override the conflicting Measurement column names of the embedded properties in the Item class, as shown in section 5.2. Instead, we prefer to override them once, in the @Embeddable classes, so any consumers of these classes don’t have to resolve the conflict.

Figure 6.5. Mapping concrete embeddable classes with their inherited properties

A pitfall to watch out for is embedding a property of abstract superclass type (like Measurement) in an entity (like Item). This can never work; the JPA provider doesn’t know how to store and load Measurement instances polymorphically. It doesn’t have the information necessary to decide whether the values in the database are Dimension or Weight instances, because there is no discriminator. This means although you can have an @Embeddable class inherit some persistent properties from a @MappedSuperclass, the reference to an instance isn’t polymorphic—it always names a concrete class.

Compare this with the alternative inheritance strategy for embeddable classes shown in the section “Converting properties of components,” in chapter 5, which supported polymorphism but required some custom type-discrimination code.

Next, we give you more tips about how to choose an appropriate combination of mapping strategies for your application’s class hierarchies.

6.7. Choosing a strategy

Picking the right inheritance-mapping strategy depends on usage of the superclasses of your entity hierarchy. You have to consider how frequently you query for instances of the superclasses and whether you have associations targeting the superclasses. Another important aspect is the attributes of super- and subtypes: whether subtypes have many additional attributes or only different behavior than their supertypes. Here are some rules of thumb:

  • If you don’t require polymorphic associations or queries, lean toward table-per-concrete class—in other words, if you never or rarely select bd from BillingDetails bd and you have no class that has an association to BillingDetails. An explicit UNION-based mapping with InheritanceType.TABLE_PER_CLASS should be preferred, because (optimized) polymorphic queries and associations will then be possible later.
  • If you do require polymorphic associations (an association to a superclass, hence to all classes in the hierarchy with dynamic resolution of the concrete class at runtime) or queries, and subclasses declare relatively few properties (particularly if the main difference between subclasses is in their behavior), lean toward InheritanceType.SINGLE_TABLE. Your goal is to minimize the number of nullable columns and to convince yourself (and your DBA) that a denormalized schema won’t create problems in the long run.
  • If you do require polymorphic associations or queries, and subclasses declare many (non-optional) properties (subclasses differ mainly by the data they hold), lean toward InheritanceType.JOINED. Alternatively, depending on the width and depth of your inheritance hierarchy and the possible cost of joins versus unions, use InheritanceType.TABLE_PER_CLASS. This decision might require evaluation of SQL execution plans with real data.

By default, choose InheritanceType.SINGLE_TABLE only for simple problems. Otherwise, for complex cases, or when a data modeler insisting on the importance of NOT NULL constraints and normalization overrules you, you should consider the InheritanceType.JOINED strategy. At that point, ask yourself whether it may not be better to remodel inheritance as delegation in the class model. Complex inheritance is often best avoided for all sorts of reasons unrelated to persistence or ORM. Hibernate acts as a buffer between the domain and relational models, but that doesn’t mean you can ignore persistence concerns completely when designing your classes.

When you start thinking about mixing inheritance strategies, remember that implicit polymorphism in Hibernate is smart enough to handle exotic cases. Also, consider that you can’t put inheritance annotations on interfaces; this isn’t standardized in JPA.

For example, consider an additional interface in the example application: ElectronicPaymentOption. This is a business interface that doesn’t have a persistence aspect—except that in the application, a persistent class such as CreditCard will likely implement this interface. No matter how you map the BillingDetails hierarchy, Hibernate can answer the query select o from ElectronicPaymentOption o correctly. This even works if other classes, which aren’t part of the BillingDetails hierarchy, are mapped as persistent and implement this interface. Hibernate always knows what tables to query, which instances to construct, and how to return a polymorphic result.

You can apply all mapping strategies to abstract classes. Hibernate won’t try to instantiate an abstract class, even if you query or load it.

We mentioned the relationship between User and BillingDetails several times and how it influences the selection of an inheritance-mapping strategy. In the following and last section of this chapter, we explore this more advanced topic in detail: polymorphic associations. If you don’t have such a relationship in your model right now, you may want to read this material later, when you encounter the issue in your application.

6.8. Polymorphic associations

Polymorphism is a defining feature of object-oriented languages like Java. Support for polymorphic associations and polymorphic queries is a fundamental feature of an ORM solution like Hibernate. Surprisingly, we’ve managed to get this far without needing to talk much about polymorphism. Refreshingly, there isn’t much to say on the topic—polymorphism is so easy to use in Hibernate that we don’t need to expend a lot of effort explaining it.

To provide an overview, we first consider a many-to-one association to a class that may have subclasses, and then a one-to-many relationship. For both examples, the classes of the domain model are the same; see figure 6.6.

Figure 6.6. A user has either a credit card or a bank account as the default billing details.

6.8.1. Polymorphic many-to-one associations

First, consider the defaultBilling property of User. It references one particular BillingDetails instance, which at runtime can be any concrete instance of that class.

You map this unidirectional association to the abstract class BillingDetails as follows:

Path: /model/src/main/java/org/jpwh/model/inheritance/associations/manytoone/User.java

@Entity
@Table(name = "USERS")
public class User {
<enter/>
    @ManyToOne(fetch = FetchType.LAZY)
    protected BillingDetails defaultBilling;
<enter/>
    // ...
}

The USERS table now has the join/foreign key column DEFAULTBILLING_ID representing this relationship. It’s a nullable column because a User might not have a default billing method assigned. Because BillingDetails is abstract, the association must refer to an instance of one of its subclasses—CreditCard or BankAccount—at runtime.

You don’t have to do anything special to enable polymorphic associations in Hibernate; if the target class of an association is mapped with @Entity and @Inheritance, the association is naturally polymorphic.

The following code demonstrates the creation of an association to an instance of the CreditCard subclass:

Path: /examples/src/test/java/org/jpwh/test/inheritance/PolymorphicManyToOne.java

CreditCard cc = new CreditCard(
    "John Doe", "1234123412341234", "06", "2015"
);
User johndoe = new User("johndoe");
johndoe.setDefaultBilling(cc);
<enter/>
em.persist(cc);
em.persist(johndoe);

Now, when you navigate the association in a second unit of work, Hibernate automatically retrieves the CreditCard instance:

Path: /examples/src/test/java/org/jpwh/test/inheritance/PolymorphicManyToOne.java

There’s just one thing to watch out for: because the defaultBilling property is mapped with FetchType.LAZY, Hibernate will proxy the association target. In this case, you wouldn’t be able to perform a typecast to the concrete class CreditCard at runtime, and even the instanceof operator would behave strangely:

Path: /examples/src/test/java/org/jpwh/test/inheritance/PolymorphicManyToOne.java

The bd reference isn’t a CreditCard instance in this case; it’s a runtime-generated special subclass of BillingDetails, a Hibernate proxy. When you invoke a method on the proxy, Hibernate delegates the call to an instance of CreditCard that it fetches lazily. Until this initialization occurs, Hibernate doesn’t know what the subtype of the given instance is—this would require a database hit, which you try to avoid with lazy loading in the first place. To perform a proxy-safe typecast, use em.getReference():

Path: /examples/src/test/java/org/jpwh/test/inheritance/PolymorphicManyToOne.java

After the getReference() call, bd and creditCard refer to two different proxy instances, both of which delegate to the same underlying CreditCard instance. The second proxy has a different interface, though, and you can call methods like creditCard.getExpMonth() that apply only to this interface. (Note that bd.getId() will trigger a SELECT if you map the id property with field access.)

You can avoid these issues by avoiding lazy fetching, as in the following code, using an eager fetch query:

Path: /examples/src/test/java/org/jpwh/test/inheritance/PolymorphicManyToOne.java

Truly object-oriented code shouldn’t use instanceof or numerous typecasts. If you find yourself running into problems with proxies, you should question your design, asking whether there is a more polymorphic approach. Hibernate also offers bytecode instrumentation as an alternative to lazy loading through proxies; we’ll get back to fetching strategies in chapter 12.

You can handle one-to-one associations the same way. What about plural associations, like the collection of billingDetails for each User? Let’s look at that next.

6.8.2. Polymorphic collections

A User may have references to many BillingDetails, not only a single default (one of the many is the default; let’s ignore that for now). You can map this with a bidirectional one-to-many association:

Path: /model/src/main/java/org/jpwh/model/inheritance/associations/onetomany/User.java

@Entity
@Table(name = "USERS")
public class User {
<enter/>
    @OneToMany(mappedBy = "user")
    protected Set<BillingDetails> billingDetails = new HashSet<>();
<enter/>
    // ...
}

Next, here’s the owning side of the relationship (declared with mappedBy in the previous mapping):

Path: /model/src/main/java/org/jpwh/model/inheritance/associations/onetomany/BillingDetails.java

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class BillingDetails {
<enter/>
    @ManyToOne(fetch = FetchType.LAZY)
    protected User user;
<enter/>
    // ...
}

So far, there is nothing special about this association mapping. The BillingDetails class hierarchy can be mapped with TABLE_PER_CLASS, SINGLE_TABLE, or a JOINED inheritance type. Hibernate is smart enough to use the right SQL queries, with either JOIN or UNION operators, when loading the collection elements.

There is one limitation, however: the BillingDetails class can’t be a @MappedSuperclass, as shown in section 6.1. It has to be mapped with @Entity and @Inheritance.

Associations with implicit polymorphism

Hibernate offers a “last resort” technique, if you really have to map an association to a class hierarchy without mapping the class hierarchy explicitly with @Inheritance. This is possible with Hibernate native XML mappings and the <any/> element. We recommend that you look this one up in the Hibernate documentation or a previous edition of this book if needed, but try to avoid it whenever possible because it results in ugly schemas.

6.9. Summary

  • Table per concrete class with implicit polymorphism is the simplest strategy to map inheritance hierarchies of entities, but it doesn’t support polymorphic associations very well. In addition, different columns from different tables share exactly the same semantics, making schema evolution more complex. We recommend this approach for the top level of your class hierarchy only, where polymorphism isn’t usually required and when modification of the superclass in the future is unlikely.
  • The table-per-concrete-class-with-unions strategy is optional, and JPA implementations may not support it, but it does handles polymorphic associations.
  • The table-per-class-hierarchy strategy is a winner in terms of both performance and simplicity; ad hoc reporting is possible without complex joins or unions, and schema evolution is straightforward. The one major problem is data integrity, because you must declare some columns as nullable. Another issue is normalization: this strategy creates functional dependencies between non-key columns, violating the third normal form.
  • The table-per-subclass-with-joins strategy’s primary advantage is that it normalizes the SQL schema, making schema evolution and integrity constraint definition straightforward. The disadvantages are that it’s more difficult to implement by hand, and performance can be unacceptable for complex class hierarchies.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.17.79.206