Глава 7. Отображение коллекции

Оглавление
  1. 7.1. Постоянные (persistent) коллекции
  2. 7.2. Как отображать коллекции
    1. 7.2.1. Сбор внешних ключей
    2. 7.2.2. Индексированные коллекции
    3. 7.2.3. Коллекции основных типов и встраиваемых объектов
  3. 7.3. Расширенные отображения коллекции
    1. 7.3.1. Отсортированные коллекции
    2. 7.3.2. Двунаправленные ассоциации
    3. 7.3.3. Двунаправленные ассоциации с индексированными коллекциями
    4. 7.3.4. Тройные ассоциации
    5. 7.3.5. Использование <idbag>
  4. 7.4. Примеры коллекций

7.1. Постоянные (persistent) коллекции

Естественно, Hibernate также позволяет сохранять (persist) коллекции. Эти постоянные коллекции могут содержать почти любой другой тип Hibernate, включая: базовые типы, пользовательские типы, компоненты и ссылки на другие сущности. Различие между значением и ссылочной семантикой в этом контексте очень важно. Объект в коллекции может обрабатываться семантикой «значение» (ее жизненный цикл полностью зависит от владельца коллекции), или это может быть ссылка на другую сущность с её собственным жизненным циклом. В последнем случае считается, что только «ссылка (link)» между двумя объектами является состоянием, удержанным коллекцией.

В качестве требования постоянные поля, содержащие коллекции, должны быть объявлены как тип интерфейса (см. «Пример 7.2 Отображение коллекции с использованием @OneToMany и @JoinColumn» ). Фактическим интерфейсом может быть java.util.Set, java.util.Collection, java.util.List, java.util.Map, java.util.SortedSet, java.util.SortedMap или что угодно («что угодно» означает, что вам нужно будет написать реализацию org.hibernate.usertype.UserCollectionType).

Обратите внимание, как в  «Примере 7.2 Отображение коллекции с использованием @OneToMany и @JoinColumn» ) переменная parts экземпляра была инициализирована экземпляром HashSet. Это лучший способ инициализировать поля, содержащие коллекции, у вновь созданных (непостоянных (non-persistent)) экземпляров. Когда вы делаете экземпляр постоянным (persistent), вызывая persist(), Hibernate фактически заменит HashSet экземпляром собственной реализации Hibernate Set. Помните о следующей ошибке:

Пример 7.1. Hibernate использует свои собственные реализации коллекции.

Cat cat = new DomesticCat();
Cat kitten = new DomesticCat();
....
Set kittens = new HashSet();
kittens.add(kitten);
cat.setKittens(kittens);
session.persist(cat);
kittens = cat.getKittens(); // Хорошо, коллекция с котятами это Set (HashSet) cat.getKittens(); // Ошибка!

Постоянные (persistent) коллекции, внедрённые (injected) Hibernate, ведут себя как HashMap, HashSet, TreeMap, TreeSet или ArrayList, в зависимости от типа интерфейса.

Экземпляры коллекций имеют обычное поведение типов значений. Они автоматически сохраняются при обращении к постоянному объекту и автоматически удаляются при отсутствии ссылок. Если коллекция передаётся из одного постоянного объекта в другой, его элементы могут перемещаться из одной таблицы в другую. Две сущности не могут передавать ссылку на один и тот же экземпляр коллекции. Из-за лежащей в основе реляционной модели свойства, содержащие коллекции, не поддерживают семантику значения null. Hibernate не различает ссылку на null и пустую коллекцию.

Заметка

Используйте постоянные коллекции так же, как вы используете обычные коллекции Java. Однако убедитесь, что вы понимаете семантику двунаправленных ассоциаций (см. раздел«7.3.2 Двунаправленные ассоциации»).

7.2. Как отображать коллекции

Используя аннотации, вы можете отображать Collection, List, Map и Set связанных сущностей, используя @OneToMany и @ManyToMany. Для коллекций базового или встраиваемого типа используйте @ElementCollection. В простейшем случае сопоставление коллекции выглядит следующим образом:

Пример 7.2. Отображение коллекции с использованием @OneToMany и @JoinColumn

@Entity
public class Product {
private String serialNumber; private Set<Part> parts = new HashSet<Part>();
@Id public String getSerialNumber() { return serialNumber; } void setSerialNumber(String sn) { serialNumber = sn; }
@OneToMany @JoinColumn(name="PART_ID") public Set<Part> getParts() { return parts; } void setParts(Set parts) { this.parts = parts; } }
@Entity public class Part { ... }

Product описывает однонаправленную связь с частью, использующей столбец соединения PART_ID. В этом однонаправленном сценарии один-ко-многим вы также можете использовать join table, как показано в  «Примере 7.3. Отображение коллекции с использованием @OneToMany и @JoinTable» .

Пример 7.3. Отображение коллекции с использованием @OneToMany и @JoinTable

@Entity
public class Product {
private String serialNumber; private Set<Part> parts = new HashSet<Part>();
@Id public String getSerialNumber() { return serialNumber; } void setSerialNumber(String sn) { serialNumber = sn; }
@OneToMany @JoinTable( name="PRODUCT_PARTS", joinColumns = @JoinColumn( name="PRODUCT_ID"), inverseJoinColumns = @JoinColumn( name="PART_ID") ) public Set<Part> getParts() { return parts; } void setParts(Set parts) { this.parts = parts; } }
@Entity public class Part { ... }

