Глава 10. Отображение наследования

Оглавление
  1. 10.1. Три стратегии
    1. 10.1.1. Иерархия Таблица на класс
    2. 10.1.2. Таблица на подкласс
    3. 10.1.3. Таблица на подкласс: использование дискриминатора
    4. 10.1.4. Смешивание «Иерархии Таблица на класс» с «Таблица на подкласс»
    5. 10.1.5. Таблица на конкретный класс
    6. 10.1.6. Таблица на конкретный класс с использованием неявного полиморфизма
    7. 10.1.7. Смешивание неявного полиморфизма с другими отображениями наследования
  2. 10.2. Ограничения

10.1. Три стратегии

Hibernate поддерживает три основные стратегии отображения наследования:

Кроме того, Hibernate поддерживает четвёртый, немного другой вид полиморфизма:

Можно использовать разные стратегии отображения для разных ветвей одной иерархии наследования. Затем вы можете использовать неявный полиморфизм для достижения полиморфизма по всей иерархии. Однако Hibernate не поддерживает смешивание <subclass>, <joined-subclass> и <union-subclass> отображений под одним и тем же корневым элементом <class>. Можно смешивать «Иерархию Таблица на класс» и «Таблица на подкласс» под одним и тем же элементом <class>, объединив элементы <subclass> и <join> (см. пример ниже).

Можно определить <subclass>, <union-subclass> и <joined-subclass> в отдельных документах отображений непосредственно под hibernate-mapping. Это позволяет расширить иерархию классов, добавив новый файл отображения. Вы должны указать атрибут extends в отображении подкласса, назвав его ранее отображённым суперклассом. Ранее эта функция делала упорядочение документов отображений важными. С Hibernate упорядочение файлов отображения не имеет значения при использовании ключевого слова extends. Порядок внутри одного файла сопоставления по-прежнему должен быть определен как суперклассы перед подклассами.

 <hibernate-mapping>
     <subclass name="DomesticCat" extends="Cat" discriminator-value="D">
          <property name="name" type="string"/>
     </subclass>
 </hibernate-mapping>

10.1.1. Иерархия Таблица на класс

Table per class hierarchy

Предположим, у нас есть интерфейс Payment с реализующими его CreditCardPayment, CashPayment и ChequePayment. Отображение «Иерархии Таблица на класс» будет выглядеть следующим образом:

<class name="Payment" table="PAYMENT">
    <id name="id" type="long" column="PAYMENT_ID">
        <generator class="native"/>
    </id>
    <discriminator column="PAYMENT_TYPE" type="string"/>
    <property name="amount" column="AMOUNT"/>
    ...
    <subclass name="CreditCardPayment" discriminator-value="CREDIT">
        <property name="creditCardType" column="CCTYPE"/>
        ...
    </subclass>
    <subclass name="CashPayment" discriminator-value="CASH">
        ...
    </subclass>
    <subclass name="ChequePayment" discriminator-value="CHEQUE">
        ...
    </subclass>
</class>

Требуется в точности одна таблица. Существует ограничение этой стратегии отображения: столбцы, объявленные подклассами, такие как CCTYPE, не могут иметь NOT NULL ограничений.

10.1.2. Таблица на подкласс

Table per subclass

Отображение «Таблица на подкласс» выглядит следующим образом:

<class name="Payment" table="PAYMENT">
    <id name="id" type="long" column="PAYMENT_ID">
        <generator class="native"/>
    </id>
    <property name="amount" column="AMOUNT"/>
    ...
    <joined-subclass name="CreditCardPayment" table="CREDIT_PAYMENT">
        <key column="PAYMENT_ID"/>
        <property name="creditCardType" column="CCTYPE"/>
        ...
    </joined-subclass>
    <joined-subclass name="CashPayment" table="CASH_PAYMENT">
        <key column="PAYMENT_ID"/>
        ...
    </joined-subclass>
    <joined-subclass name="ChequePayment" table="CHEQUE_PAYMENT">
        <key column="PAYMENT_ID"/>
        ...
    </joined-subclass>
</class>

Требуются четыре таблицы. Три таблицы подкласса имеют ассоциации первичных ключей с таблицей суперкласса, поэтому реляционная модель на самом деле является ассоциацией «один-к-одному».

10.1.3. Таблица на подкласс: использование дискриминатора

