第7章 関連マッピング

7.1. イントロダクション

関連マッピングはしばしば理解が最も難しいものになります。 この章では、基本的な一つ一つのケースについて述べます。 単方向のマッピングから始め、それから双方向のケースについて考えていきます。 例として、PersonAddress を用います。

関連は、結合テーブルを入れるかかどうかと、 多重度によって分類することにします。

すべての例でnot nullの外部キーを使用します。 これはHibernateの要件ではありません。 not null制約を外したとしても、マッピングは問題なく動作します。

7.2. 単方向関連

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

7.2.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>
</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>
</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 )
        

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

このような関連のために結合テーブルを使うことをお薦めします。

7.3. 結合テーブルを使った単方向関連

7.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 not null, addressId bigint not null primary key )
create table Address ( addressId bigint not null primary key )
        

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

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

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

7.4. 双方向関連

7.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 (または他のインデックス付きのコレクション)を使うなら、 外部キーの key カラムを not null に設定し、 コレクション側が各要素のインデックスをメンテナンスするように、 関連を扱う必要があります (update="false" かつ insert="false" と設定して、反対側を仮想的にinverseにします):

<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 であるならば、 コレクションマッピングの <key> 要素を not-null="true" にすることは重要です。 入れ子になった <column> 要素だけではなく、 <key> 要素も not-null="true" と定義しないようにしてください。

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

7.5. 結合テーブルを使った双方向関連

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

7.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="address"
            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 )
        

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

7.6. より複雑な関連マッピング

より複雑な関連結合は 極めて 稀です。 マッピングドキュメントにSQL文を埋め込むことで、 さらに複雑な状況を扱うことができます。 例えば、accountNumber, effectiveEndDate, effectiveStartDate カラムを持つaccount(口座)情報の履歴を扱うテーブルは、 以下のようにマッピングします。

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

そして、関連を 現時点の インスタンス (effectiveEndDate がnullであるもの)にマッピングします。 以下のようになります:

<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クエリを使う方がより実践的です。