第6章 コレクションのマッピング

6.1. 永続性コレクション

この節はJavaのコード例をあまり含んでいません。Javaのコレクション・フレームワークの使用法は既に知っているものとします。もし既に使用法を知っているなら、それ以上知っておく必要はありません。いつもの方法でコレクションを使用してください。

Hibernateはjava.util.Map, java.util.Set, java.util.SortedMap, java.util.SortedSet, java.util.List,そしてどんな永続性エンティティや値の配列のインスタンスも永続化することができます。 java.util.Collectionjava.util.List型のプロパティを"bag"として永続化することもできます。

警告:コレクションの永続化は、コレクション・インターフェースを実装するクラスによって加えられた余分な動作(例えばLinkedHashSetの反復順序)は保持しません。コレクションの永続化は実際にそれぞれHashMap, HashSet, TreeMap, TreeSet および ArrayListの様に振舞います。更に言えば、コレクションを保持する属性のJavaの型はインタフェース型でなくてはなりません(つまりHashMap, TreeSet , ArrayListではなく、Map, SetListでなくてはならない)。Hibernateは知らない間にMap, Set , ListのインスタンスをMap, Set , Listの永続化機能を実装したインスタンスと取り替えるのでこの制限が存在します(従ってコレクション上で==を使用する場合注意してください)。

Cat cat <literal>==</literal> new DomesticCat();
Cat kitten = new DomesticCat();
....
Set kittens = new HashSet();
kittens.add(kitten);
cat.setKittens(kittens);
session.save(cat);
kittens = cat.getKittens(); //Okay, kittens collection is a Set
(HashSet) cat.getKittens(); //Error!

コレクションはバリュータイプの通常の規則に従います。つまり、含まれているエンティティと一緒に生成、削除された参照は共有されません。リレーショナル・モデルをベースにしているためnull値のセマンティクスをサポートしません。つまりHibernateは参照先の無いコレクションと空のコレクションを見分けません。

コレクションは永続性オブジェクトによって参照されたときに自動的に永続化され、永続性オブジェクトによって参照されなくなったときに自動的に削除されます。もしコレクションがある永続性オブジェクトから他の永続性オブジェクトに渡されたなら、その要素はあるテーブルから別のテーブルへ移動されます。このことについてあまり心配する必要はありません。普通のJavaのコレクションを使うのと同じ方法でHibernateのコレクションを使ってください。しかしHibernateのコレクションを使用する前に、後述する双方向関連のセマンティクスをきちんと理解してください。

コレクションのインスタンスは、データベース内で所有するエンティティの外部キーによって識別されます。 この外部キーはコレクション・キーと呼ばれます。コレクション・キーは<key>要素によってマッピングされます。

コレクションは基本型、カスタム型、エンティティ型、コンポーネントだけでなく、ほとんどのHibernate型を格納することができます。コレクション内のオブジェクトは"値渡し"セマンティクスで扱われるか(そのためコレクションの所有者に完全に依存します)、または自身のライフサイクルを使って他のエンティティへの参照でありえます。このことは重要な定義です。また、コレクションは他のコレクションを格納できません。格納される型はコレクション要素型とよばれます。コレクションの要素は、<element>, <composite-element>, <one-to-many>, <many-to-many> あるいは <many-to-any>によってマッピングされます。最初の2つは値のセマンティクスで要素をマッピングし、あとの3つはエンティティの関連をマッピングするために使用されます。

Setとbagを除くすべてのコレクション型は、配列またはListのインデックスやMapのキーへマッピングするインデックス ・カラムを持ちます。Mapのインデックスは任意の基本型、エンティティ型あるいは合成型などかもしれません(コレクションではありえません)。配列やlistのインデックスは常に整数型です。インデックスは<index>, <index-many-to-many>, <composite-index> あるいは <index-many-to-any> を使用してマッピングされます。

Hibernateはコレクションに対して、多くのリレーショナル・モデルをカバーする幅広いマッピングを提供しています。どのようにして様々なマッピング宣言をデータベースへ変換するか感じをつかむため、スキーマ生成ツールを使ってみることを提案します。

6.2. コレクションのマッピング

コレクションは<set>, <list>, <map>, <bag>, <array> そして <primitive-array> 要素によって宣言されます。以下に<map>で例を示します。:

<map
    name="propertyName"                                         (1)
    table="table_name"                                          (2)
    schema="schema_name"                                        (3)
    lazy="true|false"                                           (4)
    inverse="true|false"                                        (5)
    cascade="all|none|save-update|delete|all-delete-orphan"     (6)
    sort="unsorted|natural|comparatorClass"                     (7)
    order-by="column_name asc|desc"                             (8)
    where="arbitrary sql where condition"                       (9)
    outer-join="true|false|auto"                                (10)
    batch-size="N"                                              (11)
    access="field|property|ClassName"                           (12)
>

    <key .... />
    <index .... />
    <element .... />
</map>
(1)

name コレクションのプロパティ名

(2)

table (オプション - デフォルトはプロパティ名) コレクションテーブルの名前(one-to-many関連では使用しません)

(3)

schema (オプション) ルート要素のスキーマ宣言を書き換える、テーブルスキーマの名前

(4)

lazy (オプション - デフォルトはfalse) lazy初期化を可能にします。(配列には使用しません)

(5)

inverse (オプション - デフォルトはfalse) 双方向関連の「逆」側としてこのコレクションにマークします。

(6)

cascade (オプション - デフォルトはnone) 子エンティティへのカスケード操作を可能にします

(7)

sort (オプション) 自然ソート順序または与えられたコンパレータ・クラスを使ってソート・コレクションを指定します

(8)

order-by (オプション, JDK1.4のみ) オプションのasc , descを一緒に使って、Map, Set,bagの反復順序を決めるテーブル・カラム(カラム)を指定します

(9)

(optional) (オプション) コレクションを検索や削除するときに使う任意のSQLのWHERE条件を指定します。(コレクションが利用可能なデータの部分集合だけを含んでいるとき有用です。)

(10)

(オプション) 可能な場合は常にouter-joinでコレクションをフェッチすることを指定します。SQLのSELECT一回に一つだけのコレクションを返すかもしれません

(11)

batch-size (オプション, デフォルトは1) lazyにこのコレクションのインスタンスを取って来るための「バッチ・サイズ」を指定します。

(12)

access (オプション - デフォルトはproperty): プロパティの値へのアクセスのためにHibernateが使用するべき戦略

Listや配列のマッピングは、インデックス(foo[i]i)を保持するテーブル・カラムを分離することを要求します。リレーショナル・モデルがインデックス・カラムを持っていない場合、例えばレガシー・データを用いて仕事をしている場合、順序の無いSetを代わりに使用します。このことは、Listは順序の無いコレクションにアクセスするより便利であると考える人達からは、かけ離れた意見と思います。HibernateのコレクションはSet, List および Mapインターフェースに付けられた実際のセマンティクスに厳密に従います。Listは要素を自然と再整列などしません!

また一方でbagの動作をエミュレートするためにListを使用することを考えた人々は、まさに不満を持っています。bagは同じ要素を複数回含んでいるかもしれない、順序が無く、インデックスがないコレクションです。Javaのコレクションフ・レームワークにはBagインターフェースがないのでListを使ってエミュレートしなくてはなりません。HibernateはListCollection型のプロパティと<bag>の要素をマッピングさせます。bagは実際にはCollectionとではなく、List規約のセマンティクスと衝 突するものであることに注意してください(しかしこの章の後で議論するようにbagを適宜ソートできます)。

注意:inverse="false"でマッピングされた大きなHibernateのbagは非効率的であり、避けるべきです。それはbagは個々の列を識別するために使用できるキーがないので、Hibernateは列を個々に作成し、削除し、更新することができないからです。

6.3. 値のコレクションとmany-to-many関連

コレクション・テーブルは、任意の値コレクションと任意のmany-to-many関連としてマッピングされた他のエンティティを参照するコレクションに対して必要となります。テーブルはキー・カラム(外部キー)、要素カラム、そしておそらくインデックス・カラムを必要とします。

コレクションテーブルから所有するクラスのテーブルへの外部キー<key>要素を使用して宣言されます。

<key column="column_name"/>
(1)

column (必須): 外部キーのカラム名

mapやlistのような索引付けされたコレクションについては<index>要素が必要です。listは、このインデックス・カラムが、番号が0から付けられた連続する整数を含んでいます。もしレガシー・データを扱わなければならないなら、インデックスが間違いなく0から始まるようにしてください。mapについてはカラムがHibernate型のどんな値も含むことができます。

<index
        column="column_name"                (1)
        type="typename"                     (2)
/>
(1)