Реализация таблицы Hibernate для каждого подкласса не требует колонки дискриминатора. Другие объектные/реляционные отображения используют разные реализации «Таблица на подкласс», для которых требуется столбец дискриминатора типа в таблице суперкласса. Подход Hibernate гораздо сложнее реализовать, но, возможно, он более корректен с точки зрения реляционных отношений. Если вы хотите использовать столбец дискриминатора со стратегией «Таблица на подкласс», вы можете комбинировать использование <subclass> и <join> следующим образом:

<class name="Payment" table="PAYMENT">
    <id name="id" type="long" column="PAYMENT_ID">
        <generator class="native"/>
    </id>
    <discriminator column="PAYMENT_TYPE" type="string"/>
    <property name="amount" column="AMOUNT"/>
    ...
    <subclass name="CreditCardPayment" discriminator-value="CREDIT">
        <join table="CREDIT_PAYMENT">
            <key column="PAYMENT_ID"/>
            <property name="creditCardType" column="CCTYPE"/>
            ...
        </join>
    </subclass>
    <subclass name="CashPayment" discriminator-value="CASH">
        <join table="CASH_PAYMENT">
            <key column="PAYMENT_ID"/>
            ...
        </join>
    </subclass>
    <subclass name="ChequePayment" discriminator-value="CHEQUE">
        <join table="CHEQUE_PAYMENT" fetch="select">
            <key column="PAYMENT_ID"/>
            ...
        </join>
    </subclass>
</class>

Дополнительное объявление fetch=«select» указывает Hibernate не получать (fetch) данные подкласса ChequePayment, используя внешнее соединение (outer join) при запросе суперкласса.

10.1.4. Смешивание «Иерархии Таблица на класс» с «Таблица на подкласс»

Вы даже можете смешивать стратегии «Иерархия Таблица на класс» и «Таблица на подкласс», используя следующий подход:

<class name="Payment" table="PAYMENT">
    <id name="id" type="long" column="PAYMENT_ID">
        <generator class="native"/>
    </id>
    <discriminator column="PAYMENT_TYPE" type="string"/>
    <property name="amount" column="AMOUNT"/>
    ...
    <subclass name="CreditCardPayment" discriminator-value="CREDIT">
        <join table="CREDIT_PAYMENT">
            <property name="creditCardType" column="CCTYPE"/>
            ...
        </join>
    </subclass>
    <subclass name="CashPayment" discriminator-value="CASH">
        ...
    </subclass>
    <subclass name="ChequePayment" discriminator-value="CHEQUE">
        ...
    </subclass>
</class>

Для любой из этих стратегий отображения полиморфная ассоциация с корневым классом Payment отображается с использованием <many-to-one>.

<many-to-one name="payment" column="PAYMENT_ID" class="Payment"/>

10.1.5. Таблица на конкретный класс

Table per concrete class

Существует два способа отображения стратегии «Таблица на конкретный класс». Во-первых, вы можете использовать <union-subclass>.

<class name="Payment">
    <id name="id" type="long" column="PAYMENT_ID">
        <generator class="sequence"/>
    </id>
    <property name="amount" column="AMOUNT"/>
    ...
    <union-subclass name="CreditCardPayment" table="CREDIT_PAYMENT">
        <property name="creditCardType" column="CCTYPE"/>
        ...
    </union-subclass>
    <union-subclass name="CashPayment" table="CASH_PAYMENT">
        ...
    </union-subclass>
    <union-subclass name="ChequePayment" table="CHEQUE_PAYMENT">
        ...
    </union-subclass>
</class>

Для подклассов задействованы три таблицы. Каждая таблица определяет столбцы для всех свойств класса, включая унаследованные свойства.

Ограничение этого подхода состоит в том, что если свойство отображается на суперкласс, имя столбца должно быть таким же во всех таблицах подкласса. Стратегия генератора идентичности не допускается в объединении наследования подкласса. Семя первичного ключа должно быть общим для всех объединенных подклассов иерархии.

Если ваш суперкласс является абстрактным, отобразите его с abstract=«true». Если он не абстрактный, то для хранения экземпляров суперкласса необходима дополнительная таблица (по умолчанию это PAYMENT в приведенном выше примере).

10.1.6. Таблица на конкретный класс с использованием неявного полиморфизма

Альтернативный подход заключается в использовании неявного полиморфизма:

<class name="CreditCardPayment" table="CREDIT_PAYMENT">
    <id name="id" type="long" column="CREDIT_PAYMENT_ID">
        <generator class="native"/>
    </id>
    <property name="amount" column="CREDIT_AMOUNT"/>
    ...
