Глава 7. Маппинг компонентов

В Hibernate понятие компонент используется в нескольких различных контекстах, для разных целей.

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

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

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 для сохраняемых свойств, при этом не нужно определять дополнительные интерфейсы или свойства-идентификаторы.

Наш Hibernate маппинг будет выглядеть так:

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

Таблица сущности человек будет иметь колонки pid, birthday, initial, first и last.

Так как компоненты являются композицией, они не поддерживают совстместно используемых ссылок. Иными словами экземпляр компонента всегда один для содержащего его экзепляра класса. Не бывает ситуации когда два разных экземпляра содержат ссылку на один и тот же экземпляр компонента. Значение null является специальным для компонента. Когда подгружается содержащийся объект, Hibernate предполагает, что если все колонки компонента отсутствуют (null), то компонент также отсутствует (null). Для большинства случаев, это так и есть.

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

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

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

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

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

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

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

Особый случай составного элемента, это составной элемент с вложенным <many-to-one> элементом. Подобный маппинг, позволяет отображать дополнительные колонки ссылочной (many-to-many) таблицы на элементы составного класса. Следующий пример ассоциации многие ко многим (many-to-many) 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>

Для тернарных (или кватернарных, и т.д.) ассоциаций:

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

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

7.3. Компоненты в роли индексов Map

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

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

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

  • Он должен реализовать java.io.Serializable.

  • Он должен переопределить методы equals() и hashCode(), в соответствии с идентичностью составных ключей реляционной модели.

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

Т.к. составной идентификатор должен быть присвоен объекту до его сохранения, мы не можем использовать unsaved-value идентификатора, для различия созданного экземпляра и сохранённого в предыдущей сессии.

Вместо этого вы можете реализовать Interceptor.isUnsaved(), если вы хотите использовать saveOrUpdate() или каскадное сохранения / удаление (cascade save / delete). Как альтернатива, вы можете определить значение атрибута unsaved-value элемента <version> (или <timestamp>), для указания признака, не сохранённого (transient) экземпляра. В этом случае, используется версия сущности вместо (назначенного) идентификатора и вам не придётся реализовывать Interceptor.isUnsaved() самостоятельно.

Для объявления класса составного идентификатора, вместо <id> используйте тэг <composite-id> (атрибуты и элементы такие же, как и у <component>):

<class name="eg.Foo" table"FOOS">
    <composite-id name="compId" class="eg.FooCompositeID">
        <key-property name="string"/>
        <key-property name="short"/>
        <key-property name="date" column="date_" type="date"/>
    </composite-id>
    <property name="name"/>
    ....
</class>

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

<many-to-one name="foo" class="eg.Foo">
<!-- атрибут "class" необязателен -->
    <column name="foo_string"/>
    <column name="foo_short"/>
    <column name="foo_date"/>
</many-to-one>

Этот новый тег <column> также используется для определения много-стобцовых (multi-column) пользовательских типов (custom types). Его можно использовать как альтернативу элементу column. Коллекция с элементами типа Foo определяется так:

<set name="foos">
    <key column="owner_id"/>
    <many-to-many class="eg.Foo">
        <column name="foo_string"/>
        <column name="foo_short"/>
        <column name="foo_date"/>
    </many-to-many>
</set>

С другой стороны, <one-to-many>, обычно, не объявляет столбцов.

Если Foo сам содержит коллекции, они также используют составной внешний ключ.

<class name="eg.Foo">
    ....
    ....
    <set name="dates" lazy="true">
        <key>   <!-- коллекция наследует составной тип ключа -->
            <column name="foo_string"/>
            <column name="foo_short"/>
            <column name="foo_date"/>
        </key>
        <element column="foo_date" type="date"/>
    </set>
</class>

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

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

<dynamic-component name="userAttributes">
    <property name="foo" column="FOO"/>
    <property name="bar" column="BAR"/>
    <many-to-one name="baz" class="eg.Baz" column="BAZ"/>
</dynamic-component>

Семантика отображения <dynamic-component> идентична <component>. Преимущество такого способа отображения, в возможности определять истинное свойство бина в момент развёртывания, редактируя документ маппинга. (Изменение документа маппинга во время выполнения также возможно, используя DOM парсер.)