How to mix inheritance strategies with JPA annotations and Hibernate?
Solution 1
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
withInheritanceType.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)
Solution 2
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 tablesBB
andCC
.Anytime one adds properties and associations to subclasses of
BB
orCC
, 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.
Meeque
Updated on October 13, 2020Comments
-
Meeque over 3 years
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-tablepersubclassHowever, 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
, andCC
. BothBB
andCC
should have a default discriminator column calledDTYPE
. 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...
-
Meeque over 13 yearsThanks for the quick reply, Pascal. I have to say that the suggested approach seems quite verbose. In particular, I'll have to specify the desired table for each property I map in a subclass. Otherwise it will be mapped to a column of the single table for the hierarchy, right? Well I'll give it a shot tomorrow, and I'll report back...
-
Devanshu Mevada over 13 years@Meeque Yes, it's verbose. But this is your only option so you don't really have the choice.
-
Meeque over 13 yearsWell, this suggestion actually worked well:) However, it still does not feel natural, and there are a few disadvantages.
-
Jeff Evans almost 13 yearsThanks, this saved my life. In our case, we also had
@Embeddables
in each ofBB
andCC
from above, so we had to go in to the@Embeddable
and set the table attribute for each@Column
there to match the secondary table. I suppose if we needed to have different tables for those@Embeddables
fromBB
orCC
, we would have needed to use@AttributeOverrides
, but kinda lucked out on that bit. -
vipa over 6 yearsThanks Pascal. Although this answer was 7 years ago, I tried this solution today with Hibernate 5.2.10.Final and it still worked.