Monday, July 14, 2008

Mapping class inheritance in hibernate

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 between entities.

There are four different approaches to representing an inheritance hierarchy:
  • Table per concrete class with implicit polymorphism.Use no explicit inheritance mapping, and default runtime polymorphic behavior.
  • Table per concrete class.Discard polymorphism and inheritance relationships completely from the SQL schema.
  • Table per class hierarchy.Enable polymorphism by denormalizing the SQL schema, and utilize a type discriminator column that holds type information.
  • Table per subclass.Represent is a (inheritance) relationships as has a (foreign key) relationships.


Table per concrete class with implicit polymorphism

@MappedSuperclass
public abstract class BillingDetails {
@Column(name = "OWNER", nullable = false)
private String owner;
...
}

@Entity
@AttributeOverride(name = "owner", column =@Column(name = "CC_OWNER", nullable = false))
public class CreditCard extends BillingDetails {
@Id @GeneratedValue
@Column(name = "CREDIT_CARD_ID")
private Long id = null;

@Column(name = "NUMBER", nullable = false)
private String number;
...
}


Table per concrete class with unions
In this situation, we again have two tables and duplicate superclass columns in both: CREDIT_CARD and BANK_ACCOUNT. What’s new is a special Hibernate mapping that includes the superclass.
<hibernate-mapping>
<class name="BillingDetails" abstract="true">
<id name="id" column="BILLING_DETAILS_ID" type="long">
<generator class="native"/>
</id>
<property name="name" column="OWNER" type="string"/>
...
<union-subclass name="CreditCard" table="CREDIT_CARD">
<property name="number" column=”NUMBER”/>
<property name="expMonth" column="EXP_MONTH"/>
<property name="expYear" column="EXP_YEAR"/>
</union-subclass>
<union-subclass name="BankAccount" table="BANK_ACCOUNT">
...
</class>
</hibernate-mapping>

The first advantage you may notice with this strategy is the shared declaration of superclass (or interface) properties. No longer do you have to duplicate these
mappings for all concrete classes—Hibernate takes care of this.
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class BillingDetails {
@Id @GeneratedValue
@Column(name = "BILLING_DETAILS_ID")
private Long id = null;

@Column(name = "OWNER", nullable = false)
private String owner;
...
}

@Entity
@Table(name = "CREDIT_CARD")
public class CreditCard extends BillingDetails {
@Column(name = "NUMBER", nullable = false)
private String number;
...
}

In JPA annotations, this strategy is known as TABLE_PER_CLASS.


Table per class hierarchy
An entire class hierarchy can be mapped to a single table. This table includes columns for all properties of all classes in the hierarchy.

This mapping strategy is a winner in terms of both performance and simplicity. Ad-hoc reporting is possible without complex joins or unions.
There is one major problem: Columns for properties declared by subclasses must be declared to be nullable. If your subclasses each define several nonnullable
properties, the loss of NOT NULL constraints may be a serious problem from the point of view of data integrity.
Another important issue is normalization. We’ve created functional dependencies between nonkey columns, violating the third normal form. As always, denormalization for performance 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.
<hibernate-mapping>
<class name="BillingDetails" table="BILLING_DETAILS">
<id name="id" column="BILLING_DETAILS_ID" type="long">
<generator class="native" />
</id>
<discriminator column="BILLING_DETAILS_TYPE" type="string" />
<property name="owner" column="OWNER" type="string" />
...
<subclass name="CreditCard" discriminator-value="CC">
<property name="number" column="CC_NUMBER" />
<property name="expMonth" column="CC_EXP_MONTH" />
<property name="expYear" column="CC_EXP_YEAR" />
</subclass>
<subclass name=”BankAccount” discriminator-value=”BA”>
...
</subclass>
</class>
</hibernate-mapping>

You have to add a special column to distinguish between persistent classes: the discriminator. This isn’t a property of the persistent class; it’s used internally by
Hibernate. The column name is BILLING_DETAILS_TYPE, and the values are strings—in this case, “CC” or “BA”. Hibernate automatically sets and retrieves the discriminator values.
In JPA annotations, this strategy is known as SINGLE_TABLE.

Table per subclass
The fourth option is to represent inheritance relationships as relational foreign key associations. Every class/subclass that declares persistent properties—including abstract classes and even interfaces—has its own table.
If an instance of the CreditCard subclass is made persistent, the values of properties declared by the BillingDetails superclass are persisted to a new row of the BILLING_DETAILS table. Only the values of properties declared by the subclass are
persisted to a new row of the CREDIT_CARD table. The two rows are linked together by their shared primary key value.

<hibernate-mapping>
<class name="BillingDetails" table="BILLING_DETAILS">
<id name="id" column="BILLING_DETAILS_ID" type="long">
<generator class="native" />
</id>
<property name="owner" column="OWNER" type="string" />
...
<joined-subclass name="CreditCard" table="CREDIT_CARD">
<key column="CREDIT_CARD_ID" />
<property name="number" column="NUMBER" />
<property name="expMonth" column="EXP_MONTH" />
<property name="expYear" column="EXP_YEAR" />
</joined-subclass>
<joined-subclass name="BankAccount" table="BANK_ACCOUNT">
...
</joined-subclass>
</class>
</hibernate-mapping>

In JPA annotations, this strategy is known as JOINED.
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.

Mixing inheritance strategies


<hibernate-mapping>
<class name="BillingDetails" table="BILLING_DETAILS">
<id>...</id>
<discriminator column="BILLING_DETAILS_TYPE" type="string" />
<subclass name="CreditCard" discriminator-value="CC">
<join table="CREDIT_CARD">
<key column="CREDIT_CARD_ID" />
<property name="number" column="CC_NUMBER" />
<property name="expMonth" column="CC_EXP_MONTH" />
<property name="expYear" column="CC_EXP_YEAR" />
...
</join>
</subclass>
<subclass name="BankAccount" discriminator-value="BA">
<property name=account " column="BA_ACCOUNT" />
...
</subclass>
...
</class>
</hibernate-mapping>

No comments:

Post a Comment