この章では、開発サイクル後半で最も一般的に使用する可能性のある作業ユニットの拡張APIのコールと手法について説明します。
この章の内容は次のとおりです。
UnitOfWork
で使用可能なメソッドの詳細は、『Oracle Fusion Middleware Java API Reference for Oracle TopLink』を参照してください。
作業ユニットでは、オブジェクト登録のオプションをいくつか用意しています。
この項の内容は次のとおりです。
例115-1は、作業ユニットのnewInstance
メソッドを使用して、新しいPet
オブジェクトを作成し、そのオブジェクトを作業ユニットに登録してクローンを返すという操作をすべて1つのステップで実行する方法を示します。ファクトリ設計パターンを使用してオブジェクトを作成し、さらにこれを問合せビルダーで指定した場合、newInstance
メソッドでは適切なファクトリが使用されます。
registerAllObjects
メソッドは、引数としてオブジェクトのCollection
をとり、クローンのCollection
を返します。これにより、例115-2に示すように、一度に多くのオブジェクトを登録できます。
注意: UnitOfWork のregisterObject 、registerNewObject またはregisterExistingObject メソッドは、集約オブジェクトに対して使用できません(22.2.1.2項「リレーショナル集約ディスクリプタの作成」を参照)。これをすると、コミット時にValidationExceptionまたはその他のエラーが発生します。詳細は、115.1.4項「集約の使用方法」を参照してください。 |
例115-2 registerAllObjectsの使用
UnitOfWork uow = session.acquireUnitOfWork(); Collection toRegister = new ArrayList(2); VetVisit vv1 = new VetVisit(); vv1.setId(70); vv1.setNotes("May have flu"); vv1.setSymptoms("High temperature"); toRegister.add(vv1); VetVisit vv2 = new VetVisit(); vv2.setId(71); vv2.setNotes("May have flu"); vv2.setSymptoms("Sick to stomach"); toRegister.add(vv2); uow.registerAllObjects(toRegister); uow.commit();
作業ユニットを使用してオブジェクトを登録する際に、TopLinkでは存在チェックを実行してオブジェクトが存在するかどうかを判断します。TopLinkではこの情報をコミット時に使用して、挿入操作または更新操作を実行するかどうかを決定します。全体的にプロジェクトにデフォルトの存在チェック・ポリシーを指定するか(117.7項「プロジェクト・レベルでの存在チェックの構成」を参照)、ディスクリプタごとにデフォルトの存在チェック・ポリシーを指定します(119.17項「ディスクリプタ・レベルでのキャッシュ存在チェックの構成」を参照)。デフォルトでは、「キャッシュのチェック」存在チェック・ポリシーが使用されます。キャッシュのチェック以外の存在チェック・ポリシーを使用する場合は、TopLinkでオブジェクトの登録にかかる時間を削減する方法を使用して、オブジェクトを登録できます。
この項では、オブジェクトの登録を迅速化する次の存在チェック・ポリシーの使用方法について説明します。
存在チェック・ポリシー「データベースのチェック」を使用してクラスのディスクリプタを構成する場合、作業ユニットに登録されたそのクラスのすべてのインスタンスの存在がデータベースでチェックされます。ただし、オブジェクトが新規か既存かがわかっている場合は、基本的なregisterObject
メソッドを使用するのではなく、registerNewObject
またはregisterExistingObject
を使用すれば、存在チェックを省略できます。これらのメソッドを使用して登録したオブジェクトについては、データベースでの存在チェックが行われません。registerNewObject
がコールされた場合は挿入操作が、registerExistingObject
がコールされた場合は更新操作が自動的に実行されます。
存在チェック・ポリシー「存在すると仮定」を使用してクラスのディスクリプタを構成する場合、TopLinkでは作業ユニットを使用して登録されたクラスのインスタンスがすべて存在すると仮定して、registerObject
メソッドを使用して登録された新規オブジェクトの場合でも、登録されたすべてのオブジェクトに対してデータベースで常に更新操作を実行します。ただし、新規オブジェクトに対してregisterNewObject
メソッドを使用した場合は、存在チェック・ポリシーが「存在すると仮定」であっても、データベースへの挿入操作が実行されます。
存在チェック・ポリシー「存在しないと仮定」を使用してクラスのディスクリプタを構成する場合、TopLinkでは作業ユニットを使用して登録されたクラスのインスタンスがすべて存在しないと仮定して、データベースから読み取ったオブジェクトの場合でも、データベースで常に挿入操作を実行します。ただし、既存のオブジェクトに対してregisterExistingObject
メソッドを使用すると、データベースの更新操作が実行されます。
集約マップ・オブジェクトは、TopLinkの作業ユニットに登録しないでください。登録すると、例外が生成されます。集約のクローニングと登録は、集約オブジェクトの所有者に基づいて自動的に行われます。言い換えれば、集約の所有者を登録すると、集約が自動的にクローン化されます。集約の所有者の作業コピーを取得した場合は、その所有者の集約も作業コピーです。
集約を使用する場合は、常に所有者のコンテキスト内で使用してください。
作業クローンの所有者から集約を取得した場合、その集約は作業クローンです。
キャッシュ・バージョンの所有者から集約を取得した場合、その集約はキャッシュ・バージョンです。
集約オブジェクトの詳細は、22.2.1.2項「リレーショナル集約ディスクリプタの作成」を参照してください。
作業ユニットのunregisterObject
メソッドを使用すると、以前登録したオブジェクトを作業ユニットから登録解除できます。登録解除したオブジェクトは作業ユニットでは無視され、その時点までにオブジェクトに加えられた未コミットの変更内容は破棄されます。
一般に、このメソッドはほとんど使用されません。このメソッドは、新規オブジェクトを作成したが、同じ作業ユニットでそれを削除することにしたという場合(推奨されません)に便利です。
表115-1では、UnitOfWork
オブジェクトの登録メソッドを示します。
表115-1 UnitOfWorkオブジェクト登録API
タスク | UnitOfWorkメソッド | 結果 |
---|---|---|
子なし新規オブジェクトの作成 |
|
新規オブジェクトはコミット後のキャッシュ・オブジェクトへの参照です。 |
子なし新規オブジェクトの作成 |
|
新規オブジェクトはコミット後のキャッシュ・オブジェクトへの参照です。 パフォーマンス拡張機能: クローンは作成されません。 |
既存の親の新規子オブジェクトの作成: 単方向リレーションシップ |
親: 子: 登録不要 |
TopLinkでは登録済オブジェクトから到達可能なすべての新規子オブジェクトに登録をカスケードします。したがって新規子オブジェクトを登録する必要がありません。 |
既存の親の新規子オブジェクトの作成: 単方向リレーションシップ |
親: 子: |
新規オブジェクトはコミット後のキャッシュ・オブジェクトへの参照です。 |
既存の親の新規子オブジェクトの作成: 双方向リレーションシップ |
親: 子: 登録不要 |
TopLinkでは登録済オブジェクトから到達可能なすべての新規子オブジェクトに登録をカスケードします。したがって新規子オブジェクトを登録する必要がありません。 |
既存の親の新規子オブジェクトの作成: 双方向リレーションシップ |
親: 子: |
コミット前に新規オブジェクトを問合せできます(一致する問合せの使用なし)。 子に対する |
データベース内に存在することが判明している既存オブジェクトの変更 |
|
パフォーマンス拡張機能: |
オブジェクトの |
|
簡便メソッド: |
新規オブジェクトがクローンから到達可能な場合、その登録は必要ありません。
新規オブジェクトを扱う際には、次の点を覚えておいてください。
到達可能な(登録されている)オブジェクトのみが永続化されます。
到達可能な新規オブジェクト、またはregisterNewObject
を使用して登録されたオブジェクトは、作業ユニットでは作業コピーとみなされます。
新規オブジェクトに対してregisterObject
をコールした場合、メソッドによって返されるクローン、および渡す引数はキャッシュ・バージョンとみなされます。
registerNewObject
メソッドは、新規オブジェクトをクローンのように登録します。コミット時には、作業ユニットがオブジェクトのインスタンスをもう1つ作成し、そのオブジェクトのキャッシュ・バージョンとします。registerNewObject
メソッドは、次の状況が該当する場合に使用します。
オブジェクトのキャッシュ・バージョンへのハンドルがトランザクションのコミット後に不要になる場合。また、新規オブジェクトのクローンを使用しない場合。
新規オブジェクトのコンストラクタにクローンを渡した後、新規オブジェクトを登録する必要がある場合。
あるオブジェクトに対してregisterNewObject
をコールした場合、TopLinkではそのオブジェクトの新規の子には登録をカスケードしません。子は永続化されますが、コミット前にその問合せはできません(一致する問合せを使用する場合を除く)。
コミット前に子が問合せに表示されるようにするには、次のいずれかを実行する必要があります。
registerObject
による親の登録
registerNewObject
によるそれぞれの子の登録(registerNewObject
により親を登録する場合)
詳細は、次を参照してください。
作業ユニットのコンテキスト内でクラスを読取り専用として宣言できます。読取り専用として宣言したクラスに対してはクローンの作成もマージも行われないため、パフォーマンスが向上します。このようなクラスは、作業ユニットでの変更の対象になりません。
作業ユニットがオブジェクトを登録する場合、オブジェクト・ツリー全体を走査して登録します。作業ユニットが読取り専用クラスを検出すると、ツリーのそのブランチは走査せず、その読取り専用クラスによって参照されるオブジェクトは登録しません。したがって、読取り専用クラスは作業ユニットでの変更の対象になりません。読取り専用クラスはキャッシュされますが、ユーザーはこれを変更しないでください。
この項の内容は次のとおりです。
あるいは、読取り専用問合せとしてオブジェクト・レベルの読取り問合せを構成できます。詳細は、108.7.1.4項「読取り専用問合せ」を参照してください。
たとえば、クラスAがクラスBを所有し、クラスCがクラスBを拡張するとします。作業ユニットを取得しますが、その中でクラスAのインスタンスのみが変更され、クラスBは変更されないということがわかっています。クラスBのインスタンスを登録する前に、次のコードを使用します。
myUnitofWork.addReadOnlyClass(B.class);
その後、クラスAのオブジェクトを登録し、その作業コピーを変更してから、作業ユニットをコミットするというトランザクションに進みます。
コミット時には、作業ユニットはクラスBのインスタンスについて、バックアップ・クローンを作業クローンと比較する必要がありません(インスタンスが明示的または暗黙的に登録されている場合でも同じです)。そのため、オブジェクト・ツリーが非常に大きい場合は、作業ユニットのパフォーマンスが向上します。
クラスCのインスタンスを登録する場合、作業ユニットではこのオブジェクトのクローンの作成やマージは行われません。クラスCはクラスBの拡張であり、クラスBは読取り専用に指定されているため、クラスCに対する変更は永続化されません。
複数のクラスを読取り専用に指定するには、それらをVector
に追加し、次のコードを使用します。
myUnitOfWork.addReadOnlyClasses(myVectorOfClasses);
ネストした作業ユニットは、親作業ユニットから読取り専用クラスのセットを継承します。ネストした作業ユニットの使用の詳細は、115.8項「ネストした作業ユニットまたはパラレル作業ユニットの使用」を参照してください。
すべての作業ユニットに対する読取り専用クラスのデフォルト・セットを設定するには、プロジェクト・メソッドsetDefaultReadOnlyClasses(Vector)
を使用します。このメソッドをコールすると、すべての新規作業ユニットに読取り専用クラスのVector
が含められます。
クラスを読取り専用として宣言すると、読取り専用の宣言がそのクラスのディスクリプタにも適用されます。開発時にディスクリプタを読取り専用として宣言するには、Javaコード、Oracle JDeveloperまたはTopLink Workbenchを使用します。こうすることで、読取り専用ディスクリプタは作業ユニットでの登録と編集の対象外となるため、パフォーマンスが向上します。
Javaコードでディスクリプタを読取り専用としてフラグ付けするには、ディスクリプタに対してsetReadOnly
メソッドを次のようにコールします。
descriptor.setReadOnly();
TopLink Workbenchでディスクリプタを読取り専用として宣言するには、特定のディスクリプタの「読取り専用」チェック・ボックスを選択します。
詳細は、119.3項「読取り専用ディスクリプタの構成」を参照してください。
デフォルトでは、作業ユニットのcommit
メソッドをコールする際、TopLinkではデータ・ソースに変更内容を書き込み、その変更内容をコミットします。
または、作業ユニットのwriteChanges
メソッドを使用してからcommit
を(直接、または外部トランザクション・サービスを介して)コールすることで、2ステージ・コミット・トランザクションを実行するか、部分コミット・トランザクションを実行できます。
作業ユニットのwriteChanges
メソッドをコールすると、作業ユニット・コミット・プロセスが始まり、すべての変更がデータ・ソースに書き込まれます。ただし、データ・ソースのトランザクションはコミットされず、変更内容が共有セッション・キャッシュにマージされることもありません。変更を完了するには、作業ユニットのcommit
メソッドを(直接、または外部トランザクション・サービスを介して)コールする必要があります。
作業ユニットのwriteChanges
メソッドをコールした後、オブジェクトを登録するか、オブジェクト・レベルの問合せを実行しようとすると、例外がスローされます。レポート問合せ、キャッシュしない問合せ、データ読取り問合せおよび変更問合せを実行できます。
例外がスローされた場合は、トランザクションがロールバックされ(または、ロールバックのマーク付けのみが行われ)、作業ユニットを回復することはできません。
このメソッドは、1回のみコールできます。このメソッドでは、追加で変更内容を書き込むことはできません。
作業ユニットのwriteChanges
メソッドを使用して、次のような様々なトランザクションの問題に取り組むことができます。
一致機能の代替として(115.4.4.1項「一致にかわる作業ユニットのメソッドwriteChangesの使用」を参照)
外部トランザクションの問題を処理するため(115.13.4項「作業ユニットを使用した外部トランザクション・タイムアウトと例外の処理方法」を参照)
問合せはデータベースに対して実行されるため、作業ユニットを通じて問合せを行うと、デフォルトでは新しい未コミットのオブジェクトは作業ユニットに含められません。明示的に指定しないかぎり、作業ユニットはその中の新しい未コミットのオブジェクトに対して問合せを実行しません。未コミットの変更がある場合は、作業ユニット内で問題が発生することがあります。データベースに書き込まれていない未コミットの変更内容は、結果セットが取得する戻り値に影響しません。
一致機能とは、新規、変更済または削除済オブジェクトを、コミットを待たずに作業ユニット内の問合せに含めることができる問合せ機能です。これにより、データベースの相対論理ビューまたはトランザクション・ビューに対する問合せを行うことができます。
一致機能を使用する前に、この制限事項に注意し(115.4.1項「一致機能の使用方法」を参照)、一致機能が現実的に必要であるかどうかを確認してください。たとえば、115.4.4項「一致する問合せの代替手段について」で説明する別の方法を検討してください。
この項の内容は次のとおりです。
注意: デフォルトでは、一致におけるメモリーの検索ステージ中にスローされた例外はTopLinkで非表示にされます。一致するときの例外処理の詳細は、115.16.4.2項「一致するときの例外処理」を参照してください。 |
一致機能を使用する際、一致する問合せで必ず正しい結果が返されるように、この項で説明する次のガイドラインに従ってください。
一致機能は、次の問合せでサポートされています。
ReadObjectQuery
ReadAllQuery
式
EJB QL
例による問合せ
選択オブジェクトまたは主キーによる問合せ(新規または削除済オブジェクトのみ適用)
一致機能は、次の問合せではサポートされていません。
ReportQuery
DataReadQuery
DataReadQuery
(削除済オブジェクトは一致可能)
StoredProcedureCall
(削除済オブジェクトは一致可能)
EISCall
(削除済オブジェクトは一致可能)
データベース固有の関数(または副選択)を使用する式またはEJB QL問合せ
パラレル式
ReadAllQuery
で一致機能を使用する場合、データベース結果が最初に問い合されます。作業ユニットがまだ変更をデータベースにコミットしていない場合、この結果には作業ユニットの変更が含まれません。データベース結果は、次の基準に従ってメモリー内で一致されます。
問合せに一致する登録済の新規オブジェクトは追加されます。
一致しなくなった変更済の既存オブジェクトは除外されます。
一致する変更済の既存オブジェクトは追加されます。
削除済のオブジェクトは除外されます。
注意: 新規オブジェクトが明示的に登録されていない場合、それらのオブジェクトは一致されません。また、除外されたオブジェクトが明示的に削除されていない場合、それらのオブジェクトも一致されません。 |
問合せで順序を使用する場合、一致された結果の順序は維持されません。一致されたインスタンスは、結果の先頭に追加されます。順序を適用するには、Collections
のメソッドsort
、またはTreeSet
の結果コレクション・クラスを使用してメモリー内に結果を格納します。ReadObjectQuery
で一致機能を使用する場合、一致オブジェクトの作業ユニットが最初に問い合されます。一致オブジェクトが見つかった場合、そのオブジェクトが返され、データベース・アクセスは回避されます。一致オブジェクトが見つからない場合、データベースが問い合されます。作業ユニットがまだ変更をデータベースにコミットしていない場合、この結果には作業ユニットの変更が含まれません。データベース結果は、次の基準に従ってメモリー内で一致されます。
データベース結果が一致しない場合、NULLが返されます。
データベース結果が削除されている場合、NULLが返されます。
注意: データベース結果により複数の結果が返される場合、最初の結果のみが考慮されます。この結果はReadAllQuery のインスタンスであり、単一の結果のみが適切であるためです。最初の結果が一致しない場合、NULLが返されます(他に有効な一致結果が存在する可能性があっても同様です)。
問合せで複数の結果が返される場合は、 |
新規オブジェクトが既存のオブジェクトに関連しているだけで、明示的に登録されていない場合、このオブジェクトは問合せで一致できません。私有オブジェクトが除外されていても、明示的に削除されていない場合、このオブジェクトも一致できません。
問合せで(結合を使用する)リレーションシップを走査する際に、関連オブジェクトが変更されている場合、問合せでこれらのオブジェクトが一致されるのは、次の条件が両方とも満たされる場合のみです。
ソース・オブジェクトが作業ユニットに登録されていること
ソース・オブジェクトのリレーションシップがインスタンス化されていること
TopLinkには、インダイレクションのインスタンス化を強制する一致オプション(遅延ロード)があります。ただし、このオプションはデータベース・アクセスを増大させる可能性があるため、慎重に使用してください。
例115-2について考えてみます。この例では、Address
オブジェクトに対して1対1マッピングでマップされたインダイレクション(17.2.4項「インダイレクション(遅延ロード)」を参照)で使用するために、address
属性を持つEmployee
オブジェクトを構成しました。
オタワに住むすべての従業員を読み取る場合、初めに一部のAddress
オブジェクトを修正して、都市をトロントからオタワに変更する必要があります。Jane Doeは、変更が必要な従業員です。
最初にUnitOfWork
を使用して、すべてのAddress
オブジェクトを読み取り、一部のcity
属性(Janeの属性を含む)をトロントからオタワに変更します。次に、一致する問合せを実行して、オタワに住むすべての従業員を取得します。しかし、Janeは現在オタワに住んでいますが、次の理由から結果には含まれていません。
トランザクションがコミットされず、データベース上ではJaneはTorontoに住んでいることになっているため、データベースからの戻り値に含まれませんでした。
JaneはUnitOfWork
キャッシュに登録されていないため、メモリー内で一致した結果に追加されません。
一致機能では、明示的な変更のみを認識します。この例では、Jane DoeのEmployee
オブジェクトは暗黙的に変更されただけでした。明示的に変更されたとみなすには、Employee
が次の基準を満たすことが必要です。
UnitOfWork
に登録。
address
属性を変更: この例では、address
属性のためにインダイレクション(遅延ロード)をトリガーする必要があります。
この例を正しく処理するには、次のようにします。
UnitOfWork
を使用して、すべての従業員を読み取ります。
この時点で、これらすべてのEmployee
オブジェクトは、UnitOfWork
に登録されています。
同じUnitOfWork
を使用して、従業員の住所にアクセスし、インダイレクト・リレーションシップをインスタンス化します。
いくつかの住所をオタワに変更して従業員の住所を修正します。
オタワに住む従業員に対して一致する問合せを実行します。
オタワに住むすべての従業員と住所が返されます。これにはもともとオタワに住んでいる従業員と、このトランザクションによって住所が変更された従業員が含まれます。
トランザクションをコミットします。
住所が変更対象となる従業員がすべて登録されておらず、その住所のリレーションシップがインスタンス化されていない場合、一致する問合せにはJaneは含まれません。
また、簡単なトランザクションを使用する方法があります。最も安全な一致する問合せは、コミット直後に作成されます。次に例を示します。
UnitOfWork
を使用して、オタワ以外のすべての住所を読み取ります。
いくつかの住所をオタワに変更して住所を修正します。
トランザクションをコミットします。
UnitOfWork
を使用して、オタワに住むすべての従業員を読み取ります。
タイプCat
の単一のPet
がすでにデータベースに存在するとします。例115-3に示すコードについて考えてみましょう。
例115-3 一致する問合せの使用
UnitOfWork uow = session.acquireUnitOfWork(); Pet pet2 = new Pet(); Pet petClone = (Pet)uow.registerObject(pet2); petClone.setId(200); petClone.setType("Cat"); petClone.setName("Mouser"); ReadAllQuery readAllCats = new ReadAllQuery(); readAllCats.setReferenceClass(Pet.class); ExpressionBuilder builder = new ExpressionBuilder(); Expression catExp = builder.get("type").equal("Cat"); readAllCats.setSelectionCriteria(catExp); List allCats = (List)uow.executeQuery(readAllCats); System.out.println("All 'Cats' read through UOW are: " + allCats); uow.commit();
このコードでは、次の出力が生成されます。
All 'Cats' read through UOW are: [Pet type Cat named Fluffy id:100]
問合せreadAllCats
に新規オブジェクトを含めるよう指示する場合、次のコードを使用します。
readAllCats.conformResultsInUnitOfWork();
出力は次のようになります。
All 'Cats' read through UOW are: [Pet type Cat named Fluffy id:100, Pet type Cat named Mouser id:200]
TopLinkによる、作業ユニットでの一致する問合せのサポートは、ディスクリプタ・レベルで指定できます。
常にディスクリプタが作業ユニット内の結果に一致するよう直接定義し、このディスクリプタに対して実行されるすべての問合せをデフォルトで作業ユニット内の結果に一致させることができます。これは、コード内、Oracle JDeveloperまたはTopLink Workbenchで指定できます。
ディスクリプタを構成し、Oracle JDeveloper、TopLink WorkbenchまたはJavaコードを使用して、常に作業ユニットで一致させることができます。
Javaコードを使用して作業ユニットで常に一致するようなディスクリプタを構成するには、Descriptor
のメソッドsetShouldAlwaysConformResultsInUnitOfWork
を使用し、true
の引数付きで渡します。
TopLinkを使用して作業ユニットで常に一致するようなディスクリプタを構成するには、119.4項「ディスクリプタ・レベルでの作業ユニット一致機能の構成」を参照してください。
この項では、一致機能によってパフォーマンスを低下させることなく、要件を満たすことのできる一致の代替手段を説明します。この項の内容は次のとおりです。
UnitOfWork
のメソッドwriteChanges
を使用して、未コミットの変更内容をデータ・ソースに書き込むことができます。これらの変更内容に対して、レポート問合せ、キャッシュされない問合せ、データ読取り問合せおよび変更問合せを実行できます(例115-4を参照)。
例115-4 作業ユニットのメソッドwriteChangesの使用
UnitOfWork uow = session.acquireUnitOfWork(); Pet pet = new Pet(); Pet petClone = (Pet)uow.registerObject(pet); petClone.setId(100); petClone.setName("Fluffy"); petClone.setType("Cat"); uow.writeChanges(); // Use uow to perform report, noncaching, and data read and modify queries // against the changes made so far uow.commit();
writeChanges
は1回のみコールできますが、オブジェクトを登録するか、オブジェクト・レベルの問合せを実行しようとすると、例外がスローされます。
詳細は、115.3項「コミット前の変更内容の書込み」を参照してください。
作業ユニットで作成された新規オブジェクトに、他のコード・モジュールからアクセスできるようにする必要が生じる場合があります。一致機能はこのアクセスに使用できます。しかし、次の代替手段は、より効果的です。
どこかで作業ユニットがセッションから取得され、必要な処理の一部を表す複数のモジュールに渡されます。
UnitOfWork uow = session.acquireUnitOfWork();
新しいペットを作成するモジュールで、次のコードに注意してください。
Pet newPet = new Pet(); Pet newPetClone = (Pet)uow.registerObject(newPet); uow.setProperty("NEW PET", newPet);
newPet
にアクセスしてさらに変更を加える必要のある他のモジュールでは、newPet
を作業ユニットのプロパティから抽出するだけです。
Pet newPet = (Pet) uow.getProperty("NEW PET"); newPet.setType("Dog");
一致する問合せは、オブジェクトが作成されているかどうか、または基準が動的かどうかがわからない場合に最適です。
ただし、オブジェクトの数量が限定され、かつ明白である場合は、作業ユニットのプロパティの使用が、単純で効率的なソリューションとなります。
3層アプリケーションでは、クライアントとサーバーがRMIやCORBAなどのシリアライズ・メカニズムを使用してオブジェクトを交換します。クライアントがオブジェクトを変更し、そのオブジェクトをサーバーに返した場合、このシリアライズ・オブジェクトを作業ユニットに直接登録することはできません。サーバー上で、シリアライズ・オブジェクトをセッション・キャッシュの元のオブジェクトとマージする必要があります。
表115-2に示すように、作業ユニットのメソッドを使用して、デシリアライズ・オブジェクトをセッション・キャッシュにマージできます。各メソッドは、シリアライズ・オブジェクトを引数としてとり、元のオブジェクトを返します。
これを実行する前に、ソース・オブジェクトがセッション・キャッシュにあることを確認する必要があります。まだオブジェクトを持たないセッション・キャッシュにデシリアライズ・オブジェクトをマージしようとすると、ディスクリプタ例外が発生します。これを回避するには、デシリアライズ・オブジェクトが表すオブジェクト・インスタンスを最初に読み取ることをお薦めします。コーディネートされたキャッシュを使用している場合、またはアプリケーションがクラスタで稼働している場合は、マージするセッションには、まだ元のオブジェクトが含まれていない場合があります。読取り操作を最初に実行して、マージ前にオブジェクトがキャッシュ内にあることを確認します。
表115-2 作業ユニットのマージ・メソッド
メソッド | 目的 | 使用する場合 |
---|---|---|
|
シリアライズ・オブジェクトとその私有された部分のすべて(シリアライズ・オブジェクトから独立したオブジェクトへの非プライベート参照を除く)を作業コピーのクローンにマージします。 |
クライアントがオブジェクトを編集したがそのリレーションシップは編集していない場合、または独立したリレーションシップを一時としてマークした場合。 |
|
シリアライズ・オブジェクトとすべての参照を作業コピーのクローンにマージします。 |
クライアントがオブジェクトとそのリレーションシップのターゲットを編集したが、まだ属性を一時としてマークしていない場合。 |
|
ダイレクト・マッピングによってマップされた属性に対するシリアライズ・オブジェクトの変更内容を、作業コピーのクローンにマージします。 |
クライアントがオブジェクトのダイレクト属性のみを編集した場合、またはオブジェクトのリレーションシップをすべて一時としてマークした場合。 |
|
シリアライズ・オブジェクトとそれに接続されているものすべて(シリアライズ・オブジェクトがルートになっているオブジェクト・ツリー全体)を作業コピーのクローンにマージします。 |
クライアントがオブジェクトのすべてのリレーションシップを走査し、変更する場合。 注意: |
3層クライアントが非常に複雑な場合は、TopLinkのリモート・セッションの使用を検討してください(87.9項「リモート・セッション」を参照)。リモート・セッションではマージが自動的に処理され、クライアントで作業ユニットを使用できるようになります。
クローンは、既存のオブジェクトと新規オブジェクトの両方にマージできます。クローンはキャッシュ内に現れず、主キーのない場合もあるため、作業ユニット内で1回しかマージできません。新規オブジェクトを2回以上マージする必要がある場合は、作業ユニットのsetShouldNewObjectsBeCached
メソッドをコールし、オブジェクトに有効な主キーがあることを確認します。これにより、オブジェクトを登録できます。
例115-5は、クライアントから受信した、対応するシリアライズ・オブジェクト(rmiClone
)に含まれている変更内容で元のオブジェクトを更新する方法の1つを示します。
例115-5 シリアライズ・オブジェクトのマージ
update(Object original, Object rmiClone) { original = uow.registerObject(original); uow.mergeCloneWithRefereneces(rmiClone); uow.commit(); }
詳細は、17.2.4.7項「インダイレクション、シリアライズおよびデタッチ」を参照してください。
コミット時に、作業ユニットとそのコンテンツは期限切れとなります。トランザクションが失敗し、ロールバックしても、作業ユニットとそのクローンは使用しないでください。
ただし、TopLinkには、作業ユニットとそのクローンを引き続き使用できるようにする次のAPIが用意されています。
commitAndResumeOnFailure
: 作業ユニットをコミットします。トランザクションのコミットが成功した場合、作業ユニットは期限切れになります。ただし、トランザクションのコミットが失敗した場合、作業ユニットとそのクローンを無効にしません。このメソッドを使用すると、失敗した作業ユニットに登録されているオブジェクトを変更して、トランザクションのコミットを再試行することができます。
同一の小さなデータセットに対して変更が繰り返されるアプリケーション内でのみ作業ユニットを再開します。別のデータセットへアクセスしている間に同一の作業ユニットを再利用すると、パフォーマンスが低下します。
例115-6は、commitAndResume
メソッドの使用方法を示します。
例115-6 commitAndResumeメソッドの使用
UnitOfWork uow = session.acquireUnitOfWork(); PetOwner petOwnerClone = (PetOwner)uow.readObject(PetOwner.class); petOwnerClone.setName("Mrs. Newowner"); uow.commitAndResume(); petOwnerClone.setPhoneNumber("KL5-7721"); uow.commit();
commitAndResume
コールにより、次のSQLが生成されます。
UPDATE PETOWNER SET NAME = 'Mrs. Newowner' WHERE (ID = 400)
その後、commit
コールにより、次のSQLが生成されます。
UPDATE PETOWNER SET PHN_NBR = 'KL5-7721' WHERE (ID = 400)
特定の状況では、作業ユニット内のクローンに加えられた変更の一部またはすべてを破棄し、作業ユニット自体は破棄しないという処理が必要なことがあります。次のオプションは、作業ユニットのすべてまたは一部を回復するためのものです。
revertObject
: 作業ユニット内の特定の作業コピーのクローンに対する変更を破棄します。
revertAndResume
: バックアップ・コピーのクローンを使用して、すべてのクローンを元の状態にリストアし、新規オブジェクトを登録解除して、削除されたオブジェクトを回復します。
作業ユニットを別の(ネストした)作業ユニット内で使用したり、2つ以上の作業ユニットを同じオブジェクトに対して並行して使用したりすることができます。
この項の内容は次のとおりです。
並行して機能する複数の作業ユニットを開始するには、セッションに対してacquireUnitOfWork
メソッドを複数回コールします。作業ユニットはそれぞれ独立して機能し、独自のキャッシュを保持します。
作業ユニットをネストさせるには、親作業ユニットに対してacquireUnitOfWork
メソッドをコールします。そうすることで、独自のキャッシュを持つ子作業ユニットが作成されます。子作業ユニットがコミットされると、データベースではなく親作業ユニットが更新されます。親がコミットされない場合、子に加えられた変更はデータベースに書き込まれません。
TopLinkでは、最も外側の作業ユニットがコミットされるまで、データベースまたはキャッシュは更新されません。親作業ユニットをコミットするには、子をコミットまたは解放する必要があります。
ある作業ユニットの作業コピーのクローンは、別の作業ユニットでは無効です。このことは、内側の作業ユニットと外側の作業ユニットの間でも変わりません。オブジェクトが使用されるすべてのレベルの作業ユニットで、オブジェクトを登録する必要があります。
例115-7は、ネストした作業ユニットの使用方法を示します。
例115-7 ネストした作業ユニットの使用
UnitOfWork outerUOW = session.acquireUnitOfWork(); Pet outerPetClone = (Pet)outerUOW.readObject(Pet.class); UnitOfWork innerUOWa = outerUOW.acquireUnitOfWork(); Pet innerPetCloneA = (Pet)innerUOWa.registerObject(outerPetClone); innerPetCloneA.setName("Muffy"); innerUOWa.commit(); UnitOfWork innerUOWb = outerUOW.acquireUnitOfWork(); Pet innerPetCloneB = (Pet)innerUOWb.registerObject(outerPetClone); innerPetCloneB.setName("Duffy"); innerUOWb.commit(); outerUOW.commit();
作業ユニットのメソッドexecuteNonSelectingCall
を使用するか、DataModifyQuery
を実行すると、作業ユニット内でネイティブSQLを実行するか、ストアド・プロシージャを起動できます。これにより、作業ユニットはそのデータベース・トランザクションを早期に開始でき、データをただちにコールできます。
作業ユニットを解放すると、データベースの変更がロールバックされます。作業ユニットをコミットし、これを完了すると、作業ユニットによりデータベースに対して変更内容がコミットされます。
DataModifyQuery
は、作業ユニットまたはデータベース・セッションのでのみ実行できます。DataModifyQuery
は、クライアントまたはサーバー・セッションでは直接実行できません。
DataReadQuery
またはセッション・メソッドのexecuteSelectingCall
は、データを変更しないため、任意のセッション・タイプで実行または使用できます。
例115-8は、作業ユニットのメソッドexecuteNonSelectingCall
とともにSQLCall
を使用する方法を示します。
警告: 検証されていないSQL文字列をメソッドに渡せるようにすると、SQLインジェクション攻撃に対してアプリケーションが脆弱になります。 |
114.7項「オブジェクトの削除」では、オブジェクト・モデルおよびスキーマ内のマッピングと外部キーに基づいて、常にSQLが正しく配置される(並べ替えられる)と説明しています。次の実行方法がわかっている場合は、削除操作の順序を制御できます。
デフォルトでは、参照整合性を維持するために、削除操作の前に挿入操作と更新操作が最初に実行されます。この方法をお薦めします。
削除後にかわりのオブジェクトを挿入することで、一意制約を持つオブジェクトを置き換える必要がある場合は、削除操作の前に挿入操作が行われると、制約に違反してしまう可能性があります。この場合、setShouldPerformDeletesFirst
をコールして、挿入操作の前に削除操作が行われるようにします。
TopLinkで削除順序の決定に使用される制約は、1対1マッピングと1対多マッピングから推論されます。そのようなマッピングがない場合は、ディスクリプタのaddConstraintDependencies(Class)
メソッドを使用して、TopLinkに制約情報を追加することができます。
たとえば、AはBを含み(1対多、私有)、BはCとの間に1対1の非プライベート・リレーションシップを有するというように、オブジェクトが構成されているとします。Aを削除(その際に、Aに含まれているBも削除)する際、Bを削除する前に、Bの一部(すべてではない)については関連オブジェクトCを削除します。
次のソリューションがあります。
addConstraintDependencies
メソッドなしでdeleteAllObjects
メソッドを使用できます(115.10.3項「addConstraintDependenciesメソッドなしでdeleteAllObjectsメソッドを使用する方法」を参照)。
addConstraintDependencies
メソッドとともにdeleteAllObjects
メソッドを使用できます(115.10.4項「addConstraintDependenciesメソッドとともにdeleteAllObjectsメソッドを使用する方法」を参照)。
最初のオプションでは、1対多(AからB)のリレーションシップを私有としないでください。Aオブジェクトを削除する際に、Aオブジェクトに含まれるすべてのBオブジェクト、およびCオブジェクトを削除する必要があります。たとえば、次のように指定します。
uow.deleteObject(existingA);
uow.deleteAllObjects(existingA.getBs());
// delete one of the Cs
uow.deleteObject(((B) existingA.getBs().get(1)).getC());
このオプションでは、次のSQLが生成されます。
DELETE FROM B WHERE (ID = 2) DELETE FROM B WHERE (ID = 1) DELETE FROM A WHERE (ID = 1) DELETE FROM C WHERE (ID = 1)
2番目のオプションでは、1対多(AからB)のリレーションシップを私有のまま維持し、AからCへの制約依存性を追加します。たとえば、次のように指定します。
session.getDescriptor(A.class).addConstraintDependencies(C.class);
削除コードは次のようになります。
uow.deleteObject(existingA);
// delete one of the Cs
uow.deleteObject(((B) existingA.getBs().get(1)).getC());
このオプションでは、次のSQLが生成されます。
DELETE FROM B WHERE (A = 1) DELETE FROM A WHERE (ID = 1) DELETE FROM C WHERE (ID = 1)
どちらの場合も、BオブジェクトはAとCより前に削除されます。主な違いは、2番目のオプションでは関連しているBがすべてAから削除されるということがわかっているため、生成されるSQL文の数が少ないことです。
ディスクリプタがオプティミスティック・バージョン・ロック・ポリシー(16.4.1項「オプティミスティック・バージョン・ロック・ポリシー」を参照)またはフィールド・ロック・ポリシー(16.4.4項「オプティミスティック・フィールド・ロック・ポリシー」を参照)を使用するよう構成されている場合は、作業ユニットのメソッドforceUpdateToVersionField
を使用して、次の問題の一方または両方を解決します。
ユーザーがトランザクションのオブジェクトを変更していなくても、トランザクションで読み取ったオブジェクトが登録後に変更された場合(115.11.1項「オプティミスティック読取りロックのチェックの強制方法」を参照)、コミット時にOptimisticLockingException
をスローする場合。
オブジェクトのバージョン・フィールドが更新されない方法でトランザクションのオブジェクトを変更し、バージョン・フィールドを介して変更される別のスレッドにアラートする場合(115.11.2項「バージョン・フィールドの更新の強制方法」を参照)。
たとえば、独自のデータベース表を持つ私有のオブジェクトを変更しても、デフォルトで親オブジェクトのバージョン・フィールドが更新されない場合などです。この場合、forceUpdateToVersionField
を使用して親のバージョン・フィールドを更新できます。
この代替方法として16.4.2項「オプティミスティック・バージョン・ロック・ポリシーとカスケード」があります。
コミット操作の前にforceUpdateToVersionField
構成をオブジェクトから削除するには、作業ユニットのメソッドremoveForceUpdateToVersionField
を使用します(115.11.3項「forceUpdateToVersionField構成を無効にする方法」を参照)。
作業ユニットを使用してオブジェクトを読み取る際、オブジェクトを変更しないかぎり、オプティミスティック・ロック・チェックはコミット時にそのオブジェクトに適用されません。ただし、オブジェクトが読み取られてから、自分でオブジェクトを変更していない場合でもその状態が変更された場合、トランザクションを失敗させる必要がある場合があります。
例115-9は、中央銀行のプライム・レートを1.25倍してローン利率を更新するトランザクションを示します。トランザクションでは、コミット時に中央銀行のプライム・レートに対してオプティミスティック読取りロックを実行し、トランザクションの開始以後、プライム・レートが変更されていないことを確認します。この例のトランザクションでは、変更されていないオブジェクト(中央銀行のプライム・レート)のバージョンを上げません。
例115-9 バージョンを上げないオプティミスティック読取りロック
try { UnitOfWork uow = session.acquireUnitOfWork(); MortgageRate cloneMortgageRate = (MortgageRate) uow.registerObject(mortgageRate); CentralPrimeRate cloneCentralPrimeRate = (CentralPrimeRate) uow.registerObject(CentralPrimeRate); // change the Mortgage Rate cloneMortgageRate.setRate(cloneCentralPrimeRate.getRate() * 1.25); // optimistic read lock check on Central prime rate with no version update uow.forceUpdateToVersionField(cloneCentralPrimeRate, false); uow.commit(); } catch(OptimisticLockException exception) { // refresh the out-of-date object session.refreshObject(exception.getObject()); // Retry }
オプティミスティック・ロックとバージョン・フィールドの更新の両方を強制実行する別の例については、115.11.2項「バージョン・フィールドの更新の強制方法」の例115-10を参照してください。
作業ユニットでは、フィールドへ直接マッピング属性または集約オブジェクト・マッピング属性が変更されるときに、オブジェクトが変更されたと判断されます。ソース・オブジェクトに関連するオブジェクトの追加、削除または変更によって、作業ユニット上ではソース・オブジェクトは変更されません。つまり、リレーションシップが1対多または1対1のターゲット外部キー・マッピングで変更される場合でも、デフォルトでは、影響を受けるオブジェクトのバージョン・フィールドは(ある場合)、変更されません。
データベース・バージョンがキャッシュ・バージョンよりも新しく、(ディスクリプタのメソッドonlyRefreshCacheIfNewerVersion
を使用)このようなリレーションシップが変更される場合のみ、キャッシュをリフレッシュするようディスクリプタを構成すると、オブジェクトは完全にリフレッシュできなくなります。バージョンが変更されていないため、作業ユニットのメソッドrefreshObject
と、true
に設定されたrefreshIdentityMapResults
オプションを持つ問合せでもオブジェクトをリフレッシュできません。
作業ユニット・コピーのクローンとtrue
値の両方で渡される作業ユニットのメソッドforceUpdateToVersionField
を使用すると、変更が加えられたときにオブジェクトのバージョン・フィールドが確実に更新されます。また、失効したデータの書込みを防ぐために、リフレッシュ前のオブジェクトの変更によってオプティミスティック・ロック例外が発生します(115.11.1項「オプティミスティック読取りロックのチェックの強制方法」を参照)。
例115-10および例115-11は、同一のカスタマ・オブジェクトに同時にアクセスする個別のスレッドで実行されるトランザクションを示します。作業ユニットのメソッドforceUpdateToVersionFieldを使用すると、あるスレッド内のカスタマ・オブジェクトに対する変更が、他のスレッドで確実に検出されます。
例115-10は、請求書スレッドで顧客に対する請求書を計算するトランザクションを示します。例115-11は、別のスレッドであるサービス・スレッドで同じ顧客にサービスを追加するか、現在のサービスを変更するトランザクションを示します。どちらの場合も、サービス・スレッドでは、請求書スレッドに通知する必要があり、これにより請求書が変更されます。
例115-10 バージョンを上げるオプティミスティック読取りロック: サービス・スレッド
/* The following code represents the service thread. Notice that the thread forces a version update */ try { UnitOfWork uow = session.acquireUnitOfWork(); Customer cloneCustomer = (Customer uow.registerObject(customer); Service cloneService = (Service uow.registerObject(service); // add a service to customer cloneService.setCustomer(cloneCustomer); cloneCustomer.getServices().add(cloneService); /* Modify the customer version to inform other application that the customer has changed */ uow.forceUpdateToVersionField(cloneCustomer, true); uow.commit(); } catch (OptimisticLockException exception) { // refresh out-of-date object session.refreshObject(exception.getObject()); // retry }
例115-11 バージョンを上げるオプティミスティック読取りロック: 請求書スレッド
/* The following code represents the invoice thread, and calculates a bill for the customer. Notice that it does not force an update to the version */ try { UnitOfWork uow = session.acquireUnitOfWork(); Customer cloneCustomer = (Customer) uow.registerObject(customer); Invoice cloneInvoice = (Invoice) uow.registerObject(new Invoice()); cloneInvoice.setCustomer(cloneCustomer); // calculate service charge int total = 0; for(Enumeration enum = cloneCustomer.getServices().elements(); enum.hasMoreElements()) { total += ((Service) enum.nextElement()).getCost(); } cloneInvoice.setTotal(total); /* Force optimistic lock checking on the customer to guarantee a valid calculation */ uow.forceUpdateToVersionField(cloneCustomer, false); uow.commit(); } catch(OptimisticLockException exception) { // refresh the customer and its privately owned parts session.refreshObject(cloneCustomer); /* If the customer's services are not privately owned then use a ReadObjectQuery to refresh all parts */ ReadObjectQuery query = new ReadObjectQuery(customer); /* Refresh the cache with the query's result and cascade refreshing to all parts including customer's services */ query.refreshIdentityMapResult(); query.cascadeAllParts(); // refresh from the database query.dontCheckCache(); session.executeQuery(query); // retry }
オブジェクトに適用したforceUpdateToVersionField
構成は、作業ユニットの存続期間中は有効のまま残ります。トランザクションをコミットした後は、forceUpdateToVersionField
構成は適用されなくなります。
コミット前にforceUpdateToVersionField
構成をオブジェクトから削除するには、作業ユニットのメソッドremoveForceUpdateToVersionField
を使用します。TopLinkでは、このトランザクション内でオブジェクトを変更しないかぎり(つまり、そのフィールドへ直接マッピング属性または集約オブジェクト・マッピング属性を変更しないかぎり)、オプティミスティック読取りロックはオブジェクトに適用されません。
データ・ソースの変更は、通常監査が必要です。ユーザーがデータ・ソースに対して変更をコミットすると、アプリケーションではデータ・ソースのフィールドが更新され、変更したユーザーと日付が記録されます。
たとえば、データベース・スキーマの各行には、フィールドlastUpdateBy
(変更をコミットしたユーザーの名前を記録する)とlastUpdateOn
(変更した日付を記録する)が含まれているとします。
UnitOfWork
のメソッドsetProperty
を使用して、UnitOfWork
を取得したユーザーの名前を記録し、AboutToUpdateEvent
ディスクリプタ・イベントのディスクリプタ・イベント・リスナーを実装できます。このイベントは、プロパティを抽出し、lastUpdateBy
およびlastUpdateOn
フィールドを更新します。
詳細は、次を参照してください。
アプリケーション・サーバーの外部トランザクション・サービスで管理されるトランザクションをサポートするため、TopLinkでは、サポートされているサーバーに対して外部接続プールと外部トランザクション・コントローラ・クラスをサポートしています。これにより、外部トランザクション・サービス・サポートをアプリケーションに組み込むことができ、サーバーによって外部的に管理されるトランザクションで作業ユニットを使用できます。詳細は、113.1.2項「作業ユニット・トランザクション境界」を参照してください。
作業ユニットと外部トランザクション・サービスを統合するには、次を使用できるようにする必要があります。
外部トランザクション・コントローラ(89.9項「サーバー・プラットフォームの構成」を参照)
外部接続プール(97.4項「外部接続プーリングの構成」を参照)
外部接続プールおよび外部トランザクション・コントローラ・サポートを構成した後、TopLinkアプリケーションで通常のように作業ユニットを使用すると、例外はほとんど発生しません。この項で説明する例外は次のとおりです。
作業ユニットは、外部トランザクション・サービスを使用している場合でもデータ・ソースに対する変更をコミットする際に使用します。特定のトランザクションに作業ユニットが1つのみ関連付けられるようにするには、例115-12に示すように、getActiveUnitOfWork
メソッドを使用して作業ユニットを取得します。
注意: 外部トランザクション・サービスを使用してデータ・ソースに対する変更をコミットする方法は他にもありますが、getActiveUnitOfWork メソッドの使用をお薦めします。 |
getActiveUnitOfWork
メソッドは、次のように既存の外部トランザクションを検索します。
アクティブ外部トランザクションが存在し、すでに作業ユニットがトランザクションに関連付けられている場合、その作業ユニットを返します。
作業ユニットが関連付けられていないアクティブ外部トランザクションが存在する場合、新規作業ユニットを取得し、それをトランザクションに関連付けて返します。
処理中のアクティブ外部トランザクションが存在しない場合、nullを返します。
TopLinkがnull
でない作業ユニットを返す場合は、通常どおりに正確に使用してください。ただし、commit
メソッドはコールしません(115.13.2項「外部トランザクションが存在する場合の作業ユニットの使用方法」を参照)。
TopLinkがnull
の作業ユニットを返す場合は、UserTransaction
インタフェースを通じて明示的に外部トランザクションを開始します。
例115-12 外部トランザクション・サービスを持つ作業ユニットの使用
// Read in any pet
Pet pet = (Pet)clientSession.readObject(Pet.class);
UnitOfWork uow = clientSession.getActiveUnitOfWork();
if (uow == null) {
throw new RuntimeException("No transaction started");;
}
Pet petClone = (Pet)uow.registerObject(pet);
petClone.setName("Furry");
getActiveUnitOfWork
がnull
ではない作業ユニットを返した場合、既存の外部トランザクションに関連付けられています。作業ユニットは通常どおり使用します。
外部トランザクションは作業ユニットによって開始されたのではないため、作業ユニットに対してcommit
を発行しても、外部トランザクションはコミットされません。作業ユニットは、トランザクションを開始したアプリケーションまたはコンテナに従います。外部トランザクションがコンテナによってコミットされるときに、TopLinkはトランザクションのコミット中のキー・ポイントで同期化のコールバックを受信します。
作業ユニットは、beforeCompletion
コールバックを受信すると、必要なSQLをデータベースに送信します。
作業ユニットは、afterCompletion
コールバックから受信したBoolean
引数を使用して、コミットが成功した(true
)か失敗した(false
)かを判断します。
トランザクションのコミットが成功した場合、作業ユニットは変更内容をセッション・キャッシュにマージします。トランザクションのコミットが失敗した場合、作業ユニットは変更内容を破棄します。
図115-2は、外部トランザクションが存在する場合の作業ユニットのライフ・サイクルを示します。
getActiveUnitOfWork
メソッドがnullの作業ユニットを返した場合、既存の外部トランザクションはありません。新規外部トランザクションを開始する必要があります。
そのためには、UserTransaction
インタフェースを使用して外部トランザクションを明示的に開始するか、サーバー・セッションに対しacquireUnitOfWork
メソッドを使用して新規作業ユニットを取得します。
作業ユニットは通常どおり使用します。
登録されているオブジェクトの変更が完了したら、UserTransaction
インタフェースを通じて明示的に、または作業ユニットのcommit
メソッドをコールすることで、トランザクションをコミットする必要があります。
その後、トランザクション同期化のコールバックが起動され、そのコールバックに基づいて、データベースの更新とキャッシュのマージが行われます。
この項では、外部トランザクションでの次の2つの一般的な問題と、その処理方法について説明します。
外部トランザクションのコミットの際、外部トランザクション・サービスでは、一定の時間内で各トランザクションの所有者がトランザクション全体から一部をコミットすることを想定しています。個々のトランザクションがこのタイムアウトを超えると、外部トランザクション・サービスが特定のトランザクションを失敗させ、ロールバックします(またはロールバックのマーク付けのみが行われます)。
トランザクションが大きく、トランザクションのコミットが外部トランザクション・サービスのタイムアウトを超過する可能性がある場合は、UnitOfWork
のメソッドwriteChanges
を使用して、外部トランザクションをコミットする前にデータ・ソースに変更を書き込みます。これにより、グローバル・トランザクションで自分のトランザクションのコミットにかかる時間を削減できます。
UnitOfWork
のメソッドwriteChanges
の制限と警告を含めた詳細は、115.3項「コミット前の変更内容の書込み」を参照してください。
外部トランザクション・サービスを持つ作業ユニットを使用する際、アプリケーションのスレッドがUnitOfWork
のメソッドcommit
をコールして返されてからしばらく経過しないと、コミット例外がスローされないことがあります。この場合は、コンテナ管理のトランザクション(CMT)のクライアントのコールに対してコミット例外がスローされ、クライアントでこのサーバー側の失敗の処理が強制実行されます。
UnitOfWork
のメソッドwriteChanges
を使用して、外部トランザクションのコミット前にデータ・ソースに対して変更を書き込むことができます。これにより、外部トランザクション・サービスでのグローバル・トランザクションのコミット時にスローされる可能性のあるほとんどの例外を、アプリケーションのスレッドで捕捉して処理することができます。
UnitOfWork
のメソッドwriteChanges
の制限と警告を含めた詳細は、115.3項「コミット前の変更内容の書込み」を参照してください。
一般的な作業ユニットの例外処理の詳細は、115.16.4項「例外の処理方法」を参照してください。
永続Beanに対するすべての変更は、トランザクションのコンテキストで実行される必要があります。
トランザクションを使用しないでエンティティBeanを変更すると、一貫性のない状態になることがあり、TopLinkキャッシュの値が破損する可能性があります。このため、TopLinkでは、トランザクションがアクティブでない場合にBeanのリモート・インタフェースを介してBeanを変更することはサポートしていません。これを試行した場合、TopLinkでは、変更はデータベースに書き込まれません。
TopLinkでは、トランザクションを使用せずにエンティティBeanのリモート・インタフェースを介してこのエンティティBeanは変更できませんが、このホーム・インタフェースでは、トランザクションを使用せず、基礎となるデータベースの状態を変更するメソッドを起動できます。たとえば、トランザクションを使用せずに、エンティティBeanのホーム・インタフェースで削除および作成のメソッドを起動できます。
TopLinkトランザクションとコンテナ管理の永続性を使用する作業ユニットと統合するには、次のことを考慮する必要があります。
CMPトランザクション属性(115.14.1項「CMPトランザクション属性の使用方法」を参照)
ローカル・トランザクション(115.14.2項「ローカル・トランザクションの使用方法」を参照)
非遅延変更(115.14.3項「非遅延変更の使用方法」を参照)
永続Beanに対するすべての変更を確実にトランザクションのコンテキストで実行するには、トランザクション属性がBeanデプロイメント・ディスクリプタで適切に指定されている必要があります。
トランザクションは、クライアントまたはコンテナによって制御されます。
クライアントによって制御されるトランザクションは、javax.transaction.UserTransaction
インタフェースを使用してアプリケーションで明示的に開始されます。
コンテナによって制御されるトランザクションは、クライアントによって制御されるトランザクションが存在しない場合にBeanメソッドを起動する際に、トランザクション属性の構成を満たすように、コンテナによって暗黙的に開始されます。
表115-3は、(トランザクションがある場合)トランザクション属性の構成方法に応じてEJBメソッド起動でどのトランザクションが使用されるか、およびクライアントによって制御されるトランザクションがメソッドの起動時に存在するかどうかを示します。
表115-3で「トランザクションを使用しない」として識別されている条件下では、エンティティBeanを変更しないことをお薦めします。また、Supports
トランザクション属性では、クライアントが明示的にトランザクションを指定しない場合は常に非トランザクション状態になるため、この属性を使用しないことをお薦めします。
表115-3 トランザクション属性別のEJBトランザクションの状態
トランザクション属性 | クライアントによって制御されるトランザクションが存在する | クライアントによって制御されるトランザクションが存在しない |
---|---|---|
|
トランザクションを使用しない |
トランザクションを使用しない |
|
クライアントによって制御されるトランザクションを使用 |
トランザクションを使用しない |
|
クライアントによって制御されるトランザクションを使用 |
コンテナによって制御されるトランザクションを使用 |
|
クライアントによって制御されるトランザクションを使用 |
コンテナによって制御されるトランザクションを使用 |
|
クライアントによって制御されるトランザクションを使用 |
例外が発生 |
|
例外が発生 |
トランザクションを使用しない |
使用しているEJBコンテナによっては、コンテナによって制御されるトランザクションを使用しないで書き込むことができます(115.13項「作業ユニットと外部トランザクション・サービスの統合」を参照)。この場合、TopLinkでは、ローカル・トランザクションという独自のトランザクションを自動的に使用します(115.14.2項「ローカル・トランザクションの使用方法」を参照)。
OC4Jなど、一部のEJBコンテナは、アクティブなJTAトランザクションを使用しない書込みをサポートしています。
トランザクション属性(115.14.1項「CMPトランザクション属性の使用方法」を参照)がSupports
、NotSupported
またはNever
に設定されている場合に、BeanメソッドをJTAトランザクション外部で実行すると、TopLinkではローカルの作業ユニット内で操作を実行し、メソッドの最後に作業ユニットをコミットします。この作業ユニットはローカル・トランザクションと呼ばれます。
この理由は、EJB仕様のセマンティックの更新がこれらのシナリオで定義されないまま残っており、適切なトランザクション・モデルでは、データ変更を可能にする前にトランザクションをアクティブにすることが要求されているためです。また、セッション・キャッシュで整合性を保つため、変更操作を作業ユニット内で行う必要があります。
OC4Jなど、一部のEJBコンテナでは、非遅延変更をサポートしています。これは、エンティティBeanの永続フィールドが変更されると、データ・ソースを即時に変更する機能です。
非遅延変更を使用すると、一部のEJBコンテナ(OC4Jなど)のネイティブの動作との下位互換性が得られます。また、トランザクションの一時的な状態を使用してトリガーやストアド・プロシージャなどのためにデータベースとエンティティの変更を同期化するアプリケーションや、同一の主キーを使用して行を削除および作成するアプリケーション、またはトランザクション内の一時的な状態を使用してその他の複雑な問合せを行うアプリケーションなど、高度なアプリケーションにも対応可能となります。
非遅延変更の短所は、データ・ソースとのインタラクションの数が最大になるため、方法としては最も効率が悪いことです。
デフォルトでは、TopLinkはコミット時まですべての変更を遅らせます。これは、データ・ソースとのインタラクションを最小限に抑えるために、最も効率的な方法です。
詳細は、16.2.3.1項「非遅延変更」を参照してください。
TopLinkアプリケーションで特定のデータベース・トランザクション分離レベルを実現すると、DatabaseLogin
のメソッドsetTransactionIsolation
のみを使用するよりも多くのことを実行できます。
通常のTopLinkアプリケーションと、一般に永続性を必要とするJava EEアプリケーションでは、いつデータベース・トランザクション分離レベルを適用するのか、特定のデータベース・トランザクション分離をどこまで実現できるのかということに、様々な要因が影響します。
この項では、これらの要因を説明し、これに対して可能なかぎり各データベース・トランザクション分離レベルを実現できるようTopLinkを構成して使用するガイドラインを示します。
この項の内容は次のとおりです。
この項では、TopLinkアプリケーションで特定のデータベース・トランザクション分離レベルをどの程度実現できるかに影響する、いくつかの重要な要因と変数について説明します。これらの要因には次のものがあります。
多くの場合、TopLinkアプリケーションは、データベースを更新できる唯一のアプリケーションではありません。TopLink以外の外部アプリケーションでも、いつでもデータベースを更新できます。
この場合、TopLinkアプリケーションでは、ObjectLevelReadQuery
のメソッドrefreshIdentityMapResult
(108.16.5項「キャッシュのリフレッシュ方法」を参照)またはDescriptor
のメソッドalwaysRefreshCache
およびdisableCacheHits
(119.9項「キャッシュ・リフレッシュ機能の構成」を参照)を使用する必要があります。
詳細は、115.15.1.5項「キャッシュ・アクセスの管理」を参照してください。
外部アプリケーションでデータベースのバージョン・フィールドを更新できる場合は、TopLinkアプリケーションでは、alwaysRefreshCache
をDescriptor
のメソッドonlyRefreshCacheIfNewerVersion
とともに使用して、リフレッシュ操作が必要な場合のみ実行されるようにします。または、推奨される別の方法として、ディスクリプタの独立キャッシュ・オプション(102.2.7項「キャッシュの分離」を参照)やキャッシュの無効化(102.2.5項「キャッシュの無効化」を参照)を使用します。
それぞれが独自のアプリケーション・サーバー・インスタンスで稼働する複数のTopLinkアプリケーションが、102.3項「キャッシュ・コーディネーション」で説明するように、分散コーディネート・キャッシュを使用するよう構成されているとします。TopLinkアプリケーション・インスタンスでは、変更が別のキャッシュに配布される前に、独自のキャッシュに対する変更をコミットします。キャッシュ・コーディネーションは即時性がないため、あるTopLinkアプリケーションのインスタンスが、キャッシュ・コーディネーション・メッセージを受け取る前に、キャッシュから古いバージョンのオブジェクトを読み取る可能性があります。
TopLinkアプリケーションに最新バージョンのオブジェクトを提供するには、ディスクリプタの独立キャッシュ・オプション(102.2.7項「キャッシュの分離」を参照)やキャッシュの無効化(102.2.5項「キャッシュの無効化」を参照)を使用します。
失効したデータを回避するには、Descriptor
のメソッドalwaysRefreshCache
およびdisableCacheHits
を使用します。disableCacheHits
メソッドの詳細は、115.15.1.5項「キャッシュ・アクセスの管理」を参照してください。
注意: Descriptor のメソッドalwaysRefreshCache およびdisableCacheHits を使用すると、データベースへのアクセス頻度が高くなります。やむを得ない場合のみ使用してください。 |
DatabaseLogin
のメソッドsetTransActionIsolation
を使用して、TopLinkが取得するデータベース接続に適用するデータベース・トランザクション分離レベルを構成します。たとえば、次のようになります。
databaseLogin.setTransactionIsolation(DatabaseLogin.TRANSACTION_SERIALIZABLE);
このメソッドは、CMPの場合と同様に、内部トランザクションおよび外部トランザクションの両方の場合において、内部接続プールまたは外部接続プールのいずれかから取得したデータベース接続に対してデータベース読取り操作と書込み操作の両方で使用するトランザクション分離レベルを設定します(87.2.1.2項「接続プール」を参照)。
ただし、TopLinkの場合、デフォルトの読取り操作では、一般的に外部接続プールから取得する書込み操作か、データベース全体を回避するキャッシュを使用する可能性がある書込み操作とは異なるデータベース接続を使用します。このように、TopLinkの場合、トランザクションまたは作業ユニット内で読取り操作を実行しても、デフォルトの読取り操作は常にトランザクションまたは作業ユニット外部で実行されます。データベース・トランザクション分離は、読取り操作と書込み操作の両方に適用されますが、読取りはトランザクションの一部として実行されません。そのため、読取り操作ではデータベースのトランザクション分離設定をオーバーライドします。
実現対象のトランザクション分離レベルに応じて、同じトランザクション分離を読取り操作と書込み操作の両方に適用する必要がある場合があります。TopLinkで読取り操作と書込み操作に同じ接続を使用するには、特別な処理が必要です。詳細は、115.15.1.4項「書込み接続による読取り」を参照してください。
書込み操作とは別のデータベース接続を使用して読取り操作を実行する、デフォルトのTopLinkを再度コールします(115.15.1.3項「DatabaseLoginのメソッドsetTransactionIsolation」を参照)。ただし、データベース・トランザクション分離の点から見ると、トランザクションとデータベース接続の間には1対1のリレーションシップがあります。つまり、読取り操作を含むすべてのデータベース操作では、特定のデータベース・トランザクション分離レベルを実現するため、同じデータベース接続を使用する必要があります。
一般に、TopLinkで読取り操作を実行する際、書込み接続がすでに存在する場合は、TopLinkではその書込み接続を使用して読取り操作を実行します。これを書込み接続による読取りといいます。書込み接続がまだ存在しない場合は、TopLinkでは別の接続を取得して読取り操作に使用します。
次のいずれかを使用して、事前に書込み接続が割り当てられるようにTopLinkを構成できます。
注意: データベース・トランザクション分離レベルによっては、書込み接続による読取りを使用すると、読取り対象のオブジェクトがロックされる可能性があります。これはパフォーマンスに影響を及ぼし、並行性が損われます。厳密なデータベース・トランザクション分離がどうしても必要な場合以外は、これらの高度なテクニックを使用しないことをお薦めします。 |
詳細は、115.15.1.6項「CMPと外部トランザクション」を参照してください。
ペシミスティック・ロック(ObjectLevelReadQuery
のメソッドacquireLocks
かacquireLocksWithoutWaiting
、またはSession
のメソッドrefreshAndLockObject
)を使用する場合は、TopLinkでは次を実行します。
読取り操作と書込み操作の両方に使用する書込み接続を割り当てる。
常にデータベースから読み取る。
常にデータベース・バージョンを使用してキャッシュを更新する。
このメソッドは拡張APIです。作業ユニットのインスタンスに対してbeginTransactionEarly
をコールする場合、作業ユニットのそのインスタンス全体ですべての読取り操作を実行する必要があります。
このメソッドにより、データベース・トランザクションが即時に開始されます。読み取られるオブジェクトにより、コミット前のデータベースのデータがロックされ、並行性が損われます。
クライアント・セッションは、接続プールまたは排他接続を使用してデータ・ソースにアクセスできます。排他接続を使用するには、ConnectionPolicy
を使用してクライアント・セッションを取得します(90.4.2項「排他接続を使用するクライアント・セッションの取得方法」を参照)
独立クライアント・セッションを使用している場合は(87.5項「独立クライアント・セッション」を参照)、分離データの読取りに排他接続を使用できます。この場合、TopLinkが書込み接続プールから排他接続を取得して分離データの読取りと書込みの両方に使用するよう構成できます。ただし、TopLinkでは、非分離データの読取りについては引き続き読取り接続プールから共有接続を取得します。
詳細は、「排他書込み接続」を参照してください。
デフォルトでは、可能なかぎり多くの共有セッション・キャッシュが使用されます。これにより、並行性が高くなり、パフォーマンスが向上します。ただし、特定のトランザクション分離レベルを実現するには、次のいずれかまたはすべてを使用したキャッシュを回避する必要があります。
このメソッドを使用すると、独立して構成されているディスクリプタのオブジェクトに対する最初の読取り操作では、常にデータベースにアクセスします。共有セッション・キャッシュを回避することにより、キャッシュ・ヒットの無効化や常時リフレッシュを実行に、より複雑なディスクリプタと問合せAPIを使用する必要がなくなります。独立クライアント・セッションの詳細は、87.5項「独立クライアント・セッション」を参照してください。これは、シリアライズ可能なトランザクション分離(115.15.5項「シリアライズ可能な読取りレベルについて」を参照)を実現する際に特に便利です。
このAPIを使用すると、主キーに基づいた問合せでなければデータベースにアクセスします。主キーに基づいた問合せの場合は、まずキャッシュにアクセスします。この場合にキャッシュ全体を回避する方法の詳細は、115.15.1.5.4項「DescriptorのメソッドdisableCacheHits」を参照してください。
このAPIは、常にデータベースにアクセスします。この場合にキャッシュ全体を回避する方法の詳細は、102.4.2項「キャッシュ・リフレッシュAPI」のDescriptor
のメソッドalwaysRefreshCache
の説明を参照してください。
このAPIを使用すると、主キーでのキャッシュ・ヒットが可能となり、読取り問合せを無効にできます。これは、問合せが常にデータベースへアクセスするよう、Descriptor
のメソッドalwaysRefreshCache
とともに使用されます。
これは、オブジェクトが共有セッション・キャッシュに追加されないようにする、問合せレベルの回避方法です。独立クライアント・セッション(115.15.1.5.1項「分離クライアント・セッション・キャッシュ」を参照)を使用すると、より簡単に同じ結果を得られます。
一般に、この項のトランザクション分離に関する情報が、CMPとCMP以外のアプリケーションの両方に適用されますが、次の例外があります。
OC4J以外のアプリケーション・サーバーでCMPを使用したTopLinkアプリケーションを使用する場合は、読取り接続プールと書込み接続プールを別々に使用し、書込み接続のみ外部トランザクションと関連付けるようコンテナを構成することをお薦めします。つまり、読取り接続はトランザクションに関与しないことになります。
注意: OC4Jでは、読取りと書込みに常に同じ接続プールが使用されます。書込みにはこのプールのJTA接続が使用され、読取りにはこのプールの非JTA接続が使用されます。 |
ただし、TopLinkではEJBファインダを問合せの別のタイプとして扱うため、ディスクリプタの構成を使用して、115.15.1.4項「書込み接続による読取り」で説明するオプションを活用できます。たとえば、ディスクリプタを構成してペシミスティック・ロック(119.26項「ロック・ポリシーの構成」を参照)を使用する場合は、そのファインダが起動する際に、書込み接続が割り当てられ、読取り操作と書込み操作の両方で同じ接続が使用されます。
トランザクション・データ・ソースを持つ外部トランザクションの詳細は、96.1.1項「外部管理トランザクション・データ・ソース」を参照してください。
このトランザクション分離レベルの使用はお薦めしません。
一般に、非コミット読取り操作は必要ありません。TopLinkを使用している場合、コミット読取りのトランザクション分離では、非コミット読取りよりもパフォーマンスが向上し、データ整合性が大幅に向上します。
作業ユニットを使用すると、共有セッション・キャッシュ内またはデータベース内のコミット済データのみを読み取ります。
リピータブル・リード操作を実現するには、作業ユニットを使用、作業ユニット内のすべてのオブジェクト(変更するオブジェクトと読取り専用のオブジェクトの両方)を登録、およびObjectLevelReadQuery
のメソッドconformResultsInUnitOfWork
またはDescriptor
のメソッドalwaysConformResultsInUnitOfWork
を使用する必要があります。
これにより、登録したオブジェクトを問い合せるたびに、オブジェクトのバージョンを現在作業ユニット内にあるとおり取得できます。
シリアライズ可能なトランザクション分離をTopLinkで実現するには、独立クライアント・セッション(87.5項「独立クライアント・セッション」を参照)を次のように使用することをお薦めします。
データベース・トランザクション分離をシリアライズ可能として構成します。
独立したオブジェクトを構成します(117.11項「プロジェクト・レベルでのキャッシュ分離機能の構成」または119.13項「ディスクリプタ・レベルでのキャッシュの分離構成」を参照)。
UnitOfWork
のメソッドbeginTransactionEarly
を使用します(115.15.1.4.2項「作業ユニットのメソッドbeginTransactionEarly」を参照)。
シリアライズ可能な書込みについてのみ実行する場合は、オプティミスティック・ロックで十分です。
仮読取りトランザクションを回避するには(つまり、トランザクションを開始してからデータベースに追加された新しいレコードを検出する場合は)、ReadQuery
のメソッドcacheQueryResults
を使用します。
この項では、よくある作業ユニットの問題とデバッグ手法について説明します。内容は次のとおりです。
作業ユニットのエラーとしてよくあるのは、コミット後にクローンを使用し続けることです。通常、クローンは静的変数に格納されるため、開発者はこのオブジェクトがキャッシュ・コピーであると誤解します。その結果、別の作業ユニットでそのオブジェクトが変更されると、開発者はキャッシュ・コピーが更新されないと考えます(作業ユニットではキャッシュ・コピーのみが更新され、古いクローンは更新されません)。
例115-13に示すエラーについて考えてみます。この例では、Pet
のキャッシュ・コピーへのハンドルを取得し、静的CACHE_PET
に格納します。作業コピーのクローンへのハンドルを取得し、静的CLONE_PET
に格納します。将来の作業ユニットで、Pet
が変更されます。
作業ユニットのクローンへのグローバル参照を誤って格納した場合は、通常、将来の作業ユニットでキャッシュ・オブジェクトが変更されたときに、クローンへのグローバル参照が更新されると予想します。更新されるのはキャッシュ・コピーのみです。
例115-13 クローンへのハンドルの誤った使用方法
// Read a Pet from the database, store in static CACHE_PET = (Pet)session.readObject(Pet.class); // Put a clone in a static. This is a bad idea and is a common error UnitOfWork uow = session.acquireUnitOfWork(); CLONE_PET = (Pet)uow.readObject(Pet.class); CLONE_PET.setName("Hairy"); uow.commit(); //Later, the pet is changed again UnitOfWork anotherUow = session.acquireUnitOfWork(); Pet petClone = (Pet)anotherUow.registerObject(CACHE_PET); petClone.setName("Fuzzy"); anotherUow.commit(); // If you incorrectly stored the clone in a static and thought it should be // updated when it is later changed, you would be wrong: only the cache copy is // updated; NOT OLD CLONES System.out.println("CACHE_PET is" + CACHE_PET); System.out.println("CLONE_PET is" + CLONE_PET);
2つのSystem.out
コールにより、次の出力が生成されます。
CACHE_PET isPet type Cat named Fuzzy id:100 CLONE_PET isPet type Cat named Hairy id:100
114.3項「オブジェクトの変更」では、次のコードを実行することにより、クラスの特定のインスタンスを読み取ることができると説明しました。
session.readObject(Class);
また、引数としてオブジェクトをとるreadObject
メソッドもあります。このメソッドは、渡されたオブジェクトの主キーに対してReadObjectQuery
を実行するのと同等です。たとえば、次のコードは後続の例のコードと同等です。
session.readObject(pet);
次のコードは、前のコードと同等です。
ReadObjectQuery query = new ReadObjectQuery(); query.setReferenceClass(Pet.class); ExpressionBuilder builder = new ExpressionBuilder(); Expression exp = builder.get("id").equal(pet.getId()); query.setSelectionCriteria(exp); session.executeQuery(query);
また、主キーに基づいた問合せは、デフォルトで、データベースにアクセスせずにキャッシュの内容を返します。結果として、例115-14に示すように、非常に迅速かつ簡単にオブジェクトのキャッシュ・コピーにアクセスする方法を使用できます。
例115-14 オブジェクトがキャッシュ・オブジェクトであるかどうかのテスト
//Here is a test to see if an object is the cache copy
boolean cached = CACHE_PET == session.readObject(CACHE_PET);
boolean cloned = CLONE_PET == session.readObject(CLONE_PET);
System.out.println("Is CACHE_PET the Cache copy of the object: " + cached);
System.out.println("Is CLONE_PET the Cache copy of the object: " + cloned);
このコードでは、次の出力が生成されます。
Is CACHE_PET the Cache copy of the object: true Is CLONE_PET the Cache copy of the object: false
作業ユニットには、パフォーマンスの分析やコードの問題の追跡を行うためのデバッグ用メソッドがいくつかあります。その中で最も便利なのは、作業ユニット内の既知のオブジェクトの情報をすべて出力するprintRegisteredObjects
です。このメソッドを使用して、登録されているオブジェクトの数を調べ、使用中のオブジェクトが登録されていることを確認します。
このメソッドを使用するには、作業ユニットの取得元セッションについて、ログ・メッセージを有効にする必要があります。セッションのログ・メッセージは、デフォルトでは無効になっています。ログ・メッセージを有効にするには、セッションのlogMessages
メソッドを使用します。ログ・メッセージを無効にするには、例115-15に示すように、セッションのdontLogMessages
メソッドを使用します。
例115-15 作業ユニットの内容のダンプ
session.logMessages(); // Enable log messages UnitOfWork uow = session.acquireUnitOfWork(); Pet petClone = (Pet)uow.readObject(Pet.class); petClone.setName("Mop Top"); Pet pet2 = new Pet(); pet2.setId(200); pet2.setName("Sparky"); pet2.setType("Dog"); uow.registerObject(pet2); uow.printRegisteredObjects(); uow.commit(); session.dontLogMessages(); // Disable log messages
この例では、次の出力が生成されます。
UnitOfWork identity hashcode: 32373 Deleted Objects: All Registered Clones: Key: [100] Identity Hash Code:13901 Object: Pet type Cat named Mop Top id:100 Key: [200] Identity Hash Code:16010 Object: Pet type Dog named Sparky id:200 New Objects: Key: [200] Identity Hash Code:16010 Object: Pet type Dog named Sparky id:200
この項で説明する処理方法の内容は次のとおりです。
TopLinkの例外は、RuntimeException
のインスタンスです。したがって、例外をスローするメソッドをtry
-catch
ブロックの中に配置する必要はありません。
ただし、作業ユニットのcommit
メソッドは、発生する可能性のある問題に対処するためにtry
-catch
ブロックの中でコールする必要があります。
例115-16は、作業ユニットの例外を処理する方法の1つを示します。
例115-16 作業ユニットのコミット例外の処理
UnitOfWork uow = session.acquireUnitOfWork(); Pet petClone = (Pet)uow.registerObject(newPet); petClone.setName("Assume this name is too long for a database constraint"); // Assume that the name argument violates a length constraint on the database. // This will cause a DatabaseException on commit try { uow.commit(); } catch (TopLinkException tle) { System.out.println("There was an exception: " + tle); }
このコードでは、次の出力が生成されます。
There was an exception: EXCEPTION [ORACLEAS TOPLINK-6004]: oracle.toplink.exceptions.DatabaseException
オプティミスティック・ロックを使用している場合、発生した例外はオプティミスティック・ロックに問題があったことを示しているため、コミット時の例外の捕捉は必須です。オプティミスティック・ロックを使用すると、特定のオブジェクトがトランザクションまたは作業ユニットで使用中であっても、そのオブジェクトにすべてのユーザーがアクセスできます。作業ユニットがオブジェクトを変更しようとすると、データベースでは、そのオブジェクトが作業ユニットによって最初に読み取られてから変更されていないことが確認されます。オブジェクトが変更されている場合は、データベースで例外が発生し、作業ユニットはトランザクションをロールバックします。詳細は、113.3.1.2項「ロックと作業ユニット」を参照してください。
外部トランザクション・サービスを使用している場合は、UnitOfWork
コードが返されてからしばらくして、例外がスローされることがあります。UnitOfWork
のメソッドwriteChanges
を使用すると、外部トランザクションがコミットされる前にほとんどの例外を捕捉して処理することができます。詳細は、115.13.4.2項「外部トランザクション・コミットの例外の処理」を参照してください。
作業ユニットでの問合せ結果を、1対多リレーションシップ、および1対1リレーションシップと1対多リレーションシップの組合せ全体で一致させることができます。例115-17は、2つのレベルのリレーションシップ(1対多と1対1)全体に対する問合せを示します。
例115-17 2つのレベルのリレーションシップ全体に対する問合せ
Expression exp = bldr.anyOf("managedEmployees").get("address").get("city").equal("Perth");
デフォルトでは、一致中にスローされた例外は抑止されます。ただし、UnitOfWork
のメソッドsetShouldThrowConformExceptions
を使用して、すべての一致する例外に作業ユニットをスローすることができます。このメソッドにより、次の値のint
引数を1つとります。
0
: 一致例外をスローしない(デフォルト)
1
: すべての一致例外をスローする
一致とインメモリー問合せを使用する際の例外処理のカスタマイズの詳細は、108.16.2.3項「インメモリー問合せで発生した例外の処理」を参照してください。
作業ユニットは、コミット時にオブジェクト参照を検証します。作業ユニットに登録されたオブジェクトが他の未登録のオブジェクトを参照している場合、オブジェクトのトランザクション独立性が損われるため、TopLinkでの検証によって例外が発生します。
登録されているオブジェクトから未登録のオブジェクトを参照するとセッション・キャッシュが破損することがありますが、アプリケーションによっては検証を無効にする必要があります。TopLinkには、検証の有効と無効を切り替えるための次のAPIが用意されています。
dontPerformValidation
: 検証を無効にします。
performFullValidation
: 検証を有効にします。
作業ユニットが変更内容をセッション・キャッシュにマージする際にエラーを検出すると、QueryException
をスローします。この例外には、無効なオブジェクトと、そのオブジェクトが無効である理由が示されていますが、それでも問題の原因を特定できない場合があります。
この場合、validateObjectSpace
メソッドを使用して登録されているオブジェクトをテストし、走査されたオブジェクトすべての完全なスタック・トレースを提供することができます。こうすることで、問題の特定が容易になります。このメソッドは、作業ユニットに対していつでもコールできます。