Глава 10. Отображение наследования
Оглавление-
10.1. Три стратегии
- 10.1.1. Иерархия Таблица на класс
- 10.1.2. Таблица на подкласс
- 10.1.3. Таблица на подкласс: использование дискриминатора
- 10.1.4. Смешивание «Иерархии Таблица на класс» с «Таблица на подкласс»
- 10.1.5. Таблица на конкретный класс
- 10.1.6. Таблица на конкретный класс с использованием неявного полиморфизма
- 10.1.7. Смешивание неявного полиморфизма с другими отображениями наследования
- 10.2. Ограничения
10.1. Три стратегии
Hibernate поддерживает три основные стратегии отображения наследования:
- Иерархия Таблица на класс (table per class hierarchy)
- Таблица на подкласс (table per subclass)
- Таблица на конкретный класс (table per concrete class)
Кроме того, Hibernate поддерживает четвёртый, немного другой вид полиморфизма:
- Неявный полиморфизм (implicit polymorphism)
Можно использовать разные стратегии отображения для разных ветвей одной иерархии наследования.
Затем вы можете использовать неявный полиморфизм для достижения полиморфизма по всей иерархии.
Однако 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 | не поддерживается | не поддерживается |