column (必須): コレクション・インデックスの値を保持するカラムの名前

(2)

type (オプション, デフォルトはInteger): コレクション・インデックスの型

もうひとつの方法として、mapはエンティティ型のオブジェクトによって索引付けされるかもしれません。我々は<index-many-to-many> 要素を使用します

<index-many-to-many
        column="column_name"                (1)
        class="ClassName"                   (2)
/>
(1)

column (必須): コレクション・インデックスの値を保持する外部キーカラムの名前

(2)

class (必須): コレクション・インデックスとして使用されるエンティティ・クラス

値のコレクションに関しては<element>タグを使用します。

<element
        column="column_name"                (1)
        type="typename"                     (2)
/>
(1)

column (必須): コレクション・インデックスの値を保持するカラムの名前

(2)

type (必須): コレクション要素の型

コレクション自身のテーブルを備えたエンティティのコレクションは、many-to-many関連についてのリレーショナルの概念に相当します。many-to-many関連はJavaのコレクションの最も自然なマッピングではありますが、通常最良のリレーショナル・モデルではありません。

<many-to-many
        column="column_name"                               (1)
        class="ClassName"                                  (2)
        outer-join="true|false|auto"                       (3)
    />
(1)

column (必須): 外部キーカラムの要素の名前

(2)

class (required): (必須): 関連クラスの名前

(3)

outer-join (オプション - デフォルトはauto): hibernate.use_outer_joinが設定されたとき関連をアウター・ジョインでフェッチ可能にする

まずStringの集合の例:

<set name="names" table="NAMES">
    <key column="GROUPID"/>
    <element column="NAME" type="string"/>
</set>

整数を含んでいるbag:(order-by属性によって反復順序を定義されたbag):

<bag name="sizes" table="SIZES" order-by="SIZE ASC">
    <key column="OWNER"/>
    <element column="SIZE" type="integer"/>
</bag>

エンティティの配列-この場合many-to-many関連(ここでエンティティはライフサイクル・オブジェクトであり、cascade="all"であることに注意):

<array name="foos" table="BAR_FOOS" cascade="all">
    <key column="BAR_ID"/>
    <index column="I"/>
    <many-to-many column="FOO_ID" class="org.hibernate.Foo"/>
</array>

Stringインデックスから日付へのマップッピング:

<map name="holidays" table="holidays" schema="dbo" order-by="hol_name asc">
    <key column="id"/>
    <index column="hol_name" type="string"/>
    <element column="hol_date" type="date"/>
</map>

コンポーネントのlist(次の章で議論します):

<list name="carComponents" table="car_components">
    <key column="car_id"/>
    <index column="posn"/>
    <composite-element class="org.hibernate.car.CarComponent">
            <property name="price" type="float"/>
            <property name="type" type="org.hibernate.car.ComponentType"/>
            <property name="serialNumber" column="serial_no" type="string"/>
    </composite-element>
</list>

6.4. one-to-many関連

one-to-many関連はコレクション・テーブルを介在せずに、2つのクラスのテーブルを直接リンクします。(これはone-to-manyリレーショナル・モデルを実装します。)このリレーショナル・モデルは以下のように、Javaのコレクションのいくつかのセマンティクスを失います:

  • map、set、listにnull値は含めません。

  • コレクションに含まれたエンティティ・クラスのインスタンスは、2つ以上のコレクションのインデックスに属せません。

  • コレクションに含まれたエンティティ・クラスのインスタンスは、2つ以上のコレクション・インデックスの値には現れません。

FooからBarへの関連はキーカラムと、もしあるならばインデックス・カラムをコレクションに含まれたエンティティ・クラスのテーブル、つまりBarへ追加することを要求します。これらのカラムは上記のように<key><index>要素を使ってマッピングします。

<one-to-many>タグは、one-to-many関連を示します

<one-to-many class="ClassName"/>
(1)

class (必須): 関連クラスの名前

例:

<set name="bars">
    <key column="foo_id"/>
    <one-to-many class="org.hibernate.Bar"/>
</set>

<one-to-many>要素はカラムを宣言する必要がないことに注意してください。同様にテーブル名を指定する必要もありません。

非常に重要な注意:もし<one-to-many>関連の<key>カラムがNOT NULLと宣言される場合、Hibernateはそれが関連を作成するか更新する時、制約違反を引き起こすかもしれません。 この問題を防ぐためには、manyの値の側(setやbag)にinverse="true"とマークした双方向関連を使用しなくてはなりません。この章で 後述する双方向関連を見てください。