Без описания какого-либо физического отображения (нет @JoinColumn или @JoinTable) используется однонаправленная один-ко-многим связь с таблицей соединений. Имя таблицы является конкатенацией имени таблицы владельца, _ и имени другой таблицы. Имя (имена) внешнего ключа, ссылающееся на таблицу владельца, представляет собой объединение таблицы владельца, _ и имени столбца первичного ключа владельца. Имя (имена) внешнего ключа, ссылающееся на другую сторону, представляет собой конкатенацию имени свойства владельца, _ и имени столбца первичного ключа другой стороны. Уникальное ограничение (unique constraint) добавляется к внешнему ключу, ссылающемуся на другую таблицу, чтобы отразить один-ко-многим.

Давайте посмотрим, как коллекции отображаются с использованием файлов отображения Hibernate. В этом случае первым шагом является выбор правильного элемента отображения. Это зависит от типа интерфейса. Например, элемент <set> используется для отображения свойств типа Set.

Пример 7.4. Отображение Set с использованием <set>

<class name="Product">
    <id name="serialNumber" column="productSerialNumber"/>
    <set name="parts">
        <key column="productSerialNumber" not-null="true"/>
        <one-to-many class="Part"/>
    </set>
</class>

В  «Примере 7.4. Отображение Set с использованием <set>» ассоциация один-ко-многим связывает сущности Product и Part. Эта ассоциация требует наличия столбца внешнего ключа и, возможно, столбца индекса в таблице Part. Это отображение теряет определённую семантику обычных коллекций Java:

Подойдя ближе к используемому тегу <one-to-many>, мы видим, что он имеет следующие параметры.

Пример 7.5. Опции элемента <one-to-many>

<one-to-many class="ClassName" (1)
                not-found="ignore|exception" (2)
                entity-name="EntityName" (3)
                node="element-name"
                embed-xml="true|false" />
class (обязательный): имя ассоциированного класса.
not-found (необязательный. По умолчанию exception): указывает, как будут обрабатываться кэшированные идентификаторы, ссылающиеся на отсутствующие записи в бд. ignore будет обрабатывать недостающую строку как ассоциацию null.
entity-name (необязательный): имя сущности ассоциированного класса как альтернатива class.

Элементу <one-to-many> не нужно объявлять какие-либо столбцы. Также не обязательно указывать имя table где-либо.

Внимание

Если столбец внешнего ключа ассоциации <one-to-many> объявлен NOT NULL, вы должны объявить <key> отображение not-null=«true» или использовать двунаправленную ассоциацию с отображением коллекции, отмеченным как inverse=«true». См. раздел «7.3.2 Двунаправленные ассоциации».

Помимо тега <set>, как показано в  «Примере 7.4. Отображение Set с использованием <set>» , также есть элементы отображения <list>, <map>, <bag>, <array> и <primitive-array>. Элемент <map> является репрезентативным:

Пример 7.6. Элементы отображения <map>

<map name="propertyName" (1)
        table="table_name" (2)
        schema="schema_name" (3)
        lazy="true|extra|false" (4)
        inverse="true|false" (5)
        cascade="all|none|save-update|delete|all-delete-orphan|(6)delete-orphan"
        sort="unsorted|natural|comparatorClass" (7)
        order-by="column_name asc|desc" (8)
        where="arbitrary sql where condition" (9)
        fetch="join|select|subselect" (10)
        batch-size="N" (11)
        access="field|property|ClassName" (12)
        optimistic-lock="true|false" (13)
        mutable="true|false" (14)
        node="element-name|."
        embed-xml="true|false">
    <key .... />
    <map-key .... />
    <element .... />
</map>
name имя свойства коллекции.
table (необязательный. По умолчанию имя свойства): имя таблицы коллекции. Оно не используется для ассоциаций «один-ко-многим».
schema (необязательный): имя схемы таблицы для переопределения схемы, объявленной в корневом элементе.
lazy (необязательный. По умолчанию имя true): отключает ленивую выборку и указывает, что ассоциация выбирается всегда. Его также можно использовать, чтобы включить «очень ленивую» выборку, когда большинство операций не инициализируют коллекцию. Это подходит для больших коллекций.
inverse (необязательный. По умолчанию имя false): отмечает эту коллекцию как «обратный» конец двунаправленной ассоциации.
cascade (необязательный. По умолчанию имя none): позволяет выполнять операции каскадом на дочерние сущности.
sort (необязательный): задает отсортированную коллекцию с естественным (natural) порядком сортировки или заданным классом компаратора.
order-by (необязательный): указывает столбец таблицы или столбцы, которые определяют порядок итерации Map, Set или bag вместе с необязательным ascили desc.
where (необязательный): задает произвольное условие SQL WHERE, которое используется при извлечении или удалении коллекции. Это полезно, если коллекция должна содержать только подмножество доступных данных.
fetch (необязательный. По умолчанию имя select): выбирает между outer-join выборкой (fetching), выборку путем последовательного select и выборкой путем последовательного subselect.
batch-size (необязательный. По умолчанию имя 1): определяет размер пакета для ленивой выборки экземпляров этой коллекции.
access (необязательный. По умолчанию имя property): стратегия Hibernate использует для доступа значение стоимоства коллекции.
optimistic-lock (необязательный. По умолчанию имя true): указывает, что изменения состояния коллекции приводят к приращениям версии для владеющей сущности. Для ассоциаций «один-ко-многим» вы можете захотеть отключить этот параметр.
mutable (необязательный. По умолчанию имя true): значение false указывает, что элементы коллекции никогда не изменяются. Это приводит к незначительной оптимизации производительности в некоторых случаях.

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

