Глава 9. Отображение компонентов

Оглавление
  1. 9.1. Зависимые объекты
  2. 9.2. Коллекции зависимых объектов
  3. 9.3. Компоненты как индексы карты
  4. 9.4. Компоненты как составные идентификаторы
  5. 9.5. Динамические компоненты

В Hibernate понятие компонент в разных контекстах используется по-разному.

9.1. Зависимые объекты

Компонент — это содержащийся объект, который сохраняется (persisted) как тип значения, а не ссылка на сущность. Термин «компонент» относится к объектно-ориентированному понятию композиции, а не к компонентам уровня архитектуры. Например, вы можете моделировать человека следующим образом:

public class Person {
    private java.util.Date birthday;
    private Name name;
    private String key;
    public String getKey() {
        return key;
    }
    private void setKey(String key) {
        this.key=key;
    }
    public java.util.Date getBirthday() {
        return birthday;
    }
    public void setBirthday(java.util.Date birthday) {
        this.birthday = birthday;
    }
    public Name getName() {
        return name;
    }
    public void setName(Name name) {
        this.name = name;
    }
    ......
    ......
}
public class Name {
    char initial;
    String first;
    String last;
    public String getFirst() {
        return first;
    }
    void setFirst(String first) {
        this.first = first;
    }
    public String getLast() {
        return last;
    }
    void setLast(String last) {
        this.last = last;
    }
    public char getInitial() {
        return initial;
    }
    void setInitial(char initial) {
        this.initial = initial;
    }
}

Теперь Name может сохраняться как компонент Person. Name определяет методы getter и setter для его постоянных (persistent) свойств, но ему не нужно объявлять какие-либо интерфейсы или свойства идентификатора.

Отображение Hibernate будет выглядеть так:

<class name="eg.Person" table="person">
    <id name="Key" column="pid" type="string">
        <generator class="uuid"/>
    </id>
    <property name="birthday" type="date"/>
    <component name="Name" class="eg.Name"> <!-- атрибут class необязателен -->
        <property name="initial"/>
        <property name="first"/>
        <property name="last"/>
    </component>
</class>

Таблица person будет иметь столбцы pid, birthday, initial, first и last.

Как и типы значений, компоненты не поддерживают общие ссылки. Другими словами, два Person могут содержать два независимых объекта Name c одним и же значением. Семантика нулевого (null) значения компонента является ad hoc. При перезагрузке содержащегося объекта Hibernate будет считать, что если все столбцы компонента равны null, то весь компонент равен null. Это подходит для большинства целей.

Свойства компонента могут быть любого типа Hibernate (коллекции, ассоциации «многие-к-одному», другие компоненты и т. д.). Вложенные компоненты не следует рассматривать как экзотическое использование. Hibernate предназначен для поддержки «мелкозернистой» объектной модели.

Элемент <component> позволяет подэлементу <parent> отображать свойство класса компонента в качестве ссылки обратно на содержащую сущность.

<class name="eg.Person" table="person">
    <id name="Key" column="pid" type="string">
        <generator class="uuid"/>
    </id>
    <property name="birthday" type="date"/>
    <component name="Name" class="eg.Name" unique="true">
        <parent name="namedPerson"/> <!-- ссылка на Person -->
        <property name="initial"/>
        <property name="first"/>
        <property name="last"/>
    </component>
</class>

9.2. Коллекции зависимых объектов

Поддерживаются коллекции компонентов (например, массив типа Name). Объявите коллекцию компонентов, заменив тег <element> тегом <composite-element>:

<set name="someNames" table="some_names" lazy="true">
    <key column="id"/>
    <composite-element class="eg.Name"> <!-- атрибут class обязателен -->
        <property name="initial"/>
        <property name="first"/>
        <property name="last"/>
    </composite-element>
</set>

Важно

Если вы определяете набор составных элементов, важно правильно реализовать equals() и hashCode().

Составные элементы могут содержать компоненты, но не коллекции. Если ваш составной элемент содержит компоненты, используйте тег <nested-composite-element>. Этот случай представляет собой набор компонентов, которые сами имеют компоненты. Возможно, вам захочется рассмотреть вопрос о том, является ли ассоциация «один-ко-многим» более подходящей. Переделайте составной элемент как сущность, но имейте в виду, что, хотя модель Java одинакова, семантика реляционной модели и персистентности все еще немного отличается.

Отображение составных элементов не поддерживает свойства с нулевым (null) значением, если вы используете <set>. В таблице составных элементов нет отдельного столбца первичного ключа. Hibernate использует значение каждого столбца для идентификации записи при удалении объектов, что невозможно при нулевых значениях. Вы должны либо использовать только ненулевые (not-null) свойства в составном элементе, либо выбрать <list>, <map>, <bag> или <idbag>.