6.5. lazy初期化(Lazy Initialization)

コレクション(配列以外)は、アプリケーションがアクセスを必要としたときだけデータベースから状態をロードするlazy初期化が出来ます。lazy初期化はユーザにとって透過的に起こります。そのためアプリケーションが通常このことについて関心を持つ必要がありません(実際、透過的なlazy初期化はHibernateが何故自身のコレクションの実装クラスを必要とするかという主な理由となります)しかしアプリケーションで以下のようなことをする場合問題を招きます。:

s = sessions.openSession();
User u = (User) s.find("from User u where u.name=?", userName, Hibernate.STRING).get(0);
Map permissions = u.getPermissions();
s.connection().commit();
s.close();

Integer accessLevel = (Integer) permissions.get("accounts");  // Error!

これは嫌な驚きに直面しそうになります。 Sessionがコミットされたときにpermissionコレクションが初期化されていないので、コレクションは決してその状態をロードできません。コレクションからコミットの直前へ読み込み行を移動して、修正してください(しかしこの問題を解決するさらに良い方法が他にもあります)。

もうひとつの方法としてlazyでないコレクションを使用してください。lazy初期化が上記のようにバグに結びつくこともあるのでデフォルトはlazy初期化を行いません。しかしながらlazy初期化がほとんどすべてのコレクションのために、特にエンティティのコレクションのために(効率の理由のための)使用されるように意図されています。

コレクションをlazy初期化する時に起こる例外はLazyInitializationExceptionでラップされています。

オプションのlazyプロパティを使用してlazyなコレクションを宣言するには以下のようにします。:

<set name="names" table="NAMES" lazy="true">
    <key column="group_id"/>
    <element column="NAME" type="string"/>
</set>

複数のアプリケーション・アーキテクチャーで、特にHibernateを使ってデータにアクセスするコードとそれを使用するコードが異なるアプリケーション層にある場合には、コレクションが初期化されるときにSessionが開いていることを保証する問題があります。以下はこの問題に対処する2つの基礎的な方法です:

  • 一度ビューのレンダリングが完全になれば、ウェブ・ベースのアプリケーションではユーザ・リクエストの終端においてのみ、Sessionをクローズするためにサーブレット・フィルタを使用することができます。もちろんこのことはアプリケーション・インフラに例外処理の正確性が要求されます。ビューを提供している最中に例外が起こったときでさえ、ユーザーに処理が戻る前にSessionがクローズされ、そしてトランザクションが終了するということは極めて重要です。サーブレット・フィルタはこのアプローチのために、Sessionにアクセス可能でなければなりません。私たちは現在のSessionを保持するために、スレッド・ローカルな変数の使用を勧めます(実装例は1章の1.4節を見てください 項1.4. 「ネコと遊ぶ」)。

  • 個別のビジネス層を備えたアプリケーションでは、ビジネスロジックは処理が返る前にウェブ層によって必要とされるすべてのコレクションを準備しなくてはなりません。このことはビジネス層は特定のユースケースに対し必要となるプレゼンテーション層/Web層にすでに、初期化されたデータをすべてロードし返すべきであるということを意味します。通常アプリケーションはウェブ層によって必要とされるコレクションそれぞれに対しHibernate.initialize()を呼ぶか(この呼び出しはSessionがクローズする前に呼ばれねばならない)、FETCH句のあるクエリーを使ってeagerにコレクションを復元します。

  • 初期化していないコレクション(またはプロキシ)にアクセスする前に、以前にロードしたオブジェクトをupdate()lock()を使って新しいSessionに関連させることが出来ます。アドホックなトランザクションのセマンティクスを導入したので、Hibernateはこれを自動的に行なうことが出来ません。

初期化せずにコレクションのサイズを得るためにHibernate Session APIのfilter()メソッドを使用することができます:

( (Integer) s.filter( collection, "select count(*)" ).get(0) ).intValue()

filter()createFilter()もコレクション全体を初期化する必要なしに、効率的にコレクションの部分集合を復元するために使用されます。

6.6. ソートされたコレクション

Hibernateはjava.utilの実装であるjava.util.SortedMapjava.util.SortedSetをサポートします。開発者はマッピング定義ファイルにコンパレーターを指定しなければなりません:

<set name="aliases" table="person_aliases" sort="natural">
    <key column="person"/>
    <element column="name" type="string"/>
