hibernate-orm/doc/reference/ja/modules/session_api.xml

1125 lines
54 KiB
XML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?xml version="1.0" encoding="Shift_JIS"?>
<chapter id="objectstate">
<title>オブジェクトを扱う</title>
<para>
Hibernate は完全なオブジェクト/リレーショナルマッピングソリューションであり、
データベース管理システムの詳細を開発者から隠蔽するだけでなく、 オブジェクトの <emphasis>状態管理</emphasis> も行います。
これは、JDBC/SQL永続層と同じようなSQL <literal></literal> の管理とは異なり、
Javaアプリケーションにおける永続化に対する、とても自然なオブジェクト指向の考え方を提供します。
</para>
<para>
言いかえれば、Hibernateを用いるアプリケーション開発者は、オブジェクトの <emphasis>状態</emphasis> については
常に意識すべきであり、SQL文の実行については必ずしもそうではありません。
この部分は、通常、Hibernateが処理し、システムのパフォーマンスをチューニングするときにだけ、 問題になってきます。
</para>
<sect1 id="objectstate-overview">
<title>Hibernateにおけるオブジェクトの状態</title>
<para>
Hibernateは次のようなオブジェクトの状態を定義し、サポートしています。
</para>
<itemizedlist>
<listitem>
<para>
<emphasis> 一時的(Transient) </emphasis> - <literal>new</literal>
演算子を使って インスタンス化されただけで、 Hibernateの <literal>Session</literal>
に関連付けられていないオブジェクトは、 一時的(transient)です。
それは、データベースに永続的な表現を持たず、識別子となる値は割り当てられていません。
一時的なインスタンスは、アプリケーションがその参照をどこにも保持しない場合に、 ガベージコレクタによって破棄されます。
オブジェクトを永続的(persistent)な状態にするためには、Hibernateの
<literal>Session</literal> を使いましょう。
この状態遷移に必要となるSQL文の発行は、Hibernateに任せましょう。
</para>
</listitem>
<listitem>
<para>
<emphasis>永続的(Persistent)</emphasis> -
永続的なインスタンスはデータベースに 永続的な表現を持ち、識別子となる値を持っています。
それは、セーブされたり、ロードされたりするかもしれませんが、
定義上は、 <literal>Session</literal> のスコープの中に存在しています。
Hibernateは、作業単位Unit of workが完了したときに、 永続状態のオブジェクトに加えられた変更を検出し、
オブジェクトの状態とデータベースを同期します。 オブジェクトを一時的(transient)にするときは、開発者は、手作業で
<literal>UPDATE</literal> 文や <literal>DELETE</literal>
文を実行しません。
</para>
</listitem>
<listitem>
<para>
<emphasis>分離(Detached)</emphasis> - 分離されたインスタンスとは、永続化されているが、
それと関連付いていた <literal>Session</literal> がクローズされているオブジェクトのことです。
そのオブジェクトへの参照は、依然として有効です。
そして、もちろん、分離された状態にあるオブジェクトは、修正することさえできます。
分離されたインスタンスは、もう一度永続化したい(そして、すべての変更を永続化したい)ときに、 新しい
<literal>Session</literal> に再追加できます。
この機能は、ユーザが考える時間を必要とするような、長期間に及ぶ作業単位に対する プログラミングモデルを可能にします。
我々は、これを <emphasis>アプリケーションのトランザクションapplication
transactions</emphasis> と呼んでいます。 すなわち、ユーザから見た作業単位だということです。
</para>
</listitem>
</itemizedlist>
<para>
これから、状態と状態遷移そして、遷移のきっかけとなるHibernateのメソッドについて 、詳細に述べます。
</para>
</sect1>
<sect1 id="objectstate-makingpersistent" revision="1">
<title>オブジェクトを永続状態にする</title>
<para>
新しくインスタンス化された永続クラスのインスタンスは、 Hibernateでは
<emphasis>一時的(transient)</emphasis> と見なされます。
以下のように、セッションと関連づけることで、一時的なインスタンスを
<emphasis>永続状態(persistent)</emphasis> にできます。
</para>
<programlisting><![CDATA[DomesticCat fritz = new DomesticCat();
fritz.setColor(Color.GINGER);
fritz.setSex('M');
fritz.setName("Fritz");
Long generatedId = (Long) sess.save(fritz);]]></programlisting>
<para>
<literal>Cat</literal> クラスの識別子が自動生成されるのであれば、
<literal>save()</literal> が呼ばれるときに、 識別子が生成され、 <literal>cat</literal>
インスタンスに割り当てられます。 <literal>Cat</literal>
の識別子が他から割り当てられる( <literal>assigned</literal> 識別子を持つ)か、複合キーであるなら、
<literal>save()</literal> を呼び出す前に、識別子を割り当てなければなりません。
<literal>save()</literal> の代わりに、EJB3 の初期ドラフトで定義された
<literal>persist()</literal> を使うことも可能です。
</para>
<para>
代わりに、識別子を引数にとる <literal>save()</literal>
メソッドを使って、 識別子を割り当てることもできます。
</para>
<programlisting><![CDATA[DomesticCat pk = new DomesticCat();
pk.setColor(Color.TABBY);
pk.setSex('F');
pk.setName("PK");
pk.setKittens( new HashSet() );
pk.addKitten(fritz);
sess.save( pk, new Long(1234) );]]></programlisting>
<para>
永続化するオブジェクトが関連オブジェクトを持っている場合 (例えば、前の例における
<literal>kittens</literal> コレクションのように)、 外部キーカラムに、 <literal>NOT
NULL</literal> 制約をつけない限りは、 これらの一連のオブジェクトをどんな順番で永続化してもかまいません。
外部キー制約を違反する恐れはありません。 しかし、 <literal>NOT NULL</literal>
制約がある場合、間違った順番でオブジェクトを <literal>save()</literal> してしまうと、
制約に違反するかもしれません。 </para>
<para>
関連するオブジェクトを自動的に保存する、 Hibernateの <emphasis>遷移的な永続化(transitive
persistence)</emphasis> 機能を 使うつもりならば、そのような詳細を気にする必要はありません。
そして、 <literal>NOT NULL</literal> 制約の違反すら起こりません。
Hibernateがすべて面倒をみてくれます。遷移的な永続化は、この章の後半に書かれています。 </para>
</sect1>
<sect1 id="objectstate-loading">
<title>オブジェクトのロード</title>
<para>
永続化されたインスタンスの識別子があらかじめ分かっているなら、 <literal>Session</literal>
<literal>load()</literal> メソッドを使って、復元できます。 <literal>load()</literal>
は、Class オブジェクトを引数にとり、 そのクラスのインスタンスを新たに生成し、状態をロードします。
そのインスタンスの状態は、永続(persistent)状態です。 </para>
<programlisting><![CDATA[Cat fritz = (Cat) sess.load(Cat.class, generatedId);]]></programlisting>
<programlisting><![CDATA[// you need to wrap primitive identifiers
long id = 1234;
DomesticCat pk = (DomesticCat) sess.load( DomesticCat.class, new Long(id) );]]></programlisting>
<para>
あるいは、以下のように、既存のインスタンスに状態をロードすることもできます。 </para>
<programlisting><![CDATA[Cat cat = new DomesticCat();
// load pk's state into cat
sess.load( cat, new Long(pkId) );
Set kittens = cat.getKittens();]]></programlisting>
<para>
DBに該当する行が無い場合、 <literal>load()</literal> は回復不可能な例外を 投げることに注意しましょう。
そのクラスがプロキシを使ってマッピングされている場合、 <literal>load()</literal>
は初期化されていないプロキシを返し、プロキシのメソッドが呼ばれるまで実際には データベースにアクセスしません。
もし、実際にデータベースからロードせずに、オブジェクトに対する関連を作りたい場合、 この振る舞いはとても役立ちます。
<literal>batch-size</literal> がクラスマッピングに定義されているならば、
複数のインスタンスを一括でロードすることが可能です。 </para>
<para>
該当する行が存在することを確信できない場合は、 <literal>get()</literal> メソッドを使うべきです。
それは、データベースにすぐにアクセスし、該当する行が無い場合はnullを返します。 </para>
<programlisting><![CDATA[Cat cat = (Cat) sess.get(Cat.class, id);
if (cat==null) {
cat = new Cat();
sess.save(cat, id);
}
return cat;]]></programlisting>
<para>
<literal>LockMode</literal> を使えば、
<literal>SELECT ... FOR UPDATE</literal>というSQLを 使ってオブジェクトをロードすることができます。
詳細な情報は、APIドキュメントを参照してください。 </para>
<programlisting><![CDATA[Cat cat = (Cat) sess.get(Cat.class, id, LockMode.UPGRADE);]]></programlisting>
<para>
関連に対するカスケード方法として
<literal>lock</literal><literal>all</literal>
指定しない限り、関連するインスタンスや含まれるコレクションは <literal>FOR UPDATE</literal> で復元
<emphasis>されない</emphasis> ことに注意しましょう。 </para>
<para>
<literal>refresh()</literal> メソッドを使うことで、どんなときでも、オブジェクトやそのコレクションを
リロードすることができます。 データベースのトリガがテーブルを更新した際に、
そのテーブルに対応するオブジェクトのプロパティを同期する場合、このメソッドが役に立ちます。 </para>
<programlisting><![CDATA[sess.save(cat);
sess.flush(); //force the SQL INSERT
sess.refresh(cat); //re-read the state (after the trigger executes)]]></programlisting>
<para>
大切な問題は、いつも次の点に関するものです。それは、Hibernateがデータベースから、
どのくらいの量を復元するのかと、どのくらいの数のSQLの <literal>SELECT</literal> 文が使われるのかです。
これは、 <emphasis>フェッチの戦略</emphasis> によります。これについては、<xref linkend="performance-fetching"/> で説明しています。 </para>
</sect1>
<sect1 id="objectstate-querying" revision="1">
<title>クエリ</title>
<para>
探したいオブジェクトの識別子が分からない場合は、クエリが必要になります。
Hibernateは使いやすくて強力なオブジェクト指向のクエリ言語 (HQL)をサポートしています。
プログラムによってクエリが作成できるように、Hibernateは洗練されたCriteriaとExampleクエリ機能(QBCとQBE
サポートしています。ResultSetをオブジェクトに変換するHibernateのオプション機能を使うことで、
データベースのネイティブなSQLでクエリを表現することもできます。 </para>
<sect2 id="objectstate-querying-executing" revision="1">
<title>クエリの実行</title>
<para>
HQLやネイティブなSQLクエリは、 <literal>org.hibernate.Query</literal>
のインスタンスとして表現されます。 このインタフェースは、パラメータバインディングやResultSetのハンドリングや
クエリの実行を行うメソッドを用意しています。 通常、 <literal>Query</literal> は、以下に示すように、
その時点の <literal>Session</literal> を使って取得します。 </para>
<programlisting><![CDATA[List cats = session.createQuery(
"from Cat as cat where cat.birthdate < ?")
.setDate(0, date)
.list();
List mothers = session.createQuery(
"select mother from Cat as cat join cat.mother as mother where cat.name = ?")
.setString(0, name)
.list();
List kittens = session.createQuery(
"from Cat as cat where cat.mother = ?")
.setEntity(0, pk)
.list();
Cat mother = (Cat) session.createQuery(
"select cat.mother from Cat as cat where cat = ?")
.setEntity(0, izi)
.uniqueResult();]]
Query mothersWithKittens = (Cat) session.createQuery(
"select mother from Cat as mother left join fetch mother.kittens");
Set uniqueMothers = new HashSet(mothersWithKittens.list());
]]></programlisting>
<para>
クエリは、普通、 <literal>list()</literal> を呼び出すことによって実行されます。
クエリの結果は、メモリ上にあるコレクションにすべてロードされます。
クエリによって復元されたエンティティのインスタンスは、永続状態です。
もし、クエリがたった1個のインスタンスを返すと分かっているなら、
<literal>uniqueResult()</literal> メソッドが手っ取り早い方法です。
即時フェッチを利用したクエリの場合、ふつう、得られたコレクションには、
ルートのオブジェクトが重複して含まれています
(しかし、ルートが持つコレクションは初期化(ロード)されています)。
この重複は <literal>Set</literal> を使って取り除くことができます。</para>
<sect3 id="objectstate-querying-executing-iterate">
<title>結果をイテレートする</title>
<para>
時々、 <literal>iterate()</literal> メソッドを使ってクエリを実行することで、
より良いパフォーマンスを得ることができます。 これは、通常、クエリによって得られた実際のエンティティのインスタンスが、
すでにセッションまたは二次キャッシュに存在することが期待できる場合だけです。
それらが、まだキャッシュされていないなら、 <literal>iterate()</literal> は、
<literal>list()</literal> よりも遅く、簡単なクエリに対しても多くのデータベースアクセスを
必要とします。そのアクセスとは、識別子だけを取得するための最初のselect回と、
実際のインスタンスを初期化するために後から行うn回のselectのことです。 </para>
<programlisting><![CDATA[// fetch ids
Iterator iter = sess.createQuery("from eg.Qux q order by q.likeliness").iterate();
while ( iter.hasNext() ) {
Qux qux = (Qux) iter.next(); // fetch the object
// something we couldnt express in the query
if ( qux.calculateComplicatedAlgorithm() ) {
// delete the current instance
iter.remove();
// dont need to process the rest
break;
}
}]]></programlisting>
</sect3>
<sect3 id="objectstate-querying-executing-tuples">
<title>オブジェクトの組tupleを返すクエリ</title>
<para>
Hibernateのクエリでは、時々、オブジェクトの組を返すことがあります。 その場合は、各タプルは配列として返されます。
</para>
<programlisting><![CDATA[Iterator kittensAndMothers = sess.createQuery(
"select kitten, mother from Cat kitten join kitten.mother mother")
.list()
.iterator();
while ( kittensAndMothers.hasNext() ) {
Object[] tuple = (Object[]) kittensAndMothers.next();
Cat kitten = tuple[0];
Cat mother = tuple[1];
....
}]]></programlisting>
</sect3>
<sect3 id="objectstate-querying-executing-scalar" revision="1">
<title>スカラーの結果</title>
<para>
クエリでは、 <literal>select</literal> 節でクラスのプロパティを指定できます。
SQLの集合関数を呼ぶこともできます。プロパティや集合関数は、
(永続状態のエンティティではなく)「スカラー値」であると見なされます。 </para>
<programlisting><![CDATA[Iterator results = sess.createQuery(
"select cat.color, min(cat.birthdate), count(cat) from Cat cat " +
"group by cat.color")
.list()
.iterator();
while ( results.hasNext() ) {
Object[] row = (Object[]) results.next();
Color type = (Color) row[0];
Date oldest = (Date) row[1];
Integer count = (Integer) row[2];
.....
}]]></programlisting>
</sect3>
<sect3 id="objectstate-querying-executing-parameters">
<title>パラメータのバインド</title>
<para>
<literal>Query</literal>
は、名前付きのパラメータやJDBCスタイルの <literal>?</literal>
パラメータに値をバインドするためのメソッドを持っています。
<emphasis>JDBCとは違い、Hibernateはパラメータにゼロから番号を振っていきます。</emphasis>
名前付きのパラメータとは、クエリ文字列のなかにある <literal>:name</literal> 形式の識別子です。
名前付きパラメータの利点は次の通りです。 </para>
<itemizedlist spacing="compact">
<listitem>
<para> 名前付きパラメータは、クエリ文字列に登場する順番と無関係です </para>
</listitem>
<listitem>
<para> 同じクエリ内に複数回登場することができます </para>
</listitem>
<listitem>
<para> 自分自身を説明します </para>
</listitem>
</itemizedlist>
<programlisting><![CDATA[//named parameter (preferred)
Query q = sess.createQuery("from DomesticCat cat where cat.name = :name");
q.setString("name", "Fritz");
Iterator cats = q.iterate();]]></programlisting>
<programlisting><![CDATA[//positional parameter
Query q = sess.createQuery("from DomesticCat cat where cat.name = ?");
q.setString(0, "Izi");
Iterator cats = q.iterate();]]></programlisting>
<programlisting><![CDATA[//named parameter list
List names = new ArrayList();
names.add("Izi");
names.add("Fritz");
Query q = sess.createQuery("from DomesticCat cat where cat.name in (:namesList)");
q.setParameterList("namesList", names);
List cats = q.list();]]></programlisting>
</sect3>
<sect3 id="objectstate-querying-executing-pagination">
<title>ページ分け
</title>
<para>
ResultSetに制限復元したい最大行数や復元したい最初の行を加える必要があれば、
以下のように、 <literal>Query</literal> インターフェイスのメソッドを使います。 </para>
<programlisting><![CDATA[Query q = sess.createQuery("from DomesticCat cat");
q.setFirstResult(20);
q.setMaxResults(10);
List cats = q.list();]]></programlisting>
<para> 制限付きのクエリをDBMSのネイティブなSQLに変換する方法を、Hibernateは知っています。
</para>
</sect3>
<sect3 id="objectstate-querying-executing-scrolling">
<title>スクロール可能なイテレーション</title>
<para>
JDBCドライバがスクロール可能な <literal>ResultSet</literal> をサポートしていれば、
<literal>Query</literal>
インターフェイスを使って、 <literal>ScrollableResults</literal> オブジェクトを
取得できます。それを使うと、クエリの結果に対して柔軟にナビゲーションできます。 </para>
<programlisting><![CDATA[Query q = sess.createQuery("select cat.name, cat from DomesticCat cat " +
"order by cat.name");
ScrollableResults cats = q.scroll();
if ( cats.first() ) {
// find the first name on each page of an alphabetical list of cats by name
firstNamesOfPages = new ArrayList();
do {
String name = cats.getString(0);
firstNamesOfPages.add(name);
}
while ( cats.scroll(PAGE_SIZE) );
// Now get the first page of cats
pageOfCats = new ArrayList();
cats.beforeFirst();
int i=0;
while( ( PAGE_SIZE > i++ ) && cats.next() ) pageOfCats.add( cats.get(1) );
}
cats.close()]]></programlisting>
<para>
この機能にはオープン状態のデータベースコネクションが必要であることに注意してください。
もし、オフラインのページ分け機能が必要であれば、 <literal>setMaxResult()</literal> /
<literal>setFirstResult()</literal> を使いましょう。 </para>
</sect3>
<sect3 id="objectstate-querying-executing-named" revision="1">
<title>名前付きクエリの外出し</title>
<para>
マッピングドキュメントに名前付きのクエリを定義することができます。
(マークアップと解釈される文字がクエリに含まれるなら、 <literal>CDATA</literal> セクションを
使うことを忘れないようにしましょう。) </para>
<programlisting><![CDATA[<query name="ByNameAndMaximumWeight"><![CDATA[
from eg.DomesticCat as cat
where cat.name = ?
and cat.weight > ?
] ]></query>]]></programlisting>
<para>パラメータのバインディングと実行は、以下のようなプログラムで行われます。 </para>
<programlisting><![CDATA[Query q = sess.getNamedQuery("ByNameAndMaximumWeight");
q.setString(0, name);
q.setInt(1, minWeight);
List cats = q.list();]]></programlisting>
<para> 実際のプログラムコードは、使われるクエリ言語に依存していないことに注意しましょう。
メタデータには、ネイティブSQLクエリを定義することもできます。 また、既存のクエリをマッピングファイルに移すことで、
Hibernateに移行することもできます。 </para>
<para>
<literal>&lt;hibernate-mapping&gt;</literal> の中のクエリ定義は、クエリに対する
ユニークな名前が必要なことにも注意してください。それに対して、 <literal>&lt;class&gt;</literal> の中の
クエリ定義は、クラスの完全限定名が前に付けられるので、自動的にユニークな名前になります。
例: <literal>eg.Cat.ByNameAndMaximumWeight</literal>
</para>
</sect3>
</sect2>
<sect2 id="objectstate-filtering" revision="1">
<title>コレクションのフィルタリング</title>
<para> コレクション <emphasis>フィルタ</emphasis> は、永続化されているコレクションや配列に適用される
特殊なタイプのクエリです。そのクエリ文字列では、コレクションのその時点での要素を意味する
<literal>this</literal> を使います。 </para>
<programlisting><![CDATA[Collection blackKittens = session.createFilter(
pk.getKittens(),
"where this.color = ?")
.setParameter( Color.BLACK, Hibernate.custom(ColorUserType.class) )
.list()
);]]></programlisting>
<para> 返されるコレクションはBagとみなされます。そして、それはもとのコレクションのコピーになります。
元のコレクションは修正されません(これは、"filter"という名前の意味とは異なりますが、 期待される動きとは一致しています)。
</para>
<para> フィルタには <literal>from</literal>
節が不要であることに気づくでしょう(必要なら、持つことも可能ですが)。 フィルタは、コレクションの要素自体を返して構いません。
</para>
<programlisting><![CDATA[Collection blackKittenMates = session.createFilter(
pk.getKittens(),
"select this.mate where this.color = eg.Color.BLACK.intValue")
.list();]]></programlisting>
<para> クエリを含まないフィルタも役に立ちます。
例えば、非常に大きなコレクションの部分集合をロードするために使えます。 </para>
<programlisting><![CDATA[Collection tenKittens = session.createFilter(
mother.getKittens(), "")
.setFirstResult(0).setMaxResults(10)
.list();]]></programlisting>
</sect2>
<sect2 id="objecstate-querying-criteria" revision="1">
<title>クライテリアのクエリ</title>
<para> HQLは非常に強力ですが、クエリ文字列を作るよりも、オブジェクト指向のAPIを使って
動的にクエリを作る方を好む開発者もいます。 こういった場合のために、Hibernateは直感的な
<literal>Criteria</literal> クエリAPIを提供しています。 </para>
<programlisting><![CDATA[Criteria crit = session.createCriteria(Cat.class);
crit.add( Expression.eq( "color", eg.Color.BLACK ) );
crit.setMaxResults(10);
List cats = crit.list();]]></programlisting>
<para> <literal>Criteria</literal>
<literal>Example</literal> APIの詳細は、 <xref linkend="querycriteria"/>
に述べられています。 </para>
</sect2>
<sect2 id="objectstate-querying-nativesql" revision="2">
<title>ネイティブSQLのクエリ</title>
<para>
<literal>createSQLQuery()</literal> を使って、SQLでクエリを表現することもできます。
そして、Hibernateに、ResultSet からオブジェクトへのマッピングをまかせます。
<literal>session.connection()</literal> を呼べばどんなときでも、直接、JDBC
<literal>Connection</literal> を使用できることを覚えておきましょう。 もし、Hibernate
APIを使うのであれば、下記のようにSQLの別名を括弧でくくらなければなりません。 </para>
<programlisting><![CDATA[List cats = session.createSQLQuery(
"SELECT {cat.*} FROM CAT {cat} WHERE ROWNUM<10",
"cat",
Cat.class
).list();]]></programlisting>
<programlisting><![CDATA[List cats = session.createSQLQuery(
"SELECT {cat}.ID AS {cat.id}, {cat}.SEX AS {cat.sex}, " +
"{cat}.MATE AS {cat.mate}, {cat}.SUBCLASS AS {cat.class}, ... " +
"FROM CAT {cat} WHERE ROWNUM<10",
"cat",
Cat.class
).list()]]></programlisting>
<para>
SQLクエリは、Hibernateクエリと同じように、名前付きのパラメータと位置パラメータを持つことができます。
HibernateにおけるネイティブなSQLクエリの詳細については、 <xref linkend="querysql"/>
を参照してください。 </para>
</sect2>
</sect1>
<sect1 id="objectstate-modifying" revision="1">
<title>永続オブジェクトの修正</title>
<para>
<emphasis>処理中の永続インスタンス</emphasis>
(例: <literal>Session</literal> によって、
ロード、セーブ、作成、クエリされたオブジェクト)は、アプリケーションに操作されます。
その際に変更された永続状態は、 <literal>Session</literal><emphasis>フラッシュ</emphasis>
されるときに、永続化されます(これは、この章の後半で述べています)。
変更を永続化するために、特殊なメソッド( <literal>update()</literal>
のようなもの。これは、別の目的で使用します)を 呼ぶ必要はありません。 オブジェクトの状態を更新する一番簡単な方法は、オブジェクトを
<literal>load()</literal> し、 <literal>Session</literal>
をオープンにしている間に、直接操作することです。 </para>
<programlisting><![CDATA[DomesticCat cat = (DomesticCat) sess.load( Cat.class, new Long(69) );
cat.setName("PK");
sess.flush(); // changes to cat are automatically detected and persisted
]]></programlisting>
<para>
オブジェクトをロードするためのSQLの <literal>SELECT</literal> と(更新された状態を永続化するための)
SQLの <literal>UPDATE</literal> が同じセッションで必要となるので、このプログラミングモデルは、
効率が悪くなる場合があります。
そのため、Hibernateは別の方法を用意しています。それは、インスタンスを分離するdetached方法です。 </para>
<para>
<emphasis>Hibernateは、 <literal>UPDATE</literal> 文や
<literal>DELETE</literal> 文を直接実行するAPIを用意していません。
Hibernateは、 <emphasis>状態管理</emphasis> サービスであり、使われるSQL
<emphasis></emphasis> のことを開発者が考える必要はありません。
JDBCはSQL文を実行する完璧なAPIであり、 <literal>session.connection()</literal>
を呼ぶことで いつでも、JDBC <literal>Connection</literal> を開発者は取得できます。
さらに、大量のデータ操作の考え方は、オンライントランザクション処理向きアプリケーションの
オブジェクト/リレーショナルマッピングと衝突します。
しかし、Hibernateの今後のバージョンでは、大量データを処理する特別な機能を提供するかもしれません。
バッチ操作に利用できるいくつかの工夫については、 <xref linkend="batch"/> を参照してください。 </emphasis>
</para>
</sect1>
<sect1 id="objectstate-detached" revision="2">
<title>分離オブジェクトの修正</title>
<para>
多くのアプリケーションでは次のことが必要になります。
それは、あるトランザクションでオブジェクトを復元し、操作するためにそれをUI層に送り、
その後に、新しいトランザクションで変更をセーブするといったことです。
並行性の高い環境で、このタイプのアプローチを使うアプリケーションでは、 "期間の長い"
作業単位の隔離性を保証するために、バージョンデータが通常使われます。 </para>
<para>
Hibernateは、 <literal>Session.update()</literal>
<literal>Session.merge()</literal> メソッドを
使って、分離インスタンスを再追加することで、このモデルに対応します。 </para>
<programlisting><![CDATA[// in the first session
Cat cat = (Cat) firstSession.load(Cat.class, catId);
Cat potentialMate = new Cat();
firstSession.save(potentialMate);
// in a higher layer of the application
cat.setMate(potentialMate);
// later, in a new session
secondSession.update(cat); // update cat
secondSession.update(mate); // update mate]]></programlisting>
<para>
識別子<literal>catId</literal> を持つ <literal>Cat</literal> が、既に
<literal>secondSession</literal> でロードされていた場合は、再追加しようとしたときに、例外が投げられます。
</para>
<para>
同じ識別子を持つ永続インスタンスをセッションが既に保持していないことを
確信できるなら <literal>update()</literal> を使いましょう。
そして、セッションの状態を考えずに、どんな場合でも変更をマージしたい場合は、 <literal>merge()</literal>
を使いましょう。 すなわち、分離オブジェクトの再追加操作が、最初に実行されることを確実にするために、 通常は
<literal>update()</literal> が新しいセッションのなかで最初に呼ばれるメソッドになります。 </para>
<para>
分離インスタンスから到達可能な、分離インスタンスをアプリケーションは個別に <literal>update()</literal>
すべきです。それは、その状態を更新したい場合に <emphasis>限り</emphasis> ます。
<emphasis>遷移的な永続化</emphasis> を使えば、もちろん自動化できます。 <xref
linkend="objectstate-transitive"/> を参照してください。 </para>
<para>
メソッドでもまた、新しいセッションにオブジェクトを再関連付けできます。 しかし、分離インスタンスは無修正でなければなりません。
</para>
<programlisting><![CDATA[//just reassociate:
sess.lock(fritz, LockMode.NONE);
//do a version check, then reassociate:
sess.lock(izi, LockMode.READ);
//do a version check, using SELECT ... FOR UPDATE, then reassociate:
sess.lock(pk, LockMode.UPGRADE);]]></programlisting>
<para>
<literal>lock()</literal> は、さまざまな
<literal>LockMode</literal> とともに使うことができます。
詳細は、APIドキュメントとトランザクション処理の章を参照してください。
再追加のときにだけ、 <literal>lock()</literal> が使われるわけではありません。 </para>
<para>
期間の長い作業単位の、その他のモデルは、<xref linked="transactions-optimistic"/> で述べています。 </para>
</sect1>
<sect1 id="objectstate-saveorupdate">
<title>自動的な状態検出</title>
<para>
Hibernateのユーザは次の2つのケースのどちらにも使える汎用的なメソッドを要求していました。
それは、新しい識別子を生成して一時的なインスタンスをセーブすることと、
その時点の識別子と関連づいている分離インスタンスを更新/再追加することのできるメソッドです。
<literal>saveOrUpdate()</literal> はこのような機能を実現したメソッドです。 </para>
<programlisting><![CDATA[// in the first session
Cat cat = (Cat) firstSession.load(Cat.class, catID);
// in a higher tier of the application
Cat mate = new Cat();
cat.setMate(mate);
// later, in a new session
secondSession.saveOrUpdate(cat); // update existing state (cat has a non-null id)
secondSession.saveOrUpdate(mate); // save the new instance (mate has a null id)]]></programlisting>
<para>
<literal>saveOrUpdate()</literal> の使用方法と意味は、
新しいユーザにとって混乱を招くかもしれません。
まず第一に、あるセッションで使用したインスタンスを別の新しいセッションで使おうとしない限り、
<literal>update()</literal><literal>saveOrUpdate()</literal>
<literal>merge()</literal> を使う必要はありません。
アプリケーション全体を通じて、これらのメソッドを全く使わないこともあります。 </para>
<para>
通常、 <literal>update()</literal><literal>saveOrUpdate()</literal>
は次のシナリオで 使われます。 </para>
<itemizedlist spacing="compact">
<listitem>
<para>アプリケーションが最初のセッションでオブジェクトをロードします。 </para>
</listitem>
<listitem>
<para>オブジェクトがUI層に送られます。 </para>
</listitem>
<listitem>
<para>オブジェクトに対して変更が加えられます。
</para>
</listitem>
<listitem>
<para> オブジェクトがビジネスロジック層に送られます。 </para>
</listitem>
<listitem>
<para>
アプリケーションは、2番目のセッションで <literal>update()</literal>
を呼ぶことで、これらの変更を永続化します。 </para>
</listitem>
</itemizedlist>
<para> <literal>saveOrUpdate()</literal> は以下のことを行います。 </para>
<itemizedlist spacing="compact">
<listitem>
<para>オブジェクトがこのセッションで、すでに永続化されていれば、何もしません。 </para>
</listitem>
<listitem>
<para>
そのセッションに関連づいている別のオブジェクトが同じ識別子を持っているなら、 例外を投げます。 </para>
</listitem>
<listitem>
<para>
オブジェクトの識別子が値を持たないならば、 <literal>save()</literal> します。 </para>
</listitem>
<listitem>
<para>
オブジェクトの識別子が値を持ち、その値が新たにインスタンス化されたオブジェクトのための値である場合、 そのオブジェクトを
<literal>save()</literal> します。 </para>
</listitem>
<listitem>
<para>
オブジェクトが( <literal>&lt;version&gt;</literal>
<literal>&lt;timestamp&gt;</literal> によって)
バージョンづけされていて、バージョンのプロパティが値を持ち、
その値が新しくインスタンス化されたオブジェクトのための値である場合、 そのオブジェクトを
<literal>save()</literal> します。 </para>
</listitem>
<listitem>
<para>
そうでない場合は、そのオブジェクトを <literal>update()</literal> します。 </para>
</listitem>
</itemizedlist>
<para>
そして、 <literal>merge()</literal> は以下のようにとても異なります。 </para>
<itemizedlist spacing="compact">
<listitem>
<para>
同じ識別子を持つ永続化インスタンスがその時点でセッションと関連付いているならば、
引数で受け取ったオブジェクトの状態を永続化インスタンスにコピーします。 </para>
</listitem>
<listitem>
<para>
永続化インスタンスがその時点でセッションに関連付いていないなら、
データベースからそれをロードするか、あるいは、新しい永続化インスタンスを作成します。 </para>
</listitem>
<listitem>
<para>永続化インスタンスが返されます。 </para>
</listitem>
<listitem>
<para> 引数として与えたインスタンスはセッションと関連を持ちません。 それは、分離状態のままです。
</para>
</listitem>
</itemizedlist>
</sect1>
<sect1 id="objectstate-deleting" revision="1">
<title>永続オブジェクトの削除</title>
<para>
<literal>Session.delete()</literal>
はオブジェクトの状態をデータベースから削除します。 もちろん、削除したオブジェクトをアプリケーションが保持したままでもよいです。
そのため、 <literal>delete()</literal> は永続インスタンスを一時的にするものと考えるのが一番です。 </para>
<programlisting><![CDATA[sess.delete(cat);]]></programlisting>
<para>
外部キー制約に違反するリスクもなく、好きな順番でオブジェクトを削除することができます。
ただし、間違った順番でオブジェクトを削除すると、外部キーカラムの <literal>NOT NULL</literal>
制約に違反する可能性があります。 例えば、親オブジェクトを削除したときに、子供オブジェクトを削除し忘れた場合です。 </para>
</sect1>
<sect1 id="objectstate-replicating" revision="1">
<title>異なる二つのデータストア間でのオブジェクトのレプリケーション</title>
<para>
永続インスタンスのグラフを別のデータストアに永続化する場合に、
識別子の値を再生成せずにすむと便利な場合があります。 </para>
<programlisting><![CDATA[//retrieve a cat from one database
Session session1 = factory1.openSession();
Transaction tx1 = session1.beginTransaction();
Cat cat = session1.get(Cat.class, catId);
tx1.commit();
session1.close();
//reconcile with a second database
Session session2 = factory2.openSession();
Transaction tx2 = session2.beginTransaction();
session2.replicate(cat, ReplicationMode.LATEST_VERSION);
tx2.commit();
session2.close();]]></programlisting>
<para>
レプリケーション先のデータベースに行が既にある場合、 <literal>replicate()</literal>
が衝突をどのように扱うかを <literal>ReplicationMode</literal> で指定します。 </para>
<itemizedlist spacing="compact">
<listitem>
<para>
<literal>ReplicationMode.IGNORE</literal> -
同じ識別子を持つ行がデータベースに存在するなら、 そのオブジェクトを無視します。 </para>
</listitem>
<listitem>
<para>
<literal>ReplicationMode.OVERWRITE</literal> - 同じ識別子を持つ既存の行を
すべて上書きします。 </para>
</listitem>
<listitem>
<para>
<literal>ReplicationMode.EXCEPTION</literal> -
同じ識別子を持つ行がデータベースに存在するなら、 例外を投げます。 </para>
</listitem>
<listitem>
<para>
<literal>ReplicationMode.LATEST_VERSION</literal> -
行に保存されているバージョン番号が、 引数のオブジェクトのバージョン番号より古いならば、その行を上書きします。
</para>
</listitem>
</itemizedlist>
<para>
次のようなケースで、この機能を使用します。 異なるデータベースインスタンスに入れられたデータの同期、
製品更新時におけるシステム設定情報の更新、非ACIDトランザクションのなかで加えられた変更のロールバックなどです。 </para>
</sect1>
<sect1 id="objectstate-flushing">
<title>セッションのフラッシュ</title>
<para>
JDBCコネクションの状態とメモリ上のオブジェクトの状態を同期させるために必要な
SQL文を <literal>Session</literal> が実行することがときどきあります。 この処理
<emphasis>flush</emphasis> は、デフォルトでは次のときに起こります。 </para>
<itemizedlist spacing="compact">
<listitem>
<para> クエリを実行する前 </para>
</listitem>
<listitem>
<para>
<literal>org.hibernate.Transaction.commit()</literal> を実行したとき
</para>
</listitem>
<listitem>
<para>
<literal>Session.flush()</literal> を実行したとき </para>
</listitem>
</itemizedlist>
<para> SQL文は以下の順番で発行されます。
</para>
<orderedlist spacing="compact">
<listitem>
<para>
すべてのエンティティの挿入。これは、 <literal>Session.save()</literal> を使ってセーブした
オブジェクトの順に実行していきます。 </para>
</listitem>
<listitem>
<para> すべてのエンティティの更新 </para>
</listitem>
<listitem>
<para> すべてのコレクションの削除 </para>
</listitem>
<listitem>
<para>
すべてのコレクションの要素に対する削除、更新、挿入 </para>
</listitem>
<listitem>
<para>すべてのコレクションの挿入 </para>
</listitem>
<listitem>
<para>
すべてのエンティティの削除。これは、<literal>Session.delete()</literal> を使って
削除したオブジェクトの順に実行していきます。 </para>
</listitem>
</orderedlist>
<para>
(1つ例外があります。 <literal>native</literal> ID 生成を使ったオブジェクトは、
それらがセーブされたときに挿入されます。) </para>
<para>
明示的に
<literal>flush()</literal> するときを除いて、 <emphasis>いつ</emphasis>
<literal>Session</literal> がJDBCをコールするのかについて
絶対的な保証はありません。ただし、それらが実行される <emphasis>順番</emphasis> だけは 保証されます。
また、Hibernate は、 <literal>Query.list(..)</literal> が古いデータや間違ったデータ返さないことを
保証しています。 </para>
<para>
フラッシュが頻繁に起こらないようにデフォルトの振る舞いを変えることができます。 <literal>FlushMode</literal>
クラスは3つの異なるモードを定義します。 それは、コミット時にだけフラッシュするモード Hibernateの
<literal>Transaction</literal> APIが使われる場合だけです
説明のあった処理順に基づいて自動でフラッシュするモード、 <literal>flush()</literal>
が明示的に呼ばれない限りフラッシュしないモードの3つです。 最後のモードは、作業単位が長期間に及ぶ場合に役に立ちます ( <xref
linkend="transactions-optimistic-longsession"/> を参照してください)。
</para>
<programlisting><![CDATA[sess = sf.openSession();
Transaction tx = sess.beginTransaction();
sess.setFlushMode(FlushMode.COMMIT); // allow queries to return stale state
Cat izi = (Cat) sess.load(Cat.class, id);
izi.setName(iznizi);
// might return stale data
sess.find("from Cat as cat left outer join cat.kittens kitten");
// change to izi is not flushed!
...
tx.commit(); // flush occurs
sess.close();]]></programlisting>
<para>フラッシュのとき、例外が発生するかもしれません。
例えば、DML操作が制約を違反するような場合です。
例外処理を理解するためには、Hibernateのトランザクションの振る舞いを理解する必要があるため、 <xref
linkend="transactions"/> で説明します。 </para>
</sect1>
<sect1 id="objectstate-transitive" revision="1">
<title>連鎖的な永続化</title>
<para>
個々のオブジェクトをセーブしたり、削除したり、再追加したりすることは
かなり面倒です。特に、関連するオブジェクトを扱うような場合には際立ちます。 よくあるのは、親子関係を扱うケースです。
以下の例を考えてみましょう。 </para>
<para>
もし、親子関係の子が値型なら(例えば、住所や文字列のコレクション)、
それらのライフサイクルは親に依存しており、便利な状態変化の"カスケード"を使うために、 追加の作業は必要はありません。
親がセーブされたとき、値型の子オブジェクトも同じようにセーブされますし、 親が削除されたときは、子も削除されます。その他の操作も同じです。
コレクションから1つの子を削除するような操作でもうまくいきます。 すなわち、Hibernateはこの削除操作を検出すると、
値型のオブジェクトは参照を共有できないので、データベースからその子供を削除します。 </para>
<para>ここで、親と子が値型でなくエンティティであるとして同じシナリオを考えてみましょう。
(例えば、カテゴリーと品目の関係や親と子の猫の関係です。) エンティティは、それ自身がライフサイクルを持ち、参照の共有をサポートします。
(そのため、コレクションからエンティティを削除することは、 エンティティ自身の削除を意味しません。)
また、エンティティは、デフォルトでは、関連する他のエンティティへ 状態をカスケードすることはありません。 Hibernateは
<emphasis>到達可能性による永続化</emphasis> をデフォルトでは実行しません。 </para>
<para>
HibernateのSessionの基本操作 <literal>persist(), merge(), saveOrUpdate(),
delete(), lock(), refresh(), evict(), replicate()</literal> が含まれます)に対して、
それぞれに対応するカスケードスタイルがあります。 それぞれのカスケードスタイルには、 <literal>create, merge,
save-update, delete, lock, refresh, evict, replicate</literal>
という名前がついています。 もし、関連に沿ってカスケードさせたい操作があるなら、マッピングファイルにそう指定しなければなりません。
例えば、以下のようにします。 </para>
<programlisting><![CDATA[<one-to-one name="person" cascade="persist"/>]]></programlisting>
<para> カスケードスタイルは、組み合わせることができます。 </para>
<programlisting><![CDATA[<one-to-one name="person" cascade="persist,delete,lock"/>]]></programlisting>
<para>
<emphasis>すべての</emphasis>
操作を関連に沿ってカスケードするよう指定するときは、 <literal>cascade="all"</literal> を使います。
デフォルトの <literal>cascade="none"</literal> は、どの操作もカスケードしないことを意味します。
</para>
<para>
特殊なカスケードスタイル
<literal>delete-orphan</literal> は、一対多関連にだけ 適用できます。
これは、関連から削除された子供のオブジェクトに対して、 <literal>delete()</literal>
操作が適用されることを意味します。 </para>
<para>おすすめ: </para>
<itemizedlist spacing="compact">
<listitem>
<para>
普通、 <literal>&lt;many-to-one&gt;</literal>
<literal>&lt;many-to-many&gt;</literal> 関連に対しては、
カスケードを設定する意味はありません。 <literal>&lt;one-to-one&gt;</literal>
<literal>&lt;one-to-many&gt;</literal> 関連に対しては、
カスケードが役に立つことがあります。 </para>
</listitem>
<listitem>
<para>
子供オブジェクトの寿命が親オブジェクトの寿命に制限を受けるならば、
<literal>cascade="all,delete-orphan"</literal> を指定し、 子供オブジェクトを
<emphasis>ライフサイクルオブジェクト</emphasis> にします。 </para>
</listitem>
<listitem>
<para> .
それ以外の場合は、カスケードはほとんど必要ないでしょう。
しかし、同じトランザクションのなかで親と子が一緒に動作することが多いと思い、
いくらかのコードを書く手間を省きたいのであれば、
<literal>cascade="persist,merge,save-update"</literal>
を使うことを考えましょう。 </para>
</listitem>
</itemizedlist>
<para> <literal>cascade="all"</literal> でマッピングした関連(単値関連やコレクション)は、
<emphasis>親子</emphasis> スタイルの関連とマークされます。
それは、親のセーブ/更新/削除が、子のセーブ/更新/削除を引き起こす関係のことです。 </para>
<para>
さらに、永続化された親が子を単に参照しているだけで、子のセーブ/更新を引き起こします。
しかし、このメタファーは不完全です。親から参照されなくなった子は、自動的に削除 <emphasis>されません</emphasis>
ただし、 <literal>cascade="delete-orphan"</literal> でマッピングされた
<literal>&lt;one-to-many&gt;</literal> 関連を 除いてです。
親子関係のカスケード操作の正確な意味は以下のようになります。 </para>
<itemizedlist spacing="compact">
<listitem>
<para>
親が
<literal>persist()</literal> に渡されたならば、 すべての子は
<literal>persist()</literal> に渡されます。 </para>
</listitem>
<listitem>
<para>
<literal>merge()</literal> に渡されたならば、 すべての子は
<literal>merge()</literal> に渡されます。 </para>
</listitem>
<listitem>
<para>
親が <literal>save()</literal>
<literal>update()</literal>
<literal>saveOrUpdate()</literal> に渡されたならば、すべての子は
<literal>saveOrUpdate()</literal> に渡されます。 </para>
</listitem>
<listitem>
<para>
一時的または分離状態の子が、永続化された親に参照されたならば、
<literal>saveOrUpdate()</literal> に渡されます。 </para>
</listitem>
<listitem>
<para>
親が削除されたならば、すべての子は、 <literal>delete()</literal> に渡されます。 </para>
</listitem>
<listitem>
<para>
子が永続化された親から参照されなくなったときは、 <emphasis>特に何も起こりません</emphasis>
よって、アプリケーションが必要であれば、明示的に削除する必要があります。
ただし、 <literal>cascade="delete-orphan"</literal> の場合を除きます。
この場合、「親のない」子は削除されます。 </para>
</listitem>
</itemizedlist>
<para>
最後に、操作のカスケードがオブジェクトグラフに適用されるのは、 <emphasis>コールした時</emphasis>
あるいは、 <emphasis>flushした時</emphasis> であることに注意してください。
すべての操作は、その操作が実行されたときに、到達可能な関連するエンティティに対して
カスケードが可能ならカスケードします。
しかし、 <literal>save-upate</literal>
<literal>delete-orphan</literal> は、 <literal>Session</literal>
がflushしている間に、 すべての到達可能な関連するエンティティに伝播します。</para>
</sect1>
<sect1 id="objectstate-metadata">
<title>メタデータの使用</title>
<para>
Hibernateは、すべてのエンティティと値型の非常にリッチなメタレベルのモデルを必要とします。
ときどき、このモデルはアプリケーションにとってとても役に立ちます。
例えば、アプリケーションは、Hibernateのメタデータを使って、"賢い" ディープコピーアルゴリズムを
実装できるかもしません。そのアルゴリズムとは、どのオブジェクトがコピーされるべきか(例:可変の値型)や
どのオブジェクトはコピーされないべきか(例:不変な値型や可能なら関連するエンティティ)を 判断できるものです。 </para>
<para>
Hibernateは<literal>ClassMetadata</literal>
<literal>CollectionMetadata</literal> インタフェースと
<literal>Type</literal> 階層を通してメタデータを公開します。
メタデータインターフェイスのインスタンスは、 <literal>SessionFactory</literal> から得られます。
</para>
<programlisting><![CDATA[Cat fritz = ......;
ClassMetadata catMeta = sessionfactory.getClassMetadata(Cat.class);
Object[] propertyValues = catMeta.getPropertyValues(fritz);
String[] propertyNames = catMeta.getPropertyNames();
Type[] propertyTypes = catMeta.getPropertyTypes();
// get a Map of all properties which are not collections or associations
Map namedValues = new HashMap();
for ( int i=0; i<propertyNames.length; i++ ) {
if ( !propertyTypes[i].isEntityType() && !propertyTypes[i].isCollectionType() ) {
namedValues.put( propertyNames[i], propertyValues[i] );
}
}]]></programlisting>
</sect1>
</chapter>