Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 1000
Meeque Avatar asked Oct 12 '10 13:10

Meeque


People also ask

What is the default strategy of inheritance when using annotations?

The default strategy, InheritanceType. SINGLE_TABLE, is used if the @Inheritance annotation is not specified on the root class of the entity hierarchy.

Which inheritance strategy is better in Hibernate?

Single Table. The single table strategy maps all entities of the inheritance structure to the same database table. This approach makes polymorphic queries very efficient and provides the best performance.

What are the three types of inheritance mapping strategies described in JPA?

Inheritance is the core concept of object oriented language, therefore we can use inheritance relationships or strategies between entities. JPA support three types of inheritance strategies such as SINGLE_TABLE, JOINED_TABLE, and TABLE_PER_CONCRETE_CLASS.


2 Answers

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)
like image 120
Pascal Thivent Avatar answered Oct 08 '22 09:10

Pascal Thivent


Just for the sake of clarity, here is Pascal's solution applied to the example code from my question:

@Entity @Inheritance( strategy = InheritanceType.SINGLE_TABLE ) @DiscriminatorColumn( name = "entityType",          discriminatorType = DiscriminatorType.STRING ) public abstract class A implements Serializable {     @Id     private String id;      // other mapped properties... }  @Entity @SecondaryTable( name = "BB" ) public class BB extends A {     @Basic( optional = false)     @Column( table = "BB" )     private String property1;      // 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 @SecondaryTable( name = "CC" ) public class CC extends A {     @ManyToOne( optional = false)     @JoinColumn( table = "CC" )     private SomeEntity association1;      // other mapped properties and associations... }  @Entity public class CC1 extends CC {     // other stuff, not necessarily mapped... }  ... 

I've successfully applied this approach to my problem, and I'll stick to it for the time being. However I still see the following disadvantages:

  • The discriminator column is located in the main table for the hierarchy, the table for root-enity A. In my case, it would be sufficient to have the discriminator column in the secondary tables BB and CC.

  • Anytime one adds properties and associations to subclasses of BB or CC, he/she has to specify that they should be mapped to the respective secondary table. Would be nice, if there was a way to make that the default.

like image 23
Meeque Avatar answered Oct 08 '22 09:10

Meeque