Hibernate поддерживает три базовые стратегии маппинга наследников.
Возмножно использование разных стратегий маппинга для различных веток одной и
той же иерархии наследников, но есть некоторые ограничения для стратегии
маппинга table-per-concrete class. Hibernate не поддерживает одновременного
использования <subclass> маппинга и <joined-subclass> маппинга в одном элементе <class>
Допустим у нас есть интерфейс Payment и классы CreditCardPayment, CashPayment, ChequePayment, которые его реализуют. Маппинг
table-per-hierarchy может выглядеть так:
<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">
...
</subclass>
<subclass name="CashPayment" discriminator-value="CASH">
...
</subclass>
<subclass name="ChequePayment" discriminator-value="CHEQUE">
...
</subclass>
</class>
Используется только одна таблица для всех классов. В этой стратегии маппинга
есть одно сильное ограничение: столбцы которые определены в подкассах не могут
быть NOT NULL.
Маппинг 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"/>
...
</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>
В данном случае требуются четыре таблицы. Три таблицы подклассов
ассоциируются с таблицей суперкласса по первичному ключу (в реляционной модели
это в действительности отношение один к одному).
Заметьте, что реализация table-per-subclass в Hibernate не требует
столбцов-дискриминаторов. Другие продукты для объекто-реляционного отображения
используют модель реализации стратегии table-per-subclass, требующую определения
столбца-дискриминатора в таблице суперкласса. Подход используемый в Hibernate
намного более труден в реализации, но, вероятно, более корректен с точки зрения
проектирования реляционной модели.
Для любой из этих двух стратегий маппинга, полиморфические ассоциации к Payment маппятся используя <many-to-one>.
<many-to-one name="payment"
column="PAYMENT"
class="Payment"/>
Стратегия маппинга table-per-concrete-class очень сильно отличается от
остальных.
<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. Вместо этого используется
Hibernate'овский неявный полиморфизм (implicit
polymorphism). Так же заметьте, что свойства интерфейса Payment определяются для каждого из подклассов.
В данном случае случае полиморфическая ассоциация к Payment маппится с использованием <any>.
<any name="payment"
meta-type="class"
id-type="long">
<column name="PAYMENT_CLASS"/>
<column name="PAYMENT_ID"/>
</any>
Будет лучше, если в качестве meta-type мы укажем
пользовательский тип, для определения маппинга подклассов Payment.
<any name="payment"
meta-type="PaymentMetaType"
id-type="long">
<column name="PAYMENT_TYPE"/> <!-- CREDIT, CASH или CHEQUE -->
<column name="PAYMENT_ID"/>
</any>
Есть еще одна дополтинельная вещь, которую нужно учитывать при данном
маппинге. Так как подклассы определены в элементах <class> (и Payment интерфейс к
ним), каждый из подклассов может в свою очередь использовать стратегии маппинга
table-per-class или table-per-subclass! (При этом остается возможность
использования полиморфических запросов к интерфейсу 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. Once again, we don't mention Payment explicitly. If we execute a query against the Payment interface - for example, from
Payment - Hibernate automatically returns instances of CreditCardPayment (and its subclasses, since they also
implement Payment), CashPayment
and ChequePayment but not instances of NonelectronicTransaction.