</class>
<class name="CashPayment" table="CASH_PAYMENT"> <id name="id" type="long" column="CASH_PAYMENT_ID"> <generator class="native"/> </id> <property name="amount" column="CASH_AMOUNT"/> ... </class>
<class name="ChequePayment" table="CHEQUE_PAYMENT"> <id name="id" type="long" column="CHEQUE_PAYMENT_ID"> <generator class="native"/> </id> <property name="amount" column="CHEQUE_AMOUNT"/> ... </class>

Обратите внимание, что интерфейс Payment не указан явно. Также обратите внимание, что свойства Payment отображаются в каждом из подклассов. Если вы хотите избежать дублирования, рассмотрите возможность использования сущностей XML (например, [<!ENTITY allproperties SYSTEM "allproperties.xml">] в объявлении DOCTYPE и %allproperties; в отображении).

Недостатком такого подхода является то, что Hibernate не генерирует SQL UNION при выполнении полиморфных запросов.

Для этой стратегии отображения полиморфная ассоциация с Payment обычно отображается с использованием <any>.

<any name="payment" meta-type="string" id-type="long">
    <meta-value value="CREDIT" class="CreditCardPayment"/>
    <meta-value value="CASH" class="CashPayment"/>
    <meta-value value="CHEQUE" class="ChequePayment"/>
    <column name="PAYMENT_CLASS"/>
    <column name="PAYMENT_ID"/>
</any>

10.1.7. Смешивание неявного полиморфизма с другими отображениями наследования

Поскольку подклассы отображаются в их собственном элементе <class>, а так как Payment — это просто интерфейс), каждый из подклассов может легко быть частью другой иерархии наследования. Вы все равно можете использовать полиморфные запросы для интерфейса Payment.

<class name="CreditCardPayment" table="CREDIT_PAYMENT">
    <id name="id" type="long" column="CREDIT_PAYMENT_ID">
        <generator class="native"/>
    </id>
    <discriminator column="CREDIT_CARD" type="string"/>
    <property name="amount" column="CREDIT_AMOUNT"/>
    ...
    <subclass name="MasterCardPayment" discriminator-value="MDC"/>
    <subclass name="VisaPayment" discriminator-value="VISA"/>
</class>
<class name="NonelectronicTransaction" table="NONELECTRONIC_TXN"> <id name="id" type="long" column="TXN_ID"> <generator class="native"/> </id> ... <joined-subclass name="CashPayment" table="CASH_PAYMENT"> <key column="PAYMENT_ID"/> <property name="amount" column="CASH_AMOUNT"/> ... </joined-subclass> <joined-subclass name="ChequePayment" table="CHEQUE_PAYMENT"> <key column="PAYMENT_ID"/> <property name="amount" column="CHEQUE_AMOUNT"/> ... </joined-subclass> </class>

Еще раз, Payment не упоминается явно. Если мы выполним запрос к интерфейсу Payment, например, from Payment, Hibernate автоматически возвращает экземпляры CreditCardPayment (и его подклассов, так как они также реализуют Payment), CashPayment и ChequePayment, но не экземпляры NonelectronicTransaction.

10.2. Ограничения

Существуют ограничения на подход «неявного полиморфизма» к таблице для стратегии отображения «Таблица на конкретный класс». Существуют несколько менее строгих ограничений для сопоставлений <union-subclass>.

В следующей таблице показаны ограничения таблицы для сопоставлений «Таблица на конкретный класс» и неявного полиморфизма в Hibernate.

Таблица 10.1. Особенности отображения наследования

Стратегия наследования Полиморфный многие-к-одному Полиморфный один-к-одному Полиморфный один-ко-многим Полиморфный многие-ко-многим Полиморфный load()/get() Полиморфные запросы Полиморфные соединения Outer join fetching
Иерархия Таблица на класс <many-to-one> <one-to-one> <one-to-many> <many-to-many> s.get(Payment.class, id) from Payment p from Order o join o.payment p поддерживается
Таблица на подкласс <many-to-one> <one-to-one> <one-to-many> <many-to-many> s.get(Payment.class, id) from Payment p from Order o join o.payment p поддерживается
Таблица на конкретный класс (union-subclass) <many-to-one> <one-to-one> только для inverse="true" <many-to-many> s.get(Payment.class, id) from Payment p from Order o join o.payment p поддерживается
Таблица на конкретный класс (неявный полиморфизм) <any> не поддерживается не поддерживается <many-to-many> s.createCriteria(Payment.class).add(Restrictions.idEq(id)).uniqueResult() from Payment p не поддерживается не поддерживается