</set>

<map name="holidays" sort="my.custom.HolidayComparator" lazy="true">
    <key column="year_id"/>
    <index column="hol_name" type="string"/>
    <element column="hol_date" type="date"/>
</map>

ソート属性として指定できる値はunsorted,natural,そしてjava.util.Comparatorの実装クラス名です。

ソートされたコレクションは実際にjava.util.TreeSetjava.util.TreeMapのように動作します。

もしデータベース自身にコレクション要素を並べさせたいならばset, bag, maporder-by属性を使うと良いでしょう。この解決法はJDK 1.4かあるいはそれ以上のバージョンで利用可能です(LinkedHashSetまたはLinkedHashMapを使用して実装されます)。これはSQLクエリ内で整列を実行するのであってメモリ上ではありません。

<set name="aliases" table="person_aliases" order-by="name asc">
    <key column="person"/>
    <element column="name" type="string"/>
</set>

<map name="holidays" order-by="hol_date, hol_name" lazy="true">
    <key column="year_id"/>
    <index column="hol_name" type="string"/>
    <element column="hol_date type="date"/>
</map>

order-by属性の値がSQL命令であってHQL命令ではないことに注意してください!

関連は実行時にfilter()を使用することで任意のcriteriaによってソートされるかもしれません。

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

6.7. <idbag>の使用

もし複合キーは悪いことであり、エンティティは人工の識別子(代理キー)を持つべきであるという考え方に賛成するならば、我々が示したmany-to-many関連と値のコレクションを複合キーを用いたテーブルへマッピングするということは少し奇妙に感じるかもしれません。この考えには確かに議論の余地があります。それは純粋な関連テーブルは代理キーによって十分な利益を得られるようには思えないからです(合成値のコレクションは利益があるかもしれませんが)。にもかかわらずHibernateは代理キーを使ったテーブルへのmany-to-many関連と値のコレクションを許す特徴を提供します。

<idbag>要素でbagの動作を持ったlist(またはCollection)をマッピングできます。

<idbag name="lovers" table="LOVERS" lazy="true">
    <collection-id column="ID" type="long">
        <generator class="hilo"/>
    </collection-id>
    <key column="PERSON1"/>
    <many-to-many column="PERSON2" class="eg.Person" outer-join="true"/>
</idbag>

ご覧のように<idbag>はエンティティ・クラスのように人工のidジェネレータを持っています。異なる代理キーはそれぞれのコレクションの列に割り当てられます。 しかしHibernateは特定の列の代理キーの値を発見するメカニズムを提供しません。

<idbag>の更新のパフォーマンスは通常の<bag>よりもいいことに注目してください。 Hibernateは個々の列を効果的に見つけることができ、ちょうどlistやmapやsetのように個別にそれらを更新、削除できます。

現在の実装では<idbag>コレクション識別子に対して、native識別子生成戦略はサポートされていません。

6.8. 双方向関連

双方向関連は関連のどちらの側からでもナビゲーションできます。2種類の双方向関連がサポートされています:

one-to-many

片側がsetかbag、もう片方の多重度が1です。

many-to-many

両側がsetかbagです。

Hibernateはmany側がインデックス付けされたコレクション(listやmapや配列)である双方向one-to-many関連をサポートしていないことに注意してください。setまたはbagマッピングを使用してください。

2つのmany-to-many関連から同じデータベース・テーブルへのマッピングを使用するか、片側をinverseと宣言して双方向many-to-many関連を指定することが出来ます(どちらを選ぶかは選択次第です)。以下に、あるクラスからそれ自身のクラスへの双方向many-to-many関連の例を示します(各categoryは多くのitemsを持つことができます。また、各itemは多くのcategory内にありえます)。:

<class name="org.hibernate.auction.Category">
    <id name="id" column="ID"/>
    ...
    <bag name="items" table="CATEGORY_ITEM" lazy="true">
        <key column="CATEGORY_ID"/>
        <many-to-many class="org.hibernate.auction.Item" column="ITEM_ID"/>
    </bag>
</class>

<class name="org.hibernate.auction.Item">
    <id name="id" column="ID"/>
    ...

    <!-- inverse end -->
    <bag name="categories" table="CATEGORY_ITEM" inverse="true" lazy="true">
        <key column="ITEM_ID"/>
        <many-to-many class="org.hibernate.auction.Category" column="CATEGORY_ID"/>
    </bag>
</class>

関連の逆側へのみ行なわれた変更は永続化されません。 このことはHibernateがメモリ内のすべての双方向関連に対して2つの表現を持つことを意味します。1つはAからBへのリンクと、もうひとつはBからAでのリンクです。もしJavaのオブジェクト・モデルのことを考え、どのようにJavaでmany-to-many関連を作成するか考えればこれを理解するのは簡単です。:

category.getItems().add(item);          // The category now "knows" about the relationship
item.getCategories().add(category);     // The item now "knows" about the relationship

session.update(item);                     // No effect, nothing will be saved!
session.update(category);                 // The relationship will be saved

inverseではない側はデータベースへメモリ内の表現を保存するために使用されます。もし両方の側が変更を引き起こす場合、不必要なInsert/Updateそして恐らく外部キー制約違反を起こすでしょう。もちろん双方向one-to-many関連に対しても同じことが言えます。

同じテーブル・カラムへのone-to-many関連をmany-to-one関連としてマッピングし、many側の関連端をinverse="true"と宣言することで双方向one-to-many関連をマッピングできます。

<class name="eg.Parent">
    <id name="id" column="id"/>
    ....
    <set name="children" inverse="true" lazy="true">
        <key column="parent_id"/>
        <one-to-many class="eg.Child"/>
    </set>
</class>

<class name="eg.Child">
    <id name="id" column="id"/>
    ....
    <many-to-one name="parent" class="eg.Parent" column="parent_id"/>
</class>

inverse="true"である関連のone側のマッピングはカスケード操作に影響ありません。両者は異なる概念です。

6.9. 3項関連

3項関連のマッピングには2つの可能なアプローチがあります。 1つ目のアプローチはcomposite要素を使用することです(議論は後ほど)。もうひとつのアプローチは関連をインデックスとして使ったMapを使用することです。:

<map name="contracts" lazy="true">
    <key column="employer_id"/>
    <index-many-to-many column="employee_id" class="Employee"/>
    <one-to-many class="Contract"/>
</map>
<map name="connections" lazy="true">
    <key column="node1_id"/>
    <index-many-to-many column="node2_id" class="Node"/>
    <many-to-many column="connection_id" class="Connection"/>
</map>

6.10. Heterogeneousな関連

<many-to-any>および<index-many-to-any>要素はHeterogeneousな関連(異なる型が混在する関連)に対して提供されます。これらのマッピング要素は<any>要素と同様の作用をします。あるとしてもめったに使用されません。

6.11. コレクションの例

今までの節はかなり混乱させられるので以下の例を見てください。:

package eg;
import java.util.Set;

public class Parent {
    private long id;
    private Set children;

    public long getId() { return id; }
    private void setId(long id) { this.id=id; }

    private Set getChildren() { return children; }
    private void setChildren(Set children) { this.children=children; }

    ....
    ....
}

このクラスは eg.Childのインスタンスのコレクションを持っています。もし各々のchildが最大でも1つのparentを持つならば最も自然なマッピングはone-to-many関連です。:

<hibernate-mapping>

    <class name="eg.Parent">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <set name="children" lazy="true">
            <key column="parent_id"/>
            <one-to-many class="eg.Child"/>
        </set>
    </class>

    <class name="eg.Child">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <property name="name"/>
    </class>

</hibernate-mapping>

このマッピング定義は下のテーブル定義へマッピングします。

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

もしそのparentが要求されたならば双方向one-to-many関連を使用してください(Parent/Child関係の章を見てください)。:

<hibernate-mapping>

    <class name="eg.Parent">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <set name="children" inverse="true" lazy="true">
            <key column="parent_id"/>
            <one-to-many class="eg.Child"/>
        </set>
    </class>

    <class name="eg.Child">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <property name="name"/>
        <many-to-one name="parent" class="eg.Parent" column="parent_id" not-null="true"/>
    </class>

</hibernate-mapping>

NOT NULL制約に注意してください。

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

一方で、childが複数のparentを持てるならばmany-to-many関連が妥当です。:

<hibernate-mapping>

    <class name="eg.Parent">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <set name="children" lazy="true" table="childset">
            <key column="parent_id"/>
            <many-to-many class="eg.Child" column="child_id"/>
        </set>
    </class>

    <class name="eg.Child">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <property name="name"/>
    </class>

</hibernate-mapping>

テーブル定義は以下のようになります。:

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