Частным случаем составного элемента является составной элемент с вложенным элементом <many-to-one>. Это отображение позволяет отобразить дополнительные столбцы таблицы ассоциаций «многие-ко-многим» на класс составных элементов. Ниже приведена ассоциация «многие-ко-многим» от Order к Item, где purchaseDate, price и quantity являются свойствами ассоциации:

<class name="eg.Order" .... >
    ....
    <set name="purchasedItems" table="purchase_items" lazy="true">
        <key column="order_id">
        <composite-element class="eg.Purchase">
            <property name="purchaseDate"/>
            <property name="price"/>
            <property name="quantity"/>
            <many-to-one name="item" class="eg.Item"/> <!-- атрибут class необязателен -->
        </composite-element>
    </set>
</class>

Нельзя ссылаться на покупку на другой стороне для навигации по двунаправленной ассоциации. Компоненты являются типами значений и не позволяют использовать общие ссылки. Один Purchase может быть в наборе Order, но на него нельзя ссылаться в тоже время из Item.

Возможны даже тройные (или четвертичные и т. д.) ассоциации:

<class name="eg.Order" .... >
    ....
    <set name="purchasedItems" table="purchase_items" lazy="true">
        <key column="order_id">
        <composite-element class="eg.OrderLine">
            <many-to-one name="purchaseDetails class="eg.Purchase"/>
            <many-to-one name="item" class="eg.Item"/>
        </composite-element>
    </set>
</class>

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

9.3. Компоненты как индексы карты

Элемент <composite-map-key> позволяет вам отображать класс компонентов как ключ Map. Убедитесь, что вы переопределили hashCode() и equals() правильно в классе компонента.

9.4. Компоненты как составные идентификаторы

Вы можете использовать компонент как идентификатор класса сущности. Класс компонента должен удовлетворять определенным требованиям:

Заметка

Хотя второе требование не является абсолютно жестким, оно рекомендуется.

Вы не можете использовать IdentifierGenerator для создания составных ключей. Вместо этого приложение должно назначать свои собственные идентификаторы.

Используйте тег <composite-id> с вложенными элементами <key-property> вместо обычного объявления <id>. Например, класс OrderLine имеет первичный ключ, который зависит от (составного) первичного ключа Order.

<class name="OrderLine">
<composite-id name="id" class="OrderLineId"> <key-property name="lineId"/> <key-property name="orderId"/> <key-property name="customerId"/> </composite-id>
<property name="name"/>
<many-to-one name="order" class="Order" insert="false" update="false"> <column name="orderId"/> <column name="customerId"/> </many-to-one> ....
</class>

Любые внешние ключи, ссылающиеся на таблицу OrderLine, теперь являются составными. Объявите это в своих отображениях для других классов. Ассоцияция с OrderLine отображается следующим образом:

<many-to-one name="orderLine" class="OrderLine">
<!-- атрибут class необязателен -->
    <column name="lineId"/>
    <column name="orderId"/>
    <column name="customerId"/>
</many-to-one>

Совет

Элемент column является альтернативой атрибуту column везде. Использование элемента column просто дает больше опций объявления, которые в основном полезны при использовании hbm2ddl

Связь «много-ко-многим» с OrderLine также использует составной внешний ключ:

<set name="undeliveredOrderLines">
    <key column name="warehouseId"/>
    <many-to-many class="OrderLine">
        <column name="lineId"/>
        <column name="orderId"/>
        <column name="customerId"/>
    </many-to-many>
</set>

Коллекция OrderLines в Order будет использовать:

<set name="orderLines" inverse="true">
    <key>
        <column name="orderId"/>
        <column name="customerId"/>
    </key>
    <one-to-many class="OrderLine"/>
</set>

Элемент <one-to-many> не объявляет столбцы.

Если OrderLine сам владеет коллекцией, он также имеет составной внешний ключ.

<class name="OrderLine">
    ....
    ....
    <list name="deliveryAttempts">
        <key>   <!-- набор наследует составной тип ключа -->
            <column name="lineId"/>
            <column name="orderId"/>
            <column name="customerId"/>
        </key>
        <list-index column="attemptId" base="1"/>
        <composite-element class="DeliveryAttempt">
            ...
        </composite-element>
    </set>
</class>

9.5. Динамические компоненты

Вы также можете указать свойство типа Map:

<dynamic-component name="userAttributes">
    <property name="foo" column="FOO" type="string"/>
    <property name="bar" column="BAR" type="integer"/>
    <many-to-one name="baz" class="Baz" column="BAZ_ID"/>
</dynamic-component>

Семантика отображения <dynamic-component> идентична <component>. Преимуществом такого вида отображения является возможность определять фактические свойства компонента во время развертывания, просто редактируя документ сопоставления. Также возможны манипуляции с документом сопоставления во время исполнения с использованием парсера DOM. Вы также можете получить доступ и изменить метамодель времени конфигурации Hibernate через объект Configuration.