Глава 8. Ассоциативные отображения
Оглавление- 8.1. Введение
-
8.2. Однонаправленные ассоциации
- 8.2.1. Многие-к-одному
- 8.2.2. Один-к-одному
- 8.2.3. Один-ко-многим
-
8.3. Однонаправленные ассоциации с присоединением таблиц
- 8.3.1. Один-ко-многим
- 8.3.2. Многие-к-одному
- 8.3.3. Один-к-одному
- 8.3.4. Многие-ко-многим
-
8.4. Двунаправленные ассоциации
- 8.4.1. Один-ко-многим / Многие-к-одному
- 8.4.2. Один-к-одному
-
8.5. Двунаправленные ассоциации с присоединением таблиц
- 8.5.1. Один-ко-многим / Многие-к-одному
- 8.5.2. Один-к-одному
- 8.5.3. Многие-ко-многим
- 8.6. Более сложные ассоциативные отображения
8.1. Введение
Ассоциативные отображения часто являются наиболее сложными в плане корректной реализации.
В этом разделе мы рассмотрим несколько канонических случаев один за одном,
начиная с однонаправленных отображений, а затем двунаправленных. Мы будем использовать
Person
и Address
во всех примерах.
Ассоциации будут классифицированы по множественности и, в зависимости от этого, будут отображены в промежуточной таблицей.
Обнуляемые (nullable) внешние ключи не считаются хорошей практикой в традиционном моделировании данных, поэтому наши примеры не используют обнуляемые (nullable) внешние ключи. Это не является обязательным условием Hibernate, и Отображения будут работать, если вы снимете ограничения по null.
8.2. Однонаправленные ассоциации
8.2.1. Многие-к-одному
Однонаправленная ассоциация «многие-к-одному» является наиболее распространённым видом однонаправленной связи.
<class name="Person"> <id name="id" column="personId"> <generator class="native"/> </id> <many-to-one name="address" column="addressId" not-null="true"/> </class>
<class name="Address"> <id name="id" column="addressId"> <generator class="native"/> </id> </class>
create table Person ( personId bigint not null primary key, addressId bigint not null ) create table Address ( addressId bigint not null primary key )
8.2.2. Один-к-одному
Однонаправленная ассоциация «один-к-одному» по внешнему ключу почти идентична. Единственное отличие — ограничение unique столбца.
<class name="Person"> <id name="id" column="personId"> <generator class="native"/> </id> <many-to-one name="address" column="addressId" unique="true" not-null="true"/> </class>
<class name="Address"> <id name="id" column="addressId"> <generator class="native"/> </id> </class>
create table Person ( personId bigint not null primary key, addressId bigint not null unique ) create table Address ( addressId bigint not null primary key )
Однонаправленная ассоциация «один-к-одному» по первичному ключу обычно использует специальный генератор идентификаторов. В этом примере, однако, мы изменили направление ассоциации:
<class name="Person"> <id name="id" column="personId"> <generator class="native"/> </id> </class>
<class name="Address"> <id name="id" column="personId"> <generator class="foreign"> <param name="property">person</param> </generator> </id> <one-to-one name="person" constrained="true"/> </class>
create table Person ( personId bigint not null primary key ) create table Address ( personId bigint not null primary key )
8.2.3. Один-ко-многим
Однонаправленная ассоциация «один-ко-многим» по внешнему ключу является необычным случаем и не рекомендуется.
<class name="Person"> <id name="id" column="personId"> <generator class="native"/> </id> <set name="addresses"> <key column="personId" not-null="true"/> <one-to-many class="Address"/> </set> </class>
<class name="Address"> <id name="id" column="addressId"> <generator class="native"/> </id> </class>
create table Person ( personId bigint not null primary key ) create table Address ( addressId bigint not null primary key, personId bigint not null )
Вместо этого вы должны использовать соединение (join) таблиц для такой ассоциации.
8.3. Однонаправленные ассоциации с присоединением таблиц
8.3.1. Один-ко-многим
Однонаправленная ассоциация «один-ко-многим» с присоединением таблицы является
предпочтительным. Указание unique=«true»
изменяет множественность с
«многие-ко-многим» на «один-ко-многим».
<class name="Person"> <id name="id" column="personId"> <generator class="native"/> </id> <set name="addresses" table="PersonAddress"> <key column="personId"/> <many-to-many column="addressId" unique="true" class="Address"/> </set> </class>
<class name="Address"> <id name="id" column="addressId"> <generator class="native"/> </id> </class>
create table Person ( personId bigint not null primary key ) create table PersonAddress ( personId bigint not null, addressId bigint not null primary key ) create table Address ( addressId bigint not null primary key )
8.3.2. Многие-к-одному
Однонаправленная ассоциация «многие-ко-одному» с присоединением таблицы является общей, когда ассоциация необязательна. Например:
<class name="Person"> <id name="id" column="personId"> <generator class="native"/> </id> <join table="PersonAddress" optional="true"> <key column="personId" unique="true"/> <many-to-one name="address" column="addressId" not-null="true"/> </join> </class>
<class name="Address"> <id name="id" column="addressId"> <generator class="native"/> </id> </class>
create table Person ( personId bigint not null primary key ) create table PersonAddress ( personId bigint not null primary key, addressId bigint not null ) create table Address ( addressId bigint not null primary key )
8.3.3. Один-к-одному
Однонаправленная ассоциация «один-к-одному» с присоединением таблицы возможна, но чрезвычайно необычна.<class name="Person"> <id name="id" column="personId"> <generator class="native"/> </id> <join table="PersonAddress" optional="true"> <key column="personId" unique="true"/> <many-to-one name="address" column="addressId" not-null="true" unique="true"/> </join> </class>
<class name="Address"> <id name="id" column="addressId"> <generator class="native"/> </id> </class>
create table Person ( personId bigint not null primary key ) create table PersonAddress ( personId bigint not null primary key, addressId bigint not null unique ) create table Address ( addressId bigint not null primary key )
8.3.4. Многие-ко-многим
Наконец, вот пример однонаправленной ассоциации «многие-ко-многим».
<class name="Person"> <id name="id" column="personId"> <generator class="native"/> </id> <set name="addresses" table="PersonAddress"> <key column="personId"/> <many-to-many column="addressId" class="Address"/> </set> </class>
<class name="Address"> <id name="id" column="addressId"> <generator class="native"/> </id> </class>
create table Person ( personId bigint not null primary key ) create table PersonAddress ( personId bigint not null, addressId bigint not null, primary key (personId, addressId) ) create table Address ( addressId bigint not null primary key )
8.4. Двунаправленные ассоциации
8.4.1. Один-ко-многим / Многие-к-одному
Двунаправленная ассоциация «многие-к-одному» является наиболее распространенным видом ассоциации. Следующий пример иллюстрирует стандартное отношение родитель/ребёнок.
<class name="Person"> <id name="id" column="personId"> <generator class="native"/> </id> <many-to-one name="address" column="addressId" not-null="true"/> </class>
<class name="Address"> <id name="id" column="addressId"> <generator class="native"/> </id> <set name="people" inverse="true"> <key column="addressId"/> <one-to-many class="Person"/> </set> </class>
create table Person ( personId bigint not null primary key, addressId bigint not null ) create table Address ( addressId bigint not null primary key )
Если вы используете List
или другую индексированную коллекцию, установите для столбца
внешнего ключа значение not null
. Hibernate будет управлять ассоциацией со стороны коллекций,
чтобы поддерживать индекс каждого элемента, делая другую сторону практически обратной, установив
update=«false»
и insert=«false»
:
<class name="Person"> <id name="id"/> ... <many-to-one name="address" column="addressId" not-null="true" insert="false" update="false"/> </class>
<class name="Address"> <id name="id"/> ... <list name="people"> <key column="addressId" not-null="true"/> <list-index column="peopleIdx"/> <one-to-many class="Person"/> </list> </class>
Если низлежащий столбец внешнего ключа NOT NULL
, важно, чтобы вы определяли
not-null=«true»
в элементе <key> отображения коллекции.
Не объявляйте только not-null=«true»
на возможном вложенном элементе
<column>, но и на элементе <key>.
8.4.2. Один-к-одному
Двунаправленная ассоциация «один-к-одному» по внешнему ключу является распространённой:
<class name="Person"> <id name="id" column="personId"> <generator class="native"/> </id> <many-to-one name="address" column="addressId" unique="true" not-null="true"/> </class>
<class name="Address"> <id name="id" column="addressId"> <generator class="native"/> </id> <one-to-one name="person" property-ref="address"/> </class>
create table Person ( personId bigint not null primary key, addressId bigint not null unique ) create table Address ( addressId bigint not null primary key )
Двунаправленная ассоциация «один-к-одному» по первичному ключу использует специальный генератор id:
<class name="Person"> <id name="id" column="personId"> <generator class="native"/> </id> <one-to-one name="address"/> </class>
<class name="Address"> <id name="id" column="personId"> <generator class="foreign"> <param name="property">person</param> </generator> </id> <one-to-one name="person" constrained="true"/> </class>
create table Person ( personId bigint not null primary key ) create table Address ( personId bigint not null primary key )
8.5. Двунаправленные ассоциации с присоединением таблиц
8.5.1. Один-ко-многим / Многие-к-одному
Ниже приведён пример
двунаправленной ассоциации «один-ко-многим»
с присоединением таблицы
. Обратное inverse=«true»
может идти
в любом конце ассоциации, в коллекции или в соединении.
<class name="Person"> <id name="id" column="personId"> <generator class="native"/> </id> <set name="addresses" table="PersonAddress"> <key column="personId"/> <many-to-many column="addressId" unique="true" class="Address"/> </set> </class>
<class name="Address"> <id name="id" column="addressId"> <generator class="native"/> </id> <join table="PersonAddress" inverse="true" optional="true"> <key column="addressId"/> <many-to-one name="person" column="personId" not-null="true"/> </join> </class>
create table Person ( personId bigint not null primary key ) create table PersonAddress ( personId bigint not null, addressId bigint not null primary key ) create table Address ( addressId bigint not null primary key )
8.5.2. Один-к-одному
Двунаправленная ассоциация «один-к-одному» с присоединением таблицы возможна, но чрезвычайно необычна.
<class name="Person"> <id name="id" column="personId"> <generator class="native"/> </id> <join table="PersonAddress" optional="true"> <key column="personId" unique="true"/> <many-to-one name="address" column="addressId" not-null="true" unique="true"/> </join> </class>
<class name="Address"> <id name="id" column="addressId"> <generator class="native"/> </id> <join table="PersonAddress" optional="true" inverse="true"> <key column="addressId" unique="true"/> <many-to-one name="person" column="personId" not-null="true" unique="true"/> </join> </class>
create table Person ( personId bigint not null primary key ) create table PersonAddress ( personId bigint not null primary key, addressId bigint not null unique ) create table Address ( addressId bigint not null primary key )
8.5.3. Многие-ко-многим
Ниже приведён пример двунаправленной ассоциации «многие-ко-многим».
<class name="Person"> <id name="id" column="personId"> <generator class="native"/> </id> <set name="addresses" table="PersonAddress"> <key column="personId"/> <many-to-many column="addressId" class="Address"/> </set> </class>
<class name="Address"> <id name="id" column="addressId"> <generator class="native"/> </id> <set name="people" inverse="true" table="PersonAddress"> <key column="addressId"/> <many-to-many column="personId" class="Person"/> </set> </class>
create table Person ( personId bigint not null primary key ) create table PersonAddress ( personId bigint not null, addressId bigint not null, primary key (personId, addressId) ) create table Address ( addressId bigint not null primary key )
8.6. Более сложные ассоциативные отображения
Более сложные объединения (joins) ассоциаций крайне редки. Hibernate обрабатывает более сложные ситуации,
используя фрагменты SQL, встроенные в документ отображения. Например, если таблица с историческими
данными учётной записи определяет AccountNumber
, effectiveEndDate
и effectiveStartDatecolumns
, она будет отображаться следующим образом:
<properties name="currentAccountKey"> <property name="accountNumber" type="string" not-null="true"/> <property name="currentAccount" type="boolean"> <formula>case when effectiveEndDate is null then 1 else 0 end</formula> </property> </properties> <property name="effectiveEndDate" type="date"/> <property name="effectiveStateDate" type="date" not-null="true"/>
Затем вы можете отобразить ассоциацию на текущий экземпляр, с нулевым (null)
effectiveEndDate
, используя:
<many-to-one name="currentAccountInfo" property-ref="currentAccountKey" class="AccountInfo"> <column name="accountNumber"/> <formula>'1'</formula> </many-to-one>
В более сложном примере представьте, что ассоциация между Employee
и Organization
поддерживается в таблице Employment
, полной исторических
данных о занятости. Ассоциация с самым последним работодателем сотрудника,
с последним startDate
, может быть отображена следующим образом:
<join> <key column="employeeId"/> <subselect> select employeeId, orgId from Employments group by orgId having startDate = max(startDate) </subselect> <many-to-one name="mostRecentEmployer" class="Organization" column="orgId"/> </join>
Эта функциональность обеспечивает определенную степень креативности и гибкости, но более практично обращаться с такими случаями с использованием HQL или запроса критериев (criteria query).