362 lines
15 KiB
XML
362 lines
15 KiB
XML
<?xml version="1.0" encoding="Shift_JIS"?>
|
||
<chapter id="example-parentchild">
|
||
<title>例:親/子供
|
||
</title>
|
||
|
||
<para>
|
||
新規ユーザがHibernateを使ってまず最初に扱うモデルの一つに、親子型のモデル化があります。
|
||
このモデル化には二つのアプローチが存在します。とりわけ新規ユーザにとって、
|
||
さまざまな理由から最も便利だと思われるアプローチは、<literal>親</literal> から <literal>子供</literal>
|
||
への <literal><one-to-many></literal> 関連により <literal>親</literal> と <literal>子供</literal>
|
||
の両方をエンティティクラスとしてモデリングする方法です
|
||
(もう一つの方法は、<literal>子供</literal> を <literal><composite-element></literal> として定義するものです)。
|
||
これで(Hibernateにおける)一対多関連のデフォルトのセマンティクスが、通常の複合要素のマッピングよりも、
|
||
親子関係のセマンティクスから遠いことがわかります。
|
||
それでは親子関係を効率的かつエレガントにモデリングするために、
|
||
<emphasis>カスケード操作を使った双方向一対多関連</emphasis> の扱い方を説明します。これはまったく難しいものではありません。
|
||
|
||
</para>
|
||
|
||
<sect1 id="example-parentchild-collections">
|
||
<title>コレクションに関する注意</title>
|
||
|
||
<para>
|
||
Hibernateのコレクションは自身のエンティティの論理的な部分と考えられ、
|
||
決して包含するエンティティのものではありません。これは致命的な違いです!
|
||
これは以下のような結果になります:
|
||
</para>
|
||
|
||
<itemizedlist>
|
||
<listitem>
|
||
<para>
|
||
オブジェクトをコレクションから削除、またはコレクションに追加するとき、
|
||
コレクションのオーナーのバージョン番号はインクリメントされます。
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
もしコレクションから削除されたオブジェクトが値型のインスタンス
|
||
(例えばコンポジットエレメント)だったならば、そのオブジェクトは永続的ではなくなり、
|
||
その状態はデータベースから完全に削除されます。
|
||
同じように、値型のインスタンスをコレクションに追加すると、その状態はすぐに永続的になります。
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
一方、もしエンティティがコレクション(一対多または多対多関連)から削除されても、
|
||
デフォルトではそれは削除されません。この動作は完全に一貫しています。
|
||
すなわち、他のエンティティの内部状態を変更しても、関連するエンティティが消滅すべきではないということです。
|
||
同様に、エンティティがコレクションに追加されても、デフォルトではそのエンティティは永続的にはなりません。
|
||
</para>
|
||
</listitem>
|
||
</itemizedlist>
|
||
|
||
<para>
|
||
その代わりに、デフォルトの動作では、エンティティをコレクションに追加すると単に二つのエンティティ間のリンクを作成し、
|
||
一方エンティティを削除するとリンクも削除します。これはすべてのケースにおいて非常に適切です。
|
||
これが適切でないのは親/子関係の場合です。この場合子供の生存は親のライフサイクルに制限されるからです。
|
||
</para>
|
||
|
||
</sect1>
|
||
|
||
<sect1 id="example-parentchild-bidir">
|
||
<title>双方向一対多
|
||
</title>
|
||
|
||
<para>
|
||
<literal>Parent</literal> から <literal>Child</literal> への単純な <literal><one-to-many></literal> 関連から始めるとします。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[<set name="children">
|
||
<key column="parent_id"/>
|
||
<one-to-many class="Child"/>
|
||
</set>]]></programlisting>
|
||
|
||
<para>
|
||
以下のコードを実行すると、
|
||
</para>
|
||
|
||
<programlisting><![CDATA[Parent p = .....;
|
||
Child c = new Child();
|
||
p.getChildren().add(c);
|
||
session.save(c);
|
||
session.flush();]]></programlisting>
|
||
|
||
<para>
|
||
Hibernateは二つのSQL文を発行します:
|
||
</para>
|
||
|
||
<itemizedlist>
|
||
<listitem>
|
||
<para>
|
||
<literal>c</literal>に対するレコードを生成する<literal>INSERT</literal>
|
||
</para>
|
||
</listitem>
|
||
<listitem>
|
||
<para>
|
||
<literal>p</literal>から<literal>c</literal>へのリンクを作成する<literal>UPDATE</literal>
|
||
</para>
|
||
</listitem>
|
||
</itemizedlist>
|
||
|
||
<para>
|
||
これは非効率的なだけではなく、<literal>parent_id</literal> カラムにおいて <literal>NOT NULL</literal> 制約に違反します。
|
||
コレクションのマッピングで <literal>not-null="true"</literal> と指定することで、null制約違反を解決することができます:
|
||
</para>
|
||
|
||
<programlisting><![CDATA[<set name="children">
|
||
<key column="parent_id" not-null="true"/>
|
||
<one-to-many class="Child"/>
|
||
</set>]]></programlisting>
|
||
|
||
<para>
|
||
しかしこの解決策は推奨できません。
|
||
</para>
|
||
<para>
|
||
この動作の根本的な原因は、<literal>p</literal> から <literal>c</literal> へのリンク
|
||
(外部キー <literal>parent_id</literal>)は <literal>Child</literal> オブジェクトの状態の一部とは考えられず、
|
||
そのため <literal>INSERT</literal> によってリンクが生成されないことです。
|
||
ですから、解決策はリンクをChildマッピングの一部にすることです。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[<many-to-one name="parent" column="parent_id" not-null="true"/>]]></programlisting>
|
||
|
||
<para>
|
||
(また <literal>Child</literal> クラスに <literal>parent</literal> プロパティを追加する必要があります。)
|
||
</para>
|
||
|
||
<para>
|
||
それでは <literal>Child</literal> エンティティがリンクの状態を制御するようになったので、
|
||
コレクションがリンクを更新しないようにしましょう。それには <literal>inverse</literal> 属性を使います。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[<set name="children" inverse="true">
|
||
<key column="parent_id"/>
|
||
<one-to-many class="Child"/>
|
||
</set>]]></programlisting>
|
||
|
||
<para>
|
||
以下のコードを使えば、新しい <literal>Child</literal> を追加することができます。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
||
Child c = new Child();
|
||
c.setParent(p);
|
||
p.getChildren().add(c);
|
||
session.save(c);
|
||
session.flush();]]></programlisting>
|
||
|
||
<para>
|
||
これにより、SQLの <literal>INSERT</literal> 文が一つだけが発行されるようになりました!
|
||
</para>
|
||
|
||
<para>
|
||
もう少し強化するには、<literal>Parent</literal> の <literal>addChild()</literal> メソッドを作成します。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[public void addChild(Child c) {
|
||
c.setParent(this);
|
||
children.add(c);
|
||
}]]></programlisting>
|
||
|
||
<para>
|
||
<literal>Child</literal> を追加するコードはこのようになります。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
||
Child c = new Child();
|
||
p.addChild(c);
|
||
session.save(c);
|
||
session.flush();]]></programlisting>
|
||
|
||
</sect1>
|
||
|
||
<sect1 id="example-parentchild-cascades">
|
||
<title>ライフサイクルのカスケード</title>
|
||
|
||
<para>
|
||
明示的に <literal>save()</literal> をコールするのはまだ煩わしいものです。これをカスケードを使って対処します。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[<set name="children" inverse="true" cascade="all">
|
||
<key column="parent_id"/>
|
||
<one-to-many class="Child"/>
|
||
</set>]]></programlisting>
|
||
|
||
<para>
|
||
これにより先ほどのコードをこのように単純化します
|
||
</para>
|
||
|
||
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
||
Child c = new Child();
|
||
p.addChild(c);
|
||
session.flush();]]></programlisting>
|
||
|
||
<para>
|
||
同様に <literal>Parent</literal> を保存または削除するときに、子供を一つ一つ取り出して扱う必要はありません。
|
||
以下のコードは <literal>p</literal> を削除し、そしてデータベースからその子供をすべて削除します。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
||
session.delete(p);
|
||
session.flush();]]></programlisting>
|
||
|
||
<para>
|
||
しかしこのコードは
|
||
</para>
|
||
|
||
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
||
Child c = (Child) p.getChildren().iterator().next();
|
||
p.getChildren().remove(c);
|
||
c.setParent(null);
|
||
session.flush();]]></programlisting>
|
||
|
||
<para>
|
||
データベースから <literal>c</literal> を削除しません。<literal>p</literal> へのリンクを削除する
|
||
(そしてこのケースでは <literal>NOT NULL</literal> 制約違反を引き起こす)だけです。
|
||
<literal>Child</literal> の <literal>delete()</literal> を明示する必要があります。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
||
Child c = (Child) p.getChildren().iterator().next();
|
||
p.getChildren().remove(c);
|
||
session.delete(c);
|
||
session.flush();]]></programlisting>
|
||
|
||
<para>
|
||
今このケースでは実際に <literal>Child</literal> が親なしでは存在できないようになりました。
|
||
そのため、もしコレクションから <literal>Child</literal> を取り除く場合、これも削除したいです。
|
||
そのためには <literal>cascade="all-delete-orphan"</literal> を使わなければなりません。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[<set name="children" inverse="true" cascade="all-delete-orphan">
|
||
<key column="parent_id"/>
|
||
<one-to-many class="Child"/>
|
||
</set>]]></programlisting>
|
||
|
||
<para>
|
||
注意:コレクションのマッピングで <literal>inverse="true"</literal> と指定しても、
|
||
コレクションの要素のイテレーションによって、依然カスケードが実行されます。
|
||
そのためもしカスケードでオブジェクトをセーブ、削除、更新する必要があるなら、
|
||
それをコレクションに追加しなければなりません。単に <literal>setParent()</literal> を呼ぶだけでは不十分です。
|
||
</para>
|
||
|
||
</sect1>
|
||
|
||
<sect1 id="example-parentchild-update">
|
||
<title>カスケードと <literal>unsaved-value</literal></title>
|
||
|
||
<para>
|
||
<literal>Parent</literal> が、ある <literal>Session</literal> でロードされ、UIのアクションで変更が加えられ、
|
||
<literal>update()</literal> を呼んでこの変更を新しいセッションで永続化したいとします。
|
||
<literal>Parent</literal> が子供のコレクションを持ち、カスケード更新が有効になっているため、
|
||
Hibernateはどの子供が新しくインスタンス化されたか、どれがデータベースの既存の行に相当するのかを知る必要があります。
|
||
<literal>Parent</literal> と <literal>Child</literal> の両方が <literal>java.lang.Long</literal>
|
||
型の識別プロパティを生成したとしましょう。
|
||
Hibernateはどの子供が新しいものかを決定するために識別プロパティの値を使います(versionやtimestampプロパティも使えます。
|
||
<xref linkend="manipulatingdata-updating-detached"/> 参照)。Hibernate3になって、
|
||
明示的に <literal>unsaved-value</literal> を指定する必要はなくなりました。
|
||
</para>
|
||
|
||
<para>
|
||
以下のコードは <literal>parent</literal> と <literal>child</literal> を更新し、<literal>newChild</literal> を挿入します。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[//parent and child were both loaded in a previous session
|
||
//parentとchildは両方とも、以前のSessionでロードされています
|
||
parent.addChild(child);
|
||
Child newChild = new Child();
|
||
parent.addChild(newChild);
|
||
session.update(parent);
|
||
session.flush();]]></programlisting>
|
||
|
||
<para>
|
||
これらは生成された識別子の場合には非常に良いのですが、割り当てられた識別子と複合識別子の場合はどうでしょうか?
|
||
これはHibernateが、(ユーザにより割り当てられた識別子を持つ)新しくインスタンス化されたオブジェクトと、
|
||
以前のSessionでロードされたオブジェクトを区別できないため、より難しいです。
|
||
この場合、Hibernateはタイムスタンプかバージョンのプロパティのどちらかを使うか、二次キャッシュに問い合わせます。
|
||
最悪の場合、行が存在するかどうかデータベースを見ます。
|
||
</para>
|
||
|
||
<!-- undocumenting
|
||
<para>
|
||
There is one further possibility. The <literal>Interceptor</literal> method named
|
||
<literal>isUnsaved()</literal> lets the application implement its own strategy for distinguishing
|
||
newly instantiated objects. For example, you could define a base class for your persistent classes.
|
||
</para>
|
||
|
||
<programlisting><![CDATA[public class Persistent {
|
||
private boolean _saved = false;
|
||
public void onSave() {
|
||
_saved=true;
|
||
}
|
||
public void onLoad() {
|
||
_saved=true;
|
||
}
|
||
......
|
||
public boolean isSaved() {
|
||
return _saved;
|
||
}
|
||
}]]></programlisting>
|
||
|
||
<para>
|
||
(The <literal>saved</literal> property is non-persistent.)
|
||
Now implement <literal>isUnsaved()</literal>, along with <literal>onLoad()</literal>
|
||
and <literal>onSave()</literal> as follows.
|
||
</para>
|
||
|
||
<programlisting><![CDATA[public Boolean isUnsaved(Object entity) {
|
||
if (entity instanceof Persistent) {
|
||
return new Boolean( !( (Persistent) entity ).isSaved() );
|
||
}
|
||
else {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
public boolean onLoad(Object entity,
|
||
Serializable id,
|
||
Object[] state,
|
||
String[] propertyNames,
|
||
Type[] types) {
|
||
|
||
if (entity instanceof Persistent) ( (Persistent) entity ).onLoad();
|
||
return false;
|
||
}
|
||
|
||
public boolean onSave(Object entity,
|
||
Serializable id,
|
||
Object[] state,
|
||
String[] propertyNames,
|
||
Type[] types) {
|
||
|
||
if (entity instanceof Persistent) ( (Persistent) entity ).onSave();
|
||
return false;
|
||
}]]></programlisting>
|
||
|
||
<para>
|
||
Don't worry; in Hibernate3 you don't need to write any of this kind of code if you don't want to.
|
||
</para>
|
||
-->
|
||
</sect1>
|
||
|
||
<sect1 id="example-parentchild-conclusion">
|
||
<title>結論
|
||
</title>
|
||
|
||
<para>
|
||
ここではかなりの量を要約したので、最初の頃は混乱しているように思われるかもしれません。
|
||
しかし実際は、すべて非常に良く動作します。ほとんどのHibernateアプリケーションでは、多くの場面で親子パターンを使用します。
|
||
</para>
|
||
|
||
<para>
|
||
最初の段落で代替方法について触れました。上記のような問題は <literal><composite-element></literal> マッピングの場合は存在せず、
|
||
にもかかわらずそれは確かに親子関係のセマンティクスを持ちます。
|
||
しかし残念ながら、複合要素クラスには二つの大きな制限があります:
|
||
1つは複合要素はコレクションを持つことができないことです。もうひとつは、
|
||
ユニークな親ではないエンティティの子供となるべきではないということです
|
||
</para>
|
||
|
||
</sect1>
|
||
|
||
</chapter> |