7.2.1. Сбор внешних ключей

На уровне базы данных коллекция экземпляров различается по внешнему ключу сущности, которому принадлежит коллекция. Этот внешний ключ называется столбцом ключа коллекции или столбцами таблицы коллекции. В столбце ключа коллекции отображается аннотация @JoinColumn, соответственно элемент XML <key>.

В столбце внешнего ключа может быть ограничение неопределенности (nullability constraint). Для большинства коллекций это подразумевается. Для однонаправленных ассоциаций «один-ко-многим» столбец внешнего ключа по умолчанию равен NULL, поэтому вам может потребоваться указать

@JoinColumn(nullable=false)

или

<key column="productSerialNumber" not-null="true"/>

Ограничение внешнего ключа может использовать ON DELETE CASCADE. В XML это можно выразить через:

<key column="productSerialNumber" on-delete="cascade"/>

В аннотациях необходимо использовать специальную Hibernate аннотацию @EnDelete.

@OnDelete(action=OnDeleteAction.CASCADE)

См. раздел «5.1.11.3 Ключ» для получения дополнительной информации о элементе <key>.

7.2.2. Индексированные коллекции

В следующих параграфах мы более подробно рассмотрим индексированные коллекции List и Map, как их индекс можно отобразить в Hibernate.

7.2.2.1. Списки (Lists)

Списки могут быть отображены двумя способами:

Чтобы упорядочить списки в памяти, добавьте @javax.persistence.OrderBy к вашему свойству. Эта аннотация принимает в качестве параметра список свойств, разделённых запятыми (целевой сущности), и соответственно упорядочивает коллекцию (например, firstname asc, age desc, weight asc nulls last), если строка пуста, коллекция будет упорядочена по первичному ключу целевой сущности.

Пример 7.7. Упорядоченные списки с использованием @OrderBy

@Entity
public class Customer {
   @Id @GeneratedValue public Integer getId() { return id; }
   public void setId(Integer id) { this.id = id; }
   private Integer id;
@OneToMany(mappedBy="customer") @OrderBy("number") public List getOrders() { return orders; } public void setOrders(List orders) { this.orders = orders; } private List orders; }
@Entity public class Order { @Id @GeneratedValue public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } private Integer id;
public String getNumber() { return number; } public void setNumber(String number) { this.number = number; } private String number;
@ManyToOne public Customer getCustomer() { return customer; } public void setCustomer(Customer customer) { this.customer = customer; } private Customer number; }
-- Table schema |-------------| |----------| | Order | | Customer | |-------------| |----------| | id | | id | | number | |----------| | customer_id | |-------------|

Чтобы сохранить значение индекса в выделенном столбце, используйте для вашего свойства аннотацию @javax.persistence.OrderColumn. В этих аннотациях описывается имя столбца и атрибуты столбца, поддерживающие значение индекса. Этот столбец размещается в таблице, содержащей ассоциацию внешнего ключа. Если имя столбца не указано, по умолчанию используется имя свойства по ссылке, за которым следует знак подчеркивания, а затем — ORDER (в следующем примере это будет order_ORDER).

Пример 7.8. Явный столбец индекса с использованием @OrderColumn

@Entity
public class Customer {
   @Id @GeneratedValue public Integer getId() { return id; }
   public void setId(Integer id) { this.id = id; }
   private Integer id;
@OneToMany(mappedBy="customer") @OrderColumn(name="orders_index") public List getOrders() { return orders; } public void setOrders(List orders) { this.orders = orders; } private List orders; }
@Entity public class Order { @Id @GeneratedValue public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } private Integer id;
public String getNumber() { return number; } public void setNumber(String number) { this.number = number; } private String number;
@ManyToOne public Customer getCustomer() { return customer; } public void setCustomer(Customer customer) { this.customer = customer; } private Customer number; }
-- Table schema |--------------| |----------| | Order | | Customer | |--------------| |----------| | id | | id | | number | |----------| | customer_id | | orders_index | |--------------|

Заметка

Мы рекомендуем вам конвертировать унаследованные (legacy) @org.hibernate.annotations.IndexColumn в стандарт JPA @javax.persistence.OrderColumn. Если вы используете пользовательский список основы индексов (возможно, в настоящее время используется атрибут org.hibernate.annotations.IndexColumn.literal), вы можете указать это, используя @org.hibernate.annotations.ListIndexBase в сочетании с @javax.persistence.OrderColumn. Основание по умолчанию равно 0, как в Java.

Глядя снова на эквивалентный файл отображения Hibernate, индекс массива или списка всегда имеет тип integer и отображается с помощью элемента <list-index>. Отображаемый столбец содержит последовательные целые числа, которые по умолчанию нумеруются от нуля.

Пример 7.9. Элемент index-list для индексированных коллекций в xml-отображении

<list-index column="column_name" (1)
            base="0|1|..." />
column (обязательный): имя столбца, содержащего значения индекса коллекции.
base (необязательный. По умолчанию 0): значение столбца индекса, соответствующего первому элементу списка или массива.

Если ваша таблица не имеет столбца индекса, и вы все еще хотите использовать List в качестве типа свойства, вы можете отобразить свойство как Hibernate <bag>. bag не сохраняет свой порядок, когда он извлекается из базы данных, но он может быть опционально отсортирован или упорядочен.

7.2.2.2. Карты (Maps)

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

