Hibernate.org Community Documentation

- 前のページ
- 次のページ

## 第24章 例： 親/子供

新規ユーザーが Hibernate を使ってまず最初に扱うモデルの一つに、親子型のモデル化があります。このモデル化には二つのアプローチが存在します。とりわけ新規ユーザーにとって、さまざまな理由から最も便利だと思われるアプローチは、 `親` から `子供` への `<one-to-many>` 関連により `親` と `子供` の両方をエンティティクラスとしてモデリングする方法です（もう一つの方法は、 `子供` を `<composite-element>` として定義するものです）。これで（ Hibernate における）一対多関連のデフォルトのセマンティクスが、通常の複合要素のマッピングよりも、親子関係のセマンティクスから遠いことがわかります。それでは親子関係を効率的かつエレガントにモデリングするために、 カスケード操作を使った双方向一対多関連 の扱い方を説明します。これはまったく難しいものではありません。

## 24.1. コレクションに関する注意

Hibernate のコレクションは自身のエンティティの論理的な部分と考えられ、決して包含するエンティティのものではありません。これは致命的な違いです。これは以下のような結果になります：

- オブジェクトをコレクションから削除、またはコレクションに追加するとき、コレクションのオーナーのバージョン番号はインクリメントされます。

- もしコレクションから削除されたオブジェクトが値型のインスタンス（例えばコンポジットエレメント) だったならば、そのオブジェクトは永続的ではなくなり、その状態はデータベースから完全に削除されます。同じように、値型のインスタンスをコレクションに追加すると、その状態はすぐに永続的になります。

- 一方、もしエンティティがコレクション（一対多または多対多関連) から削除されても、デフォルトではそれは削除されません。この動作は完全に一貫しています。すなわち、他のエンティティの内部状態を変更しても、関連するエンティティが消滅すべきではないということです。同様に、エンティティがコレクションに追加されても、デフォルトではそのエンティティは永続的にはなりません。

その代わりに、デフォルトの動作では、エンティティをコレクションに追加すると単に二つのエンティティ間のリンクを作成し、一方エンティティを削除するとリンクも削除します。これはすべてのケースにおいて非常に適切です。これが適切でないのは親/子関係の場合です。この場合子供の生存は親のライフサイクルに制限されるからです。

## 24.2. 双方向一対多

`Parent` から `Child` への単純な `<one-to-many>` 関連から始めるとします。

    <set name="children">

        <key column="parent_id"/>

        <one-to-many class="Child"/>

    </set

    >

以下のコードを実行すると、

    Parent p = .....;

    Child c = new Child();

    p.getChildren().add(c);

    session.save(c);

    session.flush();

Hibernate は二つの SQL 文を発行します:

- `c` に対するレコードを生成する `INSERT`

- `p` から `c` へのリンクを作成する `UPDATE`

これは非効率的なだけではなく、 `parent_id` カラムにおいて `NOT NULL` 制約に違反します。コレクションのマッピングで `not-null="true"` と指定することで、 null 制約違反を解決することができます:

    <set name="children">

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

        <one-to-many class="Child"/>

    </set

    >

しかしこの解決策は推奨できません。

この動作の根本的な原因は、 `p` から `c` へのリンク（外部キー `parent_id`) は `Child` オブジェクトの状態の一部とは考えられず、そのため `INSERT` によってリンクが生成されないことです。ですから、解決策はリンクを Child マッピングの一部にすることです。

    <many-to-one name="parent" column="parent_id" not-null="true"/>

(また `Child` クラスに `parent` プロパティを追加する必要があります。)

それでは `Child` エンティティがリンクの状態を制御するようになったので、コレクションがリンクを更新しないようにしましょう。それには `inverse` 属性を使います。

    <set name="children" inverse="true">

        <key column="parent_id"/>

        <one-to-many class="Child"/>

    </set

    >

以下のコードを使えば、新しい `Child` を追加することができます。

    Parent p = (Parent) session.load(Parent.class, pid);

    Child c = new Child();

    c.setParent(p);

    p.getChildren().add(c);

    session.save(c);

    session.flush();

これにより、 SQL の `INSERT` 文が一つだけが発行されるようになりました。

もう少し強化するには、 `Parent` の `addChild()` メソッドを作成します。

    public void addChild(Child c) {

        c.setParent(this);

        children.add(c);

    }

