Search code examples
hibernateinheritancejpaannotationshierarchy

How to mix inheritance strategies with JPA annotations and Hibernate?


According to the Hibernate Reference Documentation it should be possible to mix different inheritance mapping strategies when using Hibernate's XML-Metadata:
http://docs.jboss.org/hibernate/stable/core/reference/en/html/inheritance.html#inheritance-mixing-tableperclass-tablepersubclass

However, the corresponding section of the Hibernate Annotations Reference Guide does not cover that:
http://docs.jboss.org/hibernate/stable/annotations/reference/en/html/entity.html#d0e1168

On the other hand, the JavaDocs suggest that mixing inheritance strategies should be possible. For instance in javax.persistence.DiscriminatorColumn it says:

The strategy and the discriminator column are only specified in the root of an entity class hierarchy or subhierarchy in which a different inheritance strategy is applied.


The following is an example for the mapping I'm trying to achieve. I'd like to use table-per-subclass mapping near the root of the hierarchy, but change to table-per-class-hierarchy mapping near the leaves. Here's some example code:

@Entity
@Inheritance( strategy = InheritanceType.JOINED )
public abstract class A implements Serializable
{
    @Id
    private String id;

    // other mapped properties...
}

@Entity
@Inheritance( strategy = InheritanceType.SINGLE_TABLE )
public class BB extends A
{
    // other mapped properties and associations...
}

@Entity
public class BB1 extends BB
{
    // other stuff, not necessarily mapped...
}

@Entity
public class BB2 extends BB
{
    // other stuff, not necessarily mapped...
}

@Entity
@Inheritance( strategy = InheritanceType.SINGLE_TABLE )
public class CC extends A
{
    // other mapped properties and associations...
}

@Entity
public class CC1 extends CC
{
    // other stuff, not necessarily mapped...
}

...

What I expect from this mapping is having exactly 3 tables: A, BB, and CC. Both BB and CC should have a default discriminator column called DTYPE. They should also provide all columns necessary for all mapped properties and associations of their respective subclasses.

Instead , the class hierarchy seems to use the table-per-subclass inheritance strategy throughout. I.e. I get an own table for each of the entities mentioned above. I'd like to avoid this, since the leaves of the class-hierarchy are extremely light-weight and it just seems overkill to have a separate table for each of them!


Did I overlook something? Any advice is highly appreciated! I'll be glad to provide additional info...


Solution

  • According to the Hibernate Reference Documentation it should be possible to mix different inheritance mapping strategies when using Hibernate's XML-Metadata (...)

    Actually, it's not really supported, they are "cheating" using a secondary table to switch from the single table strategy in the example of the documentation. Quoting Java Persistence with Hibernate:

    You can map whole inheritance hierarchies by nesting <union-subclass>, <sub- class>, and <joined-subclass> mapping elements. 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 to it.

    This isn’t completely true, however. With some Hibernate 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. This is possible with the <join> mapping element:

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

    And you could achieve the same with annotations:

    Java Persistence also supports this mixed inheritance mapping strategy with annotations. 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.

    @Entity
    @DiscriminatorValue("CC")
    @SecondaryTable(
        name = "CREDIT_CARD",
        pkJoinColumns = @PrimaryKeyJoinColumn(name = "CREDIT_CARD_ID")
    )
    public class CreditCard extends BillingDetails {
        @Column(table = "CREDIT_CARD",
            name = "CC_NUMBER",
            nullable = false)
        private String number;
        ...
    }
    

    I didn't test this but you could maybe try to:

    • map A using a SINGLE_TABLE strategy
    • map BB, CC, etc using the @SecondaryTable annotation.

    I've not tested this, I don't know if it will work well for BB1, BB2.

    Reference

    • Java Persistence with Hibernate
      • 5.1.5 Mixing inheritance strategies (p207-p210)