Чтобы использовать одно из свойств целевой сущности в качестве ключа карты, используйте @MapKey(name=«myProperty»), где myProperty — это имя свойства в целевой сущности. При использовании @MapKey без атрибута name используется первичный ключ целевой сущности. Ключ карты использует тот же столбец, что и указанное свойство. Не существует дополнительного столбца, чтобы хранить ключ карты, потому что ключ карты представляет собой целевое свойство. Помните, что после загрузки ключ больше не синхронизируется с этим свойством. Другими словами, если вы измените значение свойства, ключ не изменится автоматически в вашей Java-модели.

Пример 7.10. Использование целевой сущности в качестве ключа карты через @MapKey

@Entity
public class Customer {
   @Id @GeneratedValue public Integer getId() { return id; }
   public void setId(Integer id) { this.id = id; }
   private Integer id;
@OneToMany(mappedBy="customer") @MapKey(name="number") public Map getOrders() { return orders; } public void setOrders(Map order) { this.orders = orders; } private Map orders; }
@Entity public class Order { @Id @GeneratedValue public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } private Integer id;
public String getNumber() { return number; } public void setNumber(String number) { this.number = number; } private String number;
@ManyToOne public Customer getCustomer() { return customer; } public void setCustomer(Customer customer) { this.customer = customer; } private Customer number; }
-- Table schema |-------------| |----------| | Order | | Customer | |-------------| |----------| | id | | id | | number | |----------| | customer_id | |-------------|

В качестве альтернативы ключ карты отображается на выделенный столбец или столбцы. Чтобы настроить отображение, используйте одну из следующих аннотаций:

Вы также можете использовать @MapKeyClass для определения типа ключа, если вы не используете generics.

Пример 7.11. Ключ карты как основной тип с использованием @MapKeyColumn

@Entity
public class Customer {
   @Id @GeneratedValue public Integer getId() { return id; }
   public void setId(Integer id) { this.id = id; }
   private Integer id;
@OneToMany @JoinTable(name="Cust_Order") @MapKeyColumn(name="orders_number") public Map getOrders() { return orders; } public void setOrders(Map orders) { this.orders = orders; } private Map orders; }
@Entity public class Order { @Id @GeneratedValue public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } private Integer id;
public String getNumber() { return number; } public void setNumber(String number) { this.number = number; } private String number;
@ManyToOne public Customer getCustomer() { return customer; } public void setCustomer(Customer customer) { this.customer = customer; } private Customer number; }
-- Table schema |-------------| |----------| |---------------| | Order | | Customer | | Cust_Order | |-------------| |----------| |---------------| | id | | id | | customer_id | | number | |----------| | order_id | | customer_id | | orders_number | |-------------| |---------------|

Заметка

Мы рекомендуем вам перейти с @org.hibernate.annotations.MapKey / @org.hibernate.annotation.MapKeyManyToMany на новый стандартный подход, описанный выше.

При использовании файлов отображения Hibernate существуют эквивалентные решения для описанных аннотаций. Вы должны использовать <map-key>, <map-key-many-to-many> и <composite-map-key>. <map-key> используется для любого базового типа, <map-key-many-to-many> для ссылки на сущность и <composite-map-key> для составного типа.

Пример 7.12. map-key xml-элемент отображения

<map-key column="column_name" (1)
            formula="any SQL expression" (2)
            type="type_name" (3)
            node="@attribute-name"
            length="N" />
column (необязательный): имя столбца, содержащего значения индекса коллекции.
formula (необязательный): формулу SQL, используемая для вычисления ключа карты.
type (обязательный): тип ключей карты.

Пример 7.13. map-key-many-to-many

<map-key-many-to-many column="column_name" (1)
            formula="any SQL expression" (2)(3)
            class="ClassName" />
column (необязательный): имя столбца внешнего ключа для значений индекса коллекции.
formula (необязательный): формулу SQL, используемая для вычисления внешнего ключа карты.
class (обязательный): класс сущности, используемый в качестве ключа карты.

7.2.3. Коллекции основных типов и встраиваемых объектов

В некоторых ситуациях вам не нужно связывать две сущности, просто создавайте коллекцию базовых типов или встраиваемых объектов. Используйте @ElementCollection для такого случая.

Пример 7.14. Коллекция базовых типов, отображаемых через @ElementCollection

            @Entity
public class User {
   [...]
   public String getLastname() { ...}
@ElementCollection @CollectionTable(name="Nicknames", joinColumns=@JoinColumn(name="user_id")) @Column(name="nickname") public Set getNicknames() { ... } }

Таблица коллекции, содержащая данные коллекции, устанавливается с помощью аннотации @CollectionTable. Если опущено имя таблицы коллекции, по умолчанию совпадает с конкатенацией имени содержащей сущнсоти и именb атрибута коллекции, разделенным знаком подчеркивания. В нашем примере это будут User_nicknames.

Столбец, содержащий основной тип, устанавливается с помощью аннотации @Column. Если опущено, имя столбца по умолчанию совпадает с именем свойства: в нашем примере это будут nicknames.

Но вы не ограничены основными типами, тип коллекции может быть любым встраиваемым объектом. Чтобы переопределить столбцы встроенного объекта в таблице коллекции, используйте аннотацию @AttributeOverride.

Пример 7.15. @ElementCollection для встраиваемых объектов

@Entity
public class User {
   [...]
   public String getLastname() { ...}
@ElementCollection @CollectionTable(name="Addresses", joinColumns=@JoinColumn(name="user_id")) @AttributeOverrides({ @AttributeOverride(name="street1", column=@Column(name="fld_street")) }) public Set<Address> getAddresses() { ... } }
@Embeddable public class Address { public String getStreet1() {...} [...] }

Такой встроенный объект не может содержать сам набор.

Заметка

В @AttributeOverride вы должны использовать префикс value. для переопределения свойств встраиваемого объекта, используемого в значении карты и префикс key. для переопределения свойств встраиваемого объекта, используемого в ключе карты.

@Entity
public class User {
   @ElementCollection
   @AttributeOverrides({
      @AttributeOverride(name="key.street1", column=@Column(name="fld_street")),
      @AttributeOverride(name="value.stars", column=@Column(name="fld_note"))
   })
   public Map getFavHomes() { ... }

Заметка

Мы рекомендуем вам перейти от @org.hibernate.annotations.CollectionOfElements к новой аннотации @ElementCollection.

Используя подход с файлом отображения, коллекция значений отображается с использованием тега <element>. Например:

Пример 7.16. Тег <element> для значений коллекции с использованием файлов отображения

<element column="column_name" (1)
            formula="any SQL expression" (2)
            type="typename" (3)
            length="L"
            precision="P"
            scale="S"
            not-null="true|false"
            unique="true|false"
            node="element-name" />
column (необязательный): имя столбца, содержащего значения элемента коллекции.
formula (необязательный): формулу SQL, используемая для вычисления элемента.
type (обязательный): тип элемента коллекции.

7.3. Расширенные отображения коллекции

7.3.1. Отсортированные коллекции

Hibernate поддерживает коллекции, реализующие java.util.SortedMap и java.util.SortedSet. С помощью аннотаций вы объявляете компаратор (comparator), используя @Sort. Вы выбрали между типами компараторов: без сортировки (unsorted), естестенная (natural) или пользовательская (custom). Если вы хотите использовать собственную реализацию компаратора, вам также нужно будет указать класс реализации, используя атрибут comparator. Обратите внимание, что вам нужно использовать интерфейс SortedSet или SortedMap.

Пример 7.17. Отсортированная коллекция с @Sort

@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
@JoinColumn(name="CUST_ID")
@Sort(type = SortType.COMPARATOR, comparator = TicketComparator.class)
public SortedSet getTickets() {
    return tickets;
}

Используя файлы отображения Hibernate, вы указываете компаратор в файле сопоставления с <sort>:

Пример 7.18. Отсортированная коллекция с использованием xml отображения

<set name="aliases"
            table="person_aliases" 
            sort="natural">
    <key column="person"/>
    <element column="name" type="string"/>
</set>
<map name="holidays" sort="my.custom.HolidayComparator"> <key column="year_id"/> <map-key column="hol_name" type="string"/> <element column="hol_date" type="date"/> </map>

Допустимые значения атрибута sort являются unsorted, natural и именем класса, реализующего java.util.Comparator.

Совет

Сортированные коллекции фактически ведут себя как java.util.TreeSet или java.util.TreeMap.

Если вы хотите, чтобы сама база данных упорядочивала элементы коллекции, используйте атрибут order-by для отображений set, bag или map. Это решение реализовано с использованием LinkedHashSet или LinkedHashMap и выполняет упорядочение в SQL-запросе, а не в памяти.

Пример 7.19. Сортировка в базе данных с использованием order-by

<set name="aliases" table="person_aliases" order-by="lower(name) asc">
    <key column="person"/>
    <element column="name" type="string"/>
</set>
<map name="holidays" order-by="hol_date, hol_name"> <key column="year_id"/> <map-key column="hol_name" type="string"/> <element column="hol_date type="date"/> </map>

Заметка

Значение атрибута order-by — это упорядочение SQL, а не HQL-упорядочение.

Ассоциации даже могут быть сортированы по произвольным критериям во время выполнения с использованием filter():

Пример 7.20. Сортировка через фильтр запроса

sortedUsers = s.createFilter( group.getUsers(), "order by this.name" ).list();

7.3.2. Двунаправленные ассоциации

Двунаправленная ассоциация позволяет осуществлять навигацию с обоих «концов» ассоциации. Поддерживаются два типа двунаправленной связи:

one-to-many
set или bag, обозначенный (valued) на одном «конце», и однозначно обозначенный (single-valued) на другом
many-to-many
set или bag, обозначенный (valued) на обоих «концах»

Часто существует ассоциация многие-к-одному, которая является стороной владельца двунаправленных отношений. Соответствующая ассоциация один-ко-многим в этом случае аннотируется @OneToMany(mappedBy=...)

Пример 7.21. Двунаправленная «один-ко-многим» с «многие-к-одному» как владелецем ассоциации

            @Entity
public class Troop {
    @OneToMany(mappedBy="troop")
    public Set getSoldiers() {
    ...
}
@Entity public class Soldier { @ManyToOne @JoinColumn(name="troop_fk") public Troop getTroop() { ... }

Troop имеет двунаправленную связь «один-ко-многим» с солдатами с солдатом через скойство troop. Вы не должны определять какое-либо физическое отображение на стороне mappedBy.

Чтобы отобразить двунаправленную «один-ко-многим» со стороной «один-ко-многим» в качестве стороны-владельца, вы должны удалить элемент mappedBy и установить для «многие-к-одному» аннотацию @JoinColumn со значением false для вставки и обновления данных. Это решение не оптимизировано и будет производить дополнительные запрос UPDATE.

Пример 7.22. Двунаправленная ассоциация со стороной «многие-к-одному» как владелецем

@Entity
public class Troop {
    @OneToMany
    @JoinColumn(name="troop_fk") //we need to duplicate the physical information
    public Set getSoldiers() {
    ...
}
@Entity public class Soldier { @ManyToOne @JoinColumn(name="troop_fk", insertable=false, updatable=false) public Troop getTroop() { ... }

Как выглядит двунаправленного отображения в Hibernate xml-файле? Там вы определяете двунаправленную ассоциацию «один-ко-многим», отображая ассоциацию «один-ко-многим» на те же столбцы в таблице что и «многие-к-одному» и объявляя многозначно обозначенный (many-valued) «конец» inverse=«true».

Пример 7.23. Двунаправленная ассоциация «один-ко-многим» через файл отображения Hibernate

<class name="Parent">
    <id name="id" column="parent_id"/>
    ....
    <set name="children" inverse="true">
        <key column="parent_id"/>
        <one-to-many class="Child"/>
    </set>
</class>
<class name="Child"> <id name="id" column="child_id"/> .... <many-to-one name="parent" class="Parent" column="parent_id" not-null="true"/> </class>

Сопоставление одного конца ассоциации с inverse=«true» не влияет на работу каскадов, поскольку это ортогональные понятия.

Связь «многие-ко-многим» определяется логически с помощью аннотации @ManyToMany. Вы также должны описать таблицу ассоциации и условия соединения с помощью аннотации @JoinTable. Если ассоциация двунаправленная, одна сторона должна быть владельцем, а одна сторона должна быть обратным концом (т. е. она будет игнорироваться при обновлении значений отношений в ассоциированной таблице):

Пример 7.24. Ассоциация «многие-ко-многим» через @ManyToMany

@Entity
public class Employer implements Serializable {
    @ManyToMany(
        targetEntity=org.hibernate.test.metadata.manytomany.Employee.class,
        cascade={CascadeType.PERSIST, CascadeType.MERGE}
    )
    @JoinTable(
        name="EMPLOYER_EMPLOYEE",
        joinColumns=@JoinColumn(name="EMPER_ID"),
        inverseJoinColumns=@JoinColumn(name="EMPEE_ID")
    )
    public Collection getEmployees() {
        return employees;
    }
    ...
}
@Entity
public class Employee implements Serializable {
    @ManyToMany(
        cascade = {CascadeType.PERSIST, CascadeType.MERGE},
        mappedBy = "employees",
        targetEntity = Employer.class
    )
    public Collection getEmployers() {
        return employers;
    }
} 

В этом примере @JoinTable определяет name, массив столбцов соединения (join) и массив столбцов обратного соединения (inverse join). Последние являются столбцами таблицы ассоциаций, которые относятся к первичному ключу Employee («другая сторона»). Как видно ранее, другая сторона не должна описывать физическое сопоставление: простой аргумент mappedBy, содержащий имя свойства владельца, связывает оба.

Как и любые другие аннотации, большинство ценностей угадывается во многих отношениях. Без описания какого-либо физического отображения в однонаправленном «многие-ко-многим» применяли следующие правила. Имя таблицы является конкатенацией имени таблицы владельца, _ и имени таблицы другой стороны. Имя (имена) внешнего ключа, ссылающееся на таблицу владельца, представляет собой объединение имени таблицы владельца и столбца первичного ключа владельца. Имя (имена) внешнего ключа, ссылающееся на другую сторону, представляет собой конкатенацию имени свойства владельца, _ и столбца (-ов) первичного ключа другой стороны. Это те же правила, что и для однонаправленных отношений «один-ко-многим».

Пример 7.25. Значения по умолчанию для @ManyToMany (однонаправленное)

@Entity
public class Store {
    @ManyToMany(cascade = CascadeType.PERSIST)
    public Set getImplantedIn() {
        ...
    }
}
@Entity public class City { ... //no bidirectional relationship }

В качестве таблицы соединений используется Store_City. Столбец Store_id является внешним ключом в таблице Store. Столбец implantedIn_id является внешним ключом таблицы City.

Без описания какого-либо физического отображения в двунаправленном варианте «многие-ко-многим» применяли следующие правила. Имя таблицы является конкатенацией имени таблицы владельца, _ и имени таблицы другой стороны. Имя (имена) внешнего ключа, ссылающееся на таблицу владельца, представляет собой конкатенацию имени другой стороны, _ и столбца первичного ключа владельца. Имя (имена) внешнего ключа, ссылающееся на другую сторону, представляет собой конкатенацию имени свойства владельца, _ и столбца (-ов) первичного ключа другой стороны. Это те же правила, что и для однонаправленных отношений «один-ко-многим».

Пример 7.26. Значения по умолчанию для @ManyToMany (двунаправленное)

@Entity
public class Store {
    @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    public Set getCustomers() {
        ...
    }
}
@Entity public class Customer { @ManyToMany(mappedBy="customers") public Set getStores() { ... } }

В качестве таблицы соединений используется Store_Customer. Столбец stores_id является внешним ключом в таблице Store. Столбец customer_id является внешним ключом таблицы Customer.

Используя xml-файлы Hibernate, вы можете отобразить двунаправленную связь «многие-ко-многим», сопоставляя две ассоциации «многие-ко-многим» с одной и той же таблицей базы данных и объявляя один конец как обратный (inverse).

Заметка

Вы не можете выбрать индексированную коллекцию.

«Пример 7.27 Ассоциация многие-ко-многим с использованием файлов сопоставления Hibernate» показывает двунаправленную связь «многие-ко-многим», которая иллюстрирует, как каждая категория может иметь много элементов, а каждый элемент может быть во многих категориях:

Пример 7.27. Ассоциация «многие-ко-многим» с использованием файлов сопоставления Hibernate

<class name="Category">
    <id name="id" column="CATEGORY_ID"/>
    ...
    <bag name="items" table="CATEGORY_ITEM">
        <key column="CATEGORY_ID"/>
        <many-to-many class="Item" column="ITEM_ID"/>
    </bag>
</class>
<class name="Item"> <id name="id" column="ITEM_ID"/> ...
<!-- inverse end --> <bag name="categories" table="CATEGORY_ITEM" inverse="true"> <key column="ITEM_ID"/> <many-to-many class="Category" column="CATEGORY_ID"/> </bag> </class>

Изменения, внесенные только в обратный конец ассоциации, не сохраняются (not persisted). Это означает, что Hibernate имеет два представления в памяти для каждой двунаправленной ассоциации: одну ссылку от A до B и другую ссылку от B до A. Это проще понять, если вы думаете о объектной модели Java и о том, как отношение «многие-ко-многим» создаются в Java:

Пример 7.28. Эффект инвертированной (inverse) против неинвертированной (non-inverse) стороны ассоциации «многие-ко-многим»

category.getItems().add(item);      // Категория теперь «знает» об отношении
item.getCategories().add(category); // Элемент теперь «знает» об отношении
session.persist(item);              // Отношения не будут сохранены!
session.persist(category);          // Отношения будет сохранены

Неинвертированная (non-inverse) сторона используется для сохранения представления в памяти (in-memory) в базу данных.

7.3.3. Двунаправленные ассоциации с индексированными коллекциями

Есть несколько дополнительных соображений для двунаправленных сопоставлений с индексированными коллекциями (где один конец представлен как <list> или <map>) при использовании файлов отображения Hibernate. Если есть свойство дочернего класса, который отображается на столбец индекса, вы можете использовать inverse=«true» для отображения коллекции:

Пример 7.29. Двунаправленная ассоциация с индексированной коллекцией

<class name="Parent">
    <id name="id" column="parent_id"/>
    ....
    <map name="children" inverse="true">
        <key column="parent_id"/>
        <map-key column="name" 
            type="string"/>
        <one-to-many class="Child"/>
    </map>
</class>
<class name="Child"> <id name="id" column="child_id"/> .... <property name="name" not-null="true"/> <many-to-one name="parent" class="Parent" column="parent_id" not-null="true"/> </class>

Если в дочернем классе такого свойства нет, ассоциация не может считаться действительно двунаправленной. То есть на одном конце ассоциации имеется информация, которая недоступна на другом конце. В этом случае вы не можете отобразить коллекцию inverse=«true». Вместо этого вы можете использовать следующее сопоставление:

Пример 7.30. Двунаправленная ассоциация с индексированной коллекцией, но без столбца индекса

<class name="Parent">
    <id name="id" column="parent_id"/>
    ....
    <map name="children">
        <key column="parent_id"
            not-null="true"/>
        <map-key column="name" 
            type="string"/>
        <one-to-many class="Child"/>
    </map>
</class>
<class name="Child"> <id name="id" column="child_id"/> .... <many-to-one name="parent" class="Parent" column="parent_id" insert="false" update="false" not-null="true"/> </class>

Обратите внимание, что в этом отображении конец, связанный с коллекцией, отвечает за обновления внешнего ключа.

7.3.4. Тройные ассоциации

Существует три возможных подхода к отображению тройной ассоциации. Один из подходов — использовать Map с ассоциацией в качестве ее индекса:

Пример 7.31. Отображение тройный ассоциации

@Entity
public class Company {
   @Id 
   int id;
   ...
   @OneToMany // unidirectional
   @MapKeyJoinColumn(name="employee_id")
   Map<Employee, Contract> contracts;
}
// или
<map name="contracts"> <key column="employer_id" not-null="true"/> <map-key-many-to-many column="employee_id" class="Employee"/> <one-to-many class="Contract"/> </map>

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

7.3.5. Использование <idbag>

Большинство ассоциаций «многие-ко-многим» и коллекции значений, показанных ранее, отображаются на таблицы с составными ключами, хотя было высказано предположение, что сущности должны иметь синтетические идентификаторы (суррогатные ключи). Таблица чистой ассоциации, похоже, мало чем отличается от суррогатного ключа, хотя коллекция составных значений может отличаться. По этой причине Hibernate предоставляет функцию, которая позволяет отображать ассоциации «многие-ко-многим» и коллекции значений на таблицы с суррогатным ключом.

Элемент <idbag> позволяет вам отображать List (или Collection) с семантикой bag. Например:

<idbag name="lovers" table="LOVERS">
    <collection-id column="ID" type="long">
        <generator class="sequence"/>
    </collection-id>
    <key column="PERSON1"/>
    <many-to-many column="PERSON2" class="Person" fetch="join"/>
</idbag>

<idbag> имеет синтетический генератор id, как и класс сущности. Каждой строке коллекции присваивается разный суррогатный ключ. Однако Hibernate не предоставляет никакого механизма для обнаружения суррогатного значения ключа для определенной строки.

Эффективность обновления <idbag> заменяет обычный <bag>. Hibernate может эффективно находить отдельные строки и обновлять их или удалять их отдельно, подобно list, map или set.

В текущей реализации стратегия генерации идентификатора native не поддерживается для идентификаторов коллекции <idbag>.

7.4. Примеры коллекций

В этом разделе рассматриваются примеры коллекций.

Следующий класс имеет коллекцию экземпляров Child:

Пример 7.32. Примеры классов Parent и Child

public class Parent {
    private long id;
    private Set<Child> children;
// getter/setter ... }
public class Child { private long id; private String name;
// getter/setter ... }

Если каждый Child имеет, самое большее, одного Parent, наиболее естественным отображением является ассоциация «один-ко-многим»:

Пример 7.33. Однонаправленное «один-ко-многим» отношение Parent-Child с использованием аннотаций

public class Parent {
    @Id
    @GeneratedValue
    private long id;
@OneToMany private Set<Child> children;
// getter/setter ... }
public class Child { @Id @GeneratedValue private long id; private String name;
// getter/setter ... }

Пример 7.34. Однонаправленное «один-ко-многим» отношение Parent-Child с использованием фалов отображений

<hibernate-mapping>
<class name="Parent"> <id name="id"> <generator class="sequence"/> </id> <set name="children"> <key column="parent_id"/> <one-to-many class="Child"/> </set> </class>
<class name="Child"> <id name="id"> <generator class="sequence"/> </id> <property name="name"/> </class>
</hibernate-mapping>

Это отображает следующие определения таблиц:

Пример 7.35. Определения таблиц для однонаправленного отображения Parent-Child

create table parent ( id bigint not null primary key )
create table child ( id bigint not null primary key, name varchar(255), parent_id bigint )
alter table child add constraint childfk0 (parent_id) references parent

Если родительский элемент обязателен, используйте двунаправленную связь «один-ко-многим»:

Пример 7.36. Двунаправленное «один-ко-многим» отношение Parent-Child с использованием аннотаций

public class Parent {
    @Id
    @GeneratedValue
    private long id;
@OneToMany(mappedBy="parent") private Set<Child> children;
// getter/setter ... }
public class Child { @Id @GeneratedValue private long id;
private String name;
@ManyToOne private Parent parent;
// getter/setter ... }

Пример 7.37. Двунаправленное «один-ко-многим» отношение Parent-Child с использованием фалов отображений

<hibernate-mapping>
<class name="Parent"> <id name="id"> <generator class="sequence"/> </id> <set name="children" inverse="true"> <key column="parent_id"/> <one-to-many class="Child"/> </set> </class>
<class name="Child"> <id name="id"> <generator class="sequence"/> </id> <property name="name"/> <many-to-one name="parent" class="Parent" column="parent_id" not-null="true"/> </class>
</hibernate-mapping>

Обратите внимание на ограничение NOT NULL:

Пример 7.38. Определения таблиц для двунаправленного отображения Parent-Child

create table parent ( id bigint not null primary key )
create table child ( id bigint not null
                     primary key,
                     name varchar(255),
                     parent_id bigint not null )
alter table child add constraint childfk0 (parent_id) references parent

В качестве альтернативы, если эта связь должна быть однонаправленной, вы можете применить ограничение NOT NULL.

Пример 7.39. Принудительное ограничение NOT NULL в однонаправленном отношении с использованием аннотаций

public class Parent {
    @Id
    @GeneratedValue
    private long id;
@OneToMany(optional=false) private Set<Child> children;
// getter/setter ... }
public class Child { @Id @GeneratedValue private long id; private String name;
// getter/setter ... }

Пример 7.40. Принудительное ограничение NOT NULL в однонаправленном отношении с использованием фалов отображений

<hibernate-mapping>
<class name="Parent"> <id name="id"> <generator class="sequence"/> </id> <set name="children"> <key column="parent_id" not-null="true"/> <one-to-many class="Child"/> </set> </class>
<class name="Child"> <id name="id"> <generator class="sequence"/> </id> <property name="name"/> </class>
</hibernate-mapping>

С другой стороны, если у Child есть несколько Parent, ассоциация «многие-ко-многим» подходит.

Пример 7.41. «многие-ко-многим» отношение Parent-Child с использованием аннотаций

public class Parent {
    @Id
    @GeneratedValue
    private long id;
@ManyToMany private Set<Child> children;
// getter/setter ... }
public class Child { @Id @GeneratedValue private long id;
private String name;
// getter/setter ... }

Пример 7.42. «многие-ко-многим» отношение Parent-Child с использованием файлов отображений

<hibernate-mapping>
<class name="Parent"> <id name="id"> <generator class="sequence"/> </id> <set name="children" table="childset"> <key column="parent_id"/> <many-to-many class="Child" column="child_id"/> </set> </class>
<class name="Child"> <id name="id"> <generator class="sequence"/> </id> <property name="name"/> </class>
</hibernate-mapping>

Определения таблиц:

Пример 7.43. Определения таблиц для отношения «многие-ко-многим»

create table parent ( id bigint not null primary key )
create table child ( id bigint not null primary key, name varchar(255) )
create table childset ( parent_id bigint not null,
                        child_id bigint not null,
                        primary key ( parent_id, child_id ) )
alter table childset add constraint childsetfk0 (parent_id) references parent
alter table childset add constraint childsetfk1 (child_id) references child

Дополнительные примеры и полное объяснение отображения отношений parent/child см. в Главе 23 «Пример: Parent/Child». Еще более сложные отображения ассоциаций рассматриваются в следующей главе.