`Child` を追加するコードはこのようになります。

    Parent p = (Parent) session.load(Parent.class, pid);

    Child c = new Child();

    p.addChild(c);

    session.save(c);

    session.flush();

## 24.3. ライフサイクルのカスケード

明示的に `save()` をコールするのはまだ煩わしいものです。これをカスケードを使って対処します。

    <set name="children" inverse="true" cascade="all">

        <key column="parent_id"/>

        <one-to-many class="Child"/>

    </set

    >

これにより先ほどのコードをこのように単純化します

    Parent p = (Parent) session.load(Parent.class, pid);

    Child c = new Child();

    p.addChild(c);

    session.flush();

同様に `Parent` を保存または削除するときに、子供を一つ一つ取り出して扱う必要はありません。以下のコードは `p` を削除し、そしてデータベースからその子供をすべて削除します。

    Parent p = (Parent) session.load(Parent.class, pid);

    session.delete(p);

    session.flush();

しかしこのコードは

    Parent p = (Parent) session.load(Parent.class, pid);

    Child c = (Child) p.getChildren().iterator().next();

    p.getChildren().remove(c);

    c.setParent(null);

    session.flush();

データベースから `c` を削除しません。 `p` へのリンクを削除する（そしてこのケースでは `NOT NULL` 制約違反を引き起こす）だけです。 `Child` の `delete()` を明示する必要があります。

    Parent p = (Parent) session.load(Parent.class, pid);

    Child c = (Child) p.getChildren().iterator().next();

    p.getChildren().remove(c);

    session.delete(c);

    session.flush();

今このケースでは実際に `Child` が親なしでは存在できないようになりました。そのため、もしコレクションから `Child` を取り除く場合、これも削除します。そのためには `cascade="all-delete-orphan"` を使わなければなりません。

    <set name="children" inverse="true" cascade="all-delete-orphan">

        <key column="parent_id"/>

        <one-to-many class="Child"/>

    </set

    >

注記：コレクションのマッピングで `inverse="true"` と指定しても、コレクションの要素のイテレーションによって、依然カスケードが実行されます。そのため、もしカスケードでオブジェクトをセーブ、削除、更新する必要があるなら、それをコレクションに追加しなければなりません。単に `setParent()` を呼ぶだけでは不十分です。

## 24.4. カスケードと unsaved-value

Suppose we loaded up a `Parent` in one `Session`, made some changes in a UI action and wanted to persist these changes in a new session by calling `update()`. The `Parent` will contain a collection of children and, since the cascading update is enabled, Hibernate needs to know which children are newly instantiated and which represent existing rows in the database. We will also assume that both `Parent` and `Child` have generated identifier properties of type `Long`. Hibernate will use the identifier and version/timestamp property value to determine which of the children are new. (See 「自動的な状態検出」.) In Hibernate3, it is no longer necessary to specify an `unsaved-value` explicitly.

以下のコードは `parent` と `child` を更新し、 `newChild` を挿入します。

    //parent and child were both loaded in a previous session

    parent.addChild(child);

    Child newChild = new Child();

    parent.addChild(newChild);

    session.update(parent);

    session.flush();

これらは生成された識別子の場合には非常に良いのですが、割り当てられた識別子と複合識別子の場合はどうでしょうか？これは Hibernate が、（ユーザーにより割り当てられた識別子を持つ）新しくインスタンス化されたオブジェクトと、以前の Session でロードされたオブジェクトを区別できないため、より難しいです。この場合、 Hibernate はタイムスタンプかバージョンのプロパティのどちらかを使うか、二次キャッシュに問い合わせます。最悪の場合、行が存在するかどうかデータベースを見ます。

## 24.5. 結論

ここではかなりの量を要約したので、最初の頃は混乱しているように思われるかもしれません。しかし実際は、すべて非常に良く動作します。ほとんどの Hibernate アプリケーションでは、多くの場面で親子パターンを使用します。

最初の段落で代替方法について触れました。上記のような問題は `<composite-element>` マッピングの場合は存在せず、にもかかわらずそれは確かに親子関係のセマンティクスを持ちます。しかし残念ながら、複合要素クラスには２つの大きな制限があります： 1つは複合要素はコレクションを持つことができないことです。もうひとつは、ユニークな親ではないエンティティの子供となるべきではないということです

製作著作 © 2004 Red Hat, Inc.

- 前のページ第23章 Additional modules
- 上に戻る
- ホーム
- 次のページ第25章 例: Weblog アプリケーション