web-dev-qa-db-ja.com

継承戦略とJPAアノテーションおよびHibernateを組み合わせる方法は?

Hibernateリファレンスドキュメントによると、HibernateのXMLメタデータを使用する場合、異なる継承マッピング戦略を混在させることが可能であるはずです。
http://docs.jboss.org/hibernate/stable/core/reference/en/html/inheritance.html#inheritance-mixing-tableperclass-tablepersubclass

ただし、Hibernate Annotations Reference Guideの対応するセクションでは、以下については説明していません。
http://docs.jboss.org/hibernate/stable/annotations/reference/en/html/entity.html#d0e1168

一方、JavaDocsは、継承戦略の混合が可能であるべきだと示唆しています。たとえばjavax.persistence.DiscriminatorColumnでは、次のように述べています。

戦略と弁別子列は、エンティティクラス階層のルートでのみ指定されますまたは異なる継承戦略のサブ階層が適用されます。


以下は、私が達成しようとしているマッピングの例です。階層のルートの近くでtable-per-subclassマッピングを使用したいのですが、table-per-class-hierarchy葉の近くのマッピング。ここにいくつかのサンプルコードがあります:

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

...

このマッピングから期待するのは、ABB、およびCCの3つのテーブルがあることです。 BBCCの両方に、DTYPEと呼ばれるデフォルトの識別子列が必要です。また、マップされたすべてのプロパティに必要なすべての列と、それぞれのサブクラスの関連付けも提供する必要があります。

代わりに、クラス階層は全体を通してtable-per-subclass継承戦略を使用しているようです。つまり上記のエンティティごとに独自のテーブルを取得します。クラス階層の葉は非常に軽量であり、葉ごとに個別のテーブルを用意するのはやり過ぎなため、これを避けたいと思います。


見落としましたか?アドバイスは大歓迎です!追加情報を提供させていただきます...

32
Meeque

Hibernateリファレンスドキュメントによると、HibernateのXMLメタデータ(...)を使用する場合、さまざまな継承マッピング戦略を混在させることができるはずです。

実際にはreallyはサポートされていません。ドキュメントの例では、セカンダリテーブルを使用して単一テーブル戦略から切り替える「不正行為」を行っています。引用HibernateでのJava Persistence

<union-subclass><sub- class>、および<joined-subclass>マッピング要素をネストすることにより、継承階層全体をマッピングできます。これらを混在させることはできません。たとえば、クラスごとのテーブル階層と弁別子を使用して、正規化されたサブクラスごとのテーブル戦略に切り替えます。 継承戦略を決定したら、それに固執する必要があります

ただし、これは完全には当てはまりません。いくつかのHibernateトリックを使用すると、特定のサブクラスのマッピング戦略を切り替えることができます。たとえば、クラス階層を単一のテーブルにマップできますが、特定のサブクラスについては、サブクラスごとのテーブルの場合と同様に、外部キーマッピング戦略を使用して別のテーブルに切り替えます。これは、<join>マッピング要素で可能です。

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

そして、アノテーションで同じことを達成できます:

Java Persistenceは、アノテーションを使用したこの混合継承マッピング戦略もサポートしています。以前と同じように、スーパークラスBillingDetailsInheritanceType.SINGLE_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;
    ...
}

私はこれをテストしませんでしたが、あなたは多分以下を試すことができます:

  • sINGLE_TABLE戦略を使用してマップA
  • @SecondaryTableアノテーションを使用してBB、CCなどをマップします。

私はこれをテストしていません、それがBB1、BB2でうまく機能するかどうかはわかりません。

参照

  • Hibernateを使用したJava Persistence
    • 5.1.5継承戦略の混合(p207-p210)
27
Pascal Thivent

わかりやすくするために、私の質問のコード例に適用したPascalのソリューションを次に示します。

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

...

私はこのアプローチを自分の問題にうまく適用できたので、当面はこれに固執します。ただし、次の欠点がまだあります。

  • 弁別子列は、階層のメインテーブル、ルートエンティティAのテーブルにあります。私の場合、セカンダリテーブルBBおよびCCに識別子列があれば十分です。

  • プロパティと関連付けをBBまたはCCのサブクラスに追加するときはいつでも、それらをそれぞれのセカンダリテーブルにマップする必要があることを指定する必要があります。それをデフォルトにする方法があれば、いいでしょう。

24
Meeque