EclipseLinkでは、ディスクリプタを使用して、あるデータ・ソースによって特定クラスのインスタンスがどのように表現されるかを定義した情報を格納します。ディスクリプタには、クラスのインスタンス変数を、データ・ソースおよび、値の格納と取得に使用されるトランスフォーメーション・ルーチンに関連付けるマッピングを含めます。それによって、ディスクリプタは、Javaオブジェクトとそのデータ・ソース表現を接続する役目を果します。
この章の内容は次のとおりです。
次の項では、オブジェクト・リレーショナルとObject-XMLのディスクリプタに共通の概念について説明します。
ディスクリプタには、あるデータ・ソースによって特定オブジェクト・クラスのインスタンスがどのように表現されるかを定義したすべての情報を格納します。
EclipseLinkディスクリプタには次の情報を格納できます。
ディスクリプタ自体の中で記述する永続Javaクラス、および対応するデータ・ソース(データベース表またはXMLの複合型インタラクション)。
そのクラスの属性とリレーションシップをデータ・ソースでどのように表現するかを記述したマッピングのコレクション。
データ・ソースの主キー情報(またはそれに相当する情報)。
フィールド名の問合せキー(または別名)のリスト。
順序番号の情報。
ディスクリプタの動作を調整するためのオプション・プロパティのセット。リフレッシュ・オプションのキャッシング、アイデンティティ・マップ、オプティミスティック・ロック、イベント・マネージャおよび問合せマネージャに関するサポートが含まれます。
EclipseLinkがサポートするデータ・ソース・タイプごとに、対応するディスクリプタ・タイプがあります。同一のデータ・ソース・タイプに対して有効なディスクリプタ・タイプが複数ある場合もあります。使用するディスクリプタのタイプにより、定義できるマッピングのタイプが決まります。
継承とは、導出されたクラス(子)にそのスーパークラス(親)の特性をどのように受け継がせるかを意味します。ディスクリプタを使用すると、リレーショナル・プロジェクトおよびXMLプロジェクトにおいてクラス間の継承リレーションシップを定義できます。
子クラスのディスクリプタでは、親クラスのディスクリプタに指定されているマッピングをオーバーライドしたり、親クラスのディスクリプタにマップされていない属性をマップできます。
図6-1は、Java継承階層の代表例としてVehicleというオブジェクト・モデルを示します。ルート・クラスのVehicleには、2つブランチ・クラスFueledVehicleおよびNonFueledVehicleが含まれています。各ブランチ・クラスには、それぞれCarおよびBicycleというリーフ・クラスが含まれます。
EclipseLinkが継承階層で認識するクラスは、次の3つの種類です。
ルート・クラスは、サブクラス階層内のすべてのインスタンス化可能クラスの情報を格納します。デフォルトでは、ルート・クラスに対して実行された問合せにより、ルート・クラスおよびインスタンス化可能なサブクラスのインスタンスが返されます。ただし、ルート・クラスの構成方法によっては、ルート・クラスに対する問合せ時にサブクラスのインスタンスを省いて、ルート・クラス自体のインスタンスのみを返すこともできます。
たとえば、図6-1のVehicleクラスが、ルート・クラスです。
ブランチ・クラスは、永続スーパークラスの他にサブクラスを持つものです。デフォルトでは、ブランチ・クラスに対して実行された問合せにより、ブランチ・クラスおよびそのサブクラスのインスタンスが返されます。ただし、ルート・クラスの場合と同様に、ブランチ・クラスの構成方法によっては、ブランチ・クラスに対する問合せ時にサブクラスのインスタンスを省いて、ブランチ・クラス自体のインスタンスのみを返すこともできます。
たとえば、図6-1のFueledVehicleクラスが、ブランチ・クラスです。
リーフ・クラスには、階層内の永続スーパークラスがありますが、サブクラスはありません。リーフ・クラスに対して実行された問合せにより、リーフ・クラスのインスタンスのみが返されます。
たとえば、図6-1のCarクラスが、リーフ・クラスです。
子クラスのディスクリプタでは、親クラスのディスクリプタに指定されているマッピングをオーバーライドしたり、親クラスのディスクリプタにマップされていない属性をマップできます。
この項では、次の内容について説明します。
継承を構成する場合は、ルート・クラス・ディスクリプタに、インスタンス化先のサブクラスの選択の方法を構成します。
これを行うには、次のいずれかの方法を実行します。
|
注意: 継承階層内のすべてのリーフ・クラスにはクラス・インジケータが必須で、かつこれらのリーフ・クラスは同じタイプのクラス・インジケータ(フィールドまたはクラス抽出メソッド)を持つ必要があります。 |
クラスの永続属性を使用すると、インスタンス化先のサブクラスを指定できます。たとえば、リレーショナル・ディスクリプタでは、ルート・クラス表のクラス・インジケータ・フィールドを使用できます。ただし、クラス・インジケータ・フィールドには、読取り専用に設定されていないダイレクト・マッピングを関連付けないでください。
|
注意: インジケータ・フィールドが主キーの一部である場合は、インジケータ・フィールドに対する書込み専用トランスフォーメーション・マッピングを定義します。 |
クラス・インジケータ・フィールドでは、値として文字列または数値を使用できます。
ルート・クラス・ディスクリプタによって、クラス・インジケータ・フィールドの値をインスタンス化されるクラスに変換する方法を指定する必要があります。
そのための方法の1つは、クラス・インジケータ・ディクショナリ(クラス・インジケータ・フィールドに格納される単純なキーを、インスタンス化するクラスに関連付けるキー値のコレクション)を使用して、ルート・クラス・ディスクリプタを構成することです。表6-1に、図6-1で示したVehicleクラスのサブクラスのクラス・インジケータ・ディクショナリを示します。
もう1つの方法は、単純にクラス名自体をクラス・インジケータ・フィールドへの値として格納することです。こうすると、クラス名の長さの範囲内で通常より長めのキー値を使用してまでクラスごとに一意のインジケータを定義することの必要性から解放されます。
オブジェクトのデータ・ソース・レコード内で利用可能なすべての情報に基づいて、クラス・インジケータを計算するJavaメソッドを定義することができます。このようなメソッドをクラス抽出メソッドといいます。
クラス抽出メソッドを使用すると、データ・モデルに明示的なクラス・インジケータ・フィールドを含める必要がなくなり、また、複雑すぎてクラス・インジケータ・フィールドには定義できないリレーションシップを処理できます。
クラス抽出メソッドには次の特性が必要です。
ルート・ディスクリプタのクラスをベースにして定義されていること
静的であること
引数としてRecordをとること
入出力引数であるRecordにjava.lang.Classオブジェクトを出力すること
状況に応じて、only-instancesおよびwith-all-subclasses式も定義する必要があります。クラス抽出メソッドを使用する場合は、共通の表を使用するすべてのクラスの兄弟インスタンスを正しくフィルタ処理するための式を、EclipseLinkに指定する必要があります。
表6-2は、一例としてEMPLOYEEという表の行を示したものです。ベース・クラスはEmployeeクラスです。Director、Manager、ProgrammerおよびTechWriterクラスはいずれもEmployeeクラスから導出されます。ただし、構築するアプリケーションでは、Manager、ProgrammerおよびTechWriterクラスのインスタンスはEmployeeのインスタンスとして表現するものの、DirectorのインスタンスはDirectorのインスタンスとして表現する必要があります。クラスとJOB_TYPEフィールド値は1対1で対応していないため、JOB_TYPEフィールド単独ではクラス・インジケータ・フィールドとして機能しません(6.1.2.1.1項「クラス・インジケータ・フィールドの使用」を参照)。この問題を解決するには、例6-1に示すクラス抽出メソッドを使用できます。
表6-2 EMPLOYEE表
| ID | NAME | JOB_TYPE | JOB_TITLE |
|---|---|---|---|
|
732 |
Bob Jones |
1 |
Manager |
|
733 |
Sarah Smith |
3 |
Technical Writer |
|
734 |
Ben Ng |
2 |
Director |
|
735 |
Sally Johnson |
3 |
Programmer |
例6-1 クラス抽出メソッド
... // If the JOB_TYPE field value in record equals 2, return the Director class. // Return the Employee class for all other JOB_TYPE field values public static Class getClassFromRecord(Record record) { if (record.get("JOB_TYPE").equals(new Integer(2)) { return Director.class; } else { return Employee.class; } }
クラス抽出メソッドを使用して継承を構成した場合、EclipseLinkではルート・クラスに関する問合せのためのSQLは生成されません。
リレーショナル・プロジェクトの場合、EclipseLinkでは、継承階層内のすべてのクラスが、ルート・ディスクリプタに設定されているのと同じ主キーを持っていることが前提とされます。
リレーショナル・プロジェクトでは、継承階層を単一の表または複数の表にマップできます。
リレーショナル・ディスクリプタを集約ディスクリプタとして指定できます。XMLディスクリプタは常にコンポジット・ディスクリプタです(6.1.3項「ディスクリプタと集約」を参照)。
リレーショナル集約ディスクリプタで継承を構成する場合は、継承ツリー内のすべてのディスクリプタが集約である必要があります。つまり、集約クラスと非集約クラスのディスクリプタを同じ継承ツリーに置くことはできません。
XMLディスクリプタで継承を構成する場合は、すべてのXMLディスクリプタがコンポジットであるため、継承はディスクリプタ・タイプによる制限を受けません。
2つのオブジェクト、つまり、ソース(親、すなわち所有)オブジェクトとターゲット(子、すなわち被所有)オブジェクトは、両者間に厳密な1対1の関係がある場合、集約によって関連付けられ、ターゲット・オブジェクトのすべての属性は、ソース・オブジェクトと同じデータ・ソース表現から取得できます。つまり、ソース・オブジェクトが存在すればターゲット・オブジェクトも存在する必要があり、ソース・オブジェクトが破棄されればターゲット・オブジェクトも破棄されるということです。
この場合、ソースおよびターゲット・オブジェクトのディスクリプタは、この関係を反映するよう定義する必要があります。
EJB 3.0では、集約は埋込み可能であることが知られています。EJB 3.0仕様では、埋込み可能である集約に、埋込み可能な別の集約を含めることはできません(つまり、EJB 3.0仕様では、ネストされた集約はサポートされていません)。
詳細は、6.1.2.4項「集約およびコンポジット・ディスクリプタと継承」を参照してください。
ディスクリプタ・カスタマイザを指定すると、実行時にディスクリプタをカスタマイズできます。ディスクリプタ・カスタマイザはJavaクラスの1つで、org.eclipse.persistence.config.DescriptorCustomizerインタフェースを実装し、デフォルト(ゼロ引数)・コンストラクタを備えています。
ディスクリプタ・カスタマイザを使用することで、コードAPIを介して実行時にディスクリプタがカスタマイズされますが、その方法は、修正メソッドを使用してディスクリプタをカスタマイズする方法と類似しています。6.1.5項「修正メソッドとロード後メソッド」を参照してください。
実行時にディスクリプタがロードされるときにコールするstatic Javaメソッドを関連付けることができます。このメソッドは、ディスクリプタのJavaコードAPIを介して実行時のディスクリプタ・インスタンスを修正できます。
ただし、ディスクリプタはセッションが接続される前にのみ修正できます。これは、セッションの接続後にディスクリプタを修正するのは望ましくないためです。
リレーショナル・プロジェクトでは、EclipseLinkによって、永続性のライフ・サイクル中にDescriptorEventの様々なインスタンスが起動されます。各ディスクリプタは、これらのイベントを受信してそれを登録済のディスクリプタ・イベント・ハンドラにディスパッチする役割を持つDescriptorEventManagerのインスタンスを所有します。
ディスクリプタ・イベント・ハンドラを使用すると、構築したアプリケーション固有のロジックをディスクリプタ・イベントの発生時に実行でき、永続データのライフ・サイクルの様々な時点において実行可能なカスタマイズ・アクションをとれるようになります。たとえば、ディスクリプタ・イベント・ハンドラを使用して次の動作を実行できます。
永続オブジェクトと他のシステム、サービスおよびフレームワークとの同期化
EclipseLinkで対応していない非永続属性の管理
オブジェクトの永続状態が変化したときにアプリケーションの他のオブジェクトに通知すること
EclipseLinkのマッピングで直接サポートされていない複雑なマッピングまたは最適化を実装すること
次の項では、オブジェクト・リレーショナル・ディスクリプタに固有の概念について説明します。
デフォルトでは、特定のオブジェクト・クラスに対してオブジェクト・レベルの読取り問合せを実行すると、そのオブジェクトのディスクリプタにマップされているすべての永続属性がEclipseLinkによって返されます。この1回の問合せを実行するだけで、対象オブジェクトのすべての永続属性が定義され、さらに、各属性のgetメソッドをコールすることで、属性値をオブジェクトから直接取得できます。
オブジェクトの属性の一部のみが必要な場合は、フェッチ・グループを使用して、そのオブジェクトの属性のサブセットのみが返されるようにした方が効率的です。
フェッチ・グループを使用して、オブジェクトの属性のサブセットを定義し、そのフェッチ・グループをReadObjectQueryまたはReadAllQuery問合せと関連付けることができます。問合せを実行すると、EclipseLinkによりフェッチ・グループ内の属性のみが取得されます。除外された属性のいずれかに関するgetメソッドをコールすると、EclipseLinkでは、このサブセットから除外されたすべての属性をフェッチする問合せが自動的に実行されます。
1つのクラスに対して複数のフェッチ・グループを定義できます。オプションで、デフォルトのフェッチ・グループとして最大1つのフェッチ・グループを指定できます。フェッチ・グループを指定しないでReadObjectQueryまたはReadAllQuery問合せを実行する場合、問合せを別の方法で構成しないかぎり、EclipseLinkではデフォルトのフェッチ・グループが使用されます。
フェッチ・グループを使用する前に、システムの使用状況を綿密に分析しておくことをお薦めします。多くの事例では、フェッチ・グループに含まれない属性をロードするために必要な追加の問合せにより、部分的な属性のロードによって得られるメリットがかなり相殺されているためです。
フェッチ・グループは、FetchType.LAZY (partial object queries)で構成された基本マッピングとのみ組み合せて使用できます。
EclipseLinkでは、フェッチ、ロード、コピーおよびマージ操作で部分的エンティティの使用を構成するために使用できるAttributeGroupを使用します。
フェッチ: データベースから取得される属性とその関連列を制御します。
ロード: 問合せから返されたエンティティで移入されるリレーションシップを制御します。
コピー: 新しいエンティティ・インスタンスにコピーされる属性を制御します。
マージ: エンティティにフェッチ、ロードまたはコピーされた属性のみをマージします。
FetchGroupは、問合せ実行の結果としてエンティティが取得されるときにフェッチする(データベースから選択する)必要のある属性を定義します。FetchGroupにリレーションシップ属性を含めることで決定されるのは、属性の必須列をフェッチして移入するかどうかのみです。遅延フェッチ・タイプの場合、属性を含めることで、そのプロキシが作成されてアクセス時に遅延ロードが有効になります。問合せでのFetchGroupの使用時にリレーションシップ・マッピングの移入を強制するには、属性をグループに含め、FetchType.EAGERに設定するか、問合せで関連するLoadGroupに含める必要があります。
FetchGroupには、FetchGroupManagerによって管理される名前付きのデフォルトFetchGroupの概念も含まれます。デフォルトFetchGroupは、1つ以上の基本マッピングが遅延するように構成され、エンティティ・クラスがFetchGroupTrackerを実装している場合に、メタデータ処理中に定義されます(通常はウィービングを通じて導入されます)。デフォルトFetchGroupは、明示的FetchGroupや名前付きFetchGroupが構成されていないこのエンティティ・タイプのすべての問合せで使用されます。
名前付きFetchGroupは、@FetchGroup注釈を使用して、またはeclipselink-orm.xmlファイル内で、エンティティに対して定義できます。
FetchGroupは、最初に作成されたときは空であると想定されます。ユーザーは、FetchGroupに属性を追加する必要があります。すべての属性を含むFetchGroupが必要な場合、FetchGroupManager.createFullFetchGroup()を使用する必要があります。
FetchGroupは、リレーションシップ・マッピングやネストしたリレーションシップ・マッピングのロード操作を実行するように構成することもできます。
LoadGroupを使用して、リレーションシップ属性の指定したセットに問合せ結果での移入を強制します。
CopyGroupによって、エンティティのコピー方法を定義するために使用される、非推奨のObjectCopyPolicyが置換されます。ソース・エンティティ・グラフからターゲット・コピーにコピーする内容を定義する属性を指定することに加え、CopyGroupでは次の定義も可能です。
shouldResetPrimaryKey: 識別子属性をデフォルト値にリセットします。これは、コピー操作でソースと同様の状態の新しいエンティティを作成するためにエンティティをクローニングする場合に使用されます。デフォルトはfalseです。
shouldResetVersion: オプティミスティック・バージョンのロック属性をコピーでデフォルト値にリセットします。デフォルトはfalseです。
depth: リレーションシップを処理するためのカスケード・モードを定義します。デフォルトでは、CASCADE_PRIVATE_PARTSが使用されますが、NO_CASCADEやCASCADE_ALL_PARTSにも構成できます。
使用可能な属性を定義して、部分エンティティをAttributeGroupが関連付けられた永続性コンテキストにマージすると、それらの属性のみがマージされます。エンティティ内のリレーションシップ・マッピングも、それらのカスケード・マージ設定に従ってマージされます。
各リレーショナル・ディスクリプタには、次の構成に使用できるDescriptorQueryManagerのインスタンスが用意されています。
名前付き問合せ
基本的な永続性操作用のカスタムのデフォルト問合せ
追加の結合式
オブジェクト・アイデンティティを保持するうえで重要なのは、各オブジェクト・インスタンスを区別するために一意の値(特定の順序)の割当てを管理することです。
順序付けオプションにプロジェクト・レベルとセッション・レベルのいずれを構成するかにより、EclipseLinkで使用される順序付けのタイプが決まります。POJOプロジェクトの場合、セッション・レベルの順序構成を使用して、プロジェクト・レベルの順序構成をセッション単位でオーバーライドすることもできます。
順序タイプの構成後、ディスクリプタの参照クラスごとに、1つの属性(通常は主キーとして使用する属性)を独自の順序に関連付ける必要があります。
オブジェクト・リレーショナル・マッピングでは、次に示すロック・ポリシーのいずれかを使用してディスクリプタを構成し、ドメイン・オブジェクトへの同時アクセスを制御できます。
オプティミスティック: すべてのユーザーにデータへの読取りアクセス権限があります。ユーザーが変更を加えようとした場合、そのユーザーがデータを読み取った後にデータが変更されていないか、アプリケーションによってチェックされます。
ペシミスティック: 更新目的でデータにアクセスした最初のユーザーによって、更新処理が完了するまでデータがロックされます。
ロックなし: 複数のユーザーが互いの変更内容を上書きする操作は阻止されません。
ほとんどのタイプのアプリケーションでは、ユーザー同士が互いの変更内容を上書きできないようにするために、オプティミスティック・ロックを使用することをお薦めします。
この項では、EclipseLinkでサポートされている次のような様々なタイプのロック・ポリシーについて説明します。
オプティミスティック・ロックを使用した場合、すべてのユーザーにデータへの読取りアクセス権限があります。ユーザーが変更を加えようとした場合、そのユーザーがデータを読み取った後にデータが変更されていないか、アプリケーションによってチェックされます。
オプティミスティック・バージョン・ロック・ポリシーでは、バージョン・フィールド(書込みロック・フィールド)を使用して、オプティミスティック・ロックが実行されます(バージョン・フィールドは、参照クラス内に作成し、オブジェクト変更がコミットされるたびにEclipseLinkによって更新されます)。
EclipseLinkは、データ・ソースからオブジェクトを読み取るときにこのバージョン・フィールドの値をキャッシュします。クライアントがオブジェクトに書き込もうとすると、EclipseLinkはキャッシュしたバージョン値とデータ・ソース内の最新のバージョン値を次のように比較します。
2つの値が一致した場合は、EclipseLinkはオブジェクト内のバージョン・フィールドを更新し、データ・ソースに対する変更内容をコミットします。
2つの値が一致しなかった場合は、そのクライアントが最初にオブジェクトを読み取った後に別のクライアントがそのオブジェクトを更新したことを意味するため、書込み操作を却下します。
EclipseLinkには、次のようなバージョン・ベースのオプティミスティック・ロック・ポリシーが用意されています。
VersionLockingPolicy
TimestampLockingPolicy
これらのロック・ポリシーの詳細は、『Oracle Fusion Middleware Oracle TopLinkソリューション・ガイド』のオプティミスティック・ロックの設定に関する項を参照してください。
|
注意: 次の理由から、通常はnumericタイプのバージョンのロックをお薦めします。
|
オプティミスティック・ロック違反により更新が失敗すると、EclipseLinkではOptimisticLockExceptionがスローされます。この例外は、データベースの変更を行っているアプリケーションで処理する必要があります。アプリケーションは、ロックの競合をクライアントに通知し、オブジェクトをリフレッシュして、クライアントに変更内容の再適用を依頼する必要があります。
バージョン値は、オブジェクト内にマップ済属性として格納するか、キャッシュに格納するかを選択できます。3層アプリケーションでは、オブジェクトの更新時にバージョン値が確実にクライアントに渡されるようにするため、オブジェクト内にバージョン値を格納しておく方法が一般的です(6.2.4.3項「アプリケーションでのロックの適用」を参照)。
バージョン値をキャッシュに格納する場合、バージョン値のマッピングは必要ありません。バージョン・フィールドをマップする場合、マッピングを読取り専用として構成する必要があります。
私有の子オブジェクトの変更時にその親オブジェクトのバージョン・フィールドが確実に更新されるようにするには、6.2.4.1.1項「オプティミスティック・バージョン・ロック・ポリシーとカスケード」を使用することを検討します。
ストアド・プロシージャを使用してオブジェクトを更新または削除する場合、オプティミスティック・ロックの障害を検出するために必要な行数をデータベースが返さないことがあるため、ストアド・プロシージャでオプティミスティック・ロックのバージョンを確認し、一致しない場合はエラーをスローする必要があります。StoredProcedureCallでは、バージョン・ロックのみが直接サポートされます。タイムスタンプおよびフィールド・ロックでは、同じフィールドの2つのバージョンをコールに渡す必要があるため、##パラメータを使用してトランザクション行にアクセスするSQLコールは、他のロック・ポリシーに使用できます。
使用するデータベース・スキーマが、親オブジェクトおよびその私有の子オブジェクトの両方を同じ表内に格納する設計になっている場合には、その子オブジェクトを更新すると、親オブジェクトのバージョン・フィールドが更新されます。
一方、親オブジェクトおよびその私有の子オブジェクトを異なる表に格納する場合は、デフォルトでは、子を変更しても、親のバージョン・フィールドは更新されません。
この場合に親オブジェクトのバージョン・フィールドを確実に更新するには、親オブジェクトのバージョン・フィールドを手動で更新するか、またはVersionLockingPolicyを使用している場合は、子オブジェクトのバージョン・フィールドの更新が親に自動的にカスケードされるようにEclipseLinkを構成することができます。
オプティミスティック・バージョン・ロックのカスケード機能を有効化した場合に、私有の子オブジェクトを変更すると、EclipseLinkはその私有されている外部参照マッピングを走査し、ルートに向かってすべての親オブジェクトを更新します。
オプティミスティック・バージョン・ロックのカスケードは、子オブジェクトが作業ユニット内に登録されている場合にのみ適用されます。
EclipseLinkでは、次の場合にオプティミスティック・バージョン・ロックのカスケードがサポートされます。
私有の1対1および1対多マッピングでのオブジェクト変更
次のコレクション・マッピング(私有かどうかは問わない)でのリレーションシップ変更(追加または削除)
ダイレクト・コレクション
1対多
多対多
集約コレクション
図6-2に例示したオブジェクトの図について考えてみます。
この例では、ObjectAがObjectBを私有し、ObjectBはObjectCを私有し、ObjectCはObjectDを私有します。
ObjectBを作業ユニットに登録し、ObjectBを変更して、作業ユニットをコミットしたとします。この場合、ObjectBはObjectAのキャッシュをチェックし、値が存在しない場合にはデータベースに対してObjectAを問い合せます。次に、ObjectBは自身の変更をObjectAに通知します。ObjectAは、自身に対応する表に変更がない場合でも、自身のバージョン・オプティミスティック・ロック・フィールドを更新します。
ObjectAを作業ユニットに登録し、そのObjectB、ObjectC、ObjectDにこの順序でアクセスして、ObjectDのフィールドを変更し、作業ユニットをコミットするとします。この場合は、ObjectDが自身の変更をObjectCに通知します。ObjectCは、自身に対応する表に変更がない場合でも、自身のバージョン・オプティミスティック・ロック・フィールドを更新します。次に、ObjectCはObjectDの変更をObjectBに通知します。次に、ObjectBがObjectDの変更をObjectAに通知します。ObjectAは、自身に対応する表に変更がない場合でも、自身のバージョン・オプティミスティック・ロック・フィールドを更新します。
オプティミスティック・ロックで、オプティミスティック・ロックのバージョンをキャッシュに格納する場合、ロックされたオブジェクトの値をロールバックするにはUnitOfWorkメソッドcommitAndResumeOnFailureを使用します。
ロックされた複数のバージョンを1つのオブジェクトに格納する場合は、更新失敗時に各オブジェクト(すなわち、オブジェクトのバージョン)をリフレッシュする必要があります。あるいは、失敗時に新しい作業ユニットを取得して、そのユニットに変更を再適用することもできます。
オプティミスティック・フィールド・ロック・ポリシーでは、表内に現存する1つ以上のフィールドを使用して、クライアントが対象オブジェクトを読み取った以後にそのオブジェクトが変更されたかどうかを判別することによって、オプティミスティック・ロックが実行されます。
ユーザーがオブジェクトを最初に読み取ったとき、またはオブジェクトを作業ユニットに登録したときに、作業ユニットにより、そのオブジェクトの元の状態がキャッシュされます。コミット時には、ロック・フィールドの元の値と更新中のデータ・ソースの現在の値が、作業ユニットにより比較されます。ロック・フィールドのいずれかの値が変更されていた場合には、オプティミスティック・ロック例外がスローされます。
EclipseLinkには、次のようなオプティミスティック・フィールド・ロック・ポリシーが用意されています。
AllFieldsLockingPolicy
ChangedFieldsLockingPolicy
SelectedFieldsLockingPolicy
VersionLockingPolicy
TimestampLockingPolicy
これらのロック・ポリシーの詳細は、『Oracle Fusion Middleware Oracle TopLinkソリューション・ガイド』のオプティミスティック・ロックの設定に関する項を参照してください。
ペシミスティック・ロックを使用した場合は、データを更新する目的でそのデータにアクセスする最初のユーザーが、更新を完了するまでデータをロックします。
ペシミスティック・ロック・ポリシーを使用する場合は、更新をただちに失敗させるか、読取りロックが取得されるまで待機させるようにポリシーを構成できます。
ペシミスティック・ロック・ポリシーは、コンテナ管理の永続性タイプと、EJB情報を含むディスクリプタを保持するプロジェクトでのみ使用できます。
(ペシミスティック・ロック・ポリシーではなく)ペシミスティック・ロックは、問合せレベルでも使用できます。
EclipseLinkでは、ペシミスティック・ロックをコンテナ管理の永続性を持つエンティティとともに使用する場合にこのロックの最適化が行われ、問合せをペシミスティック・ロックに設定し、(ファインダの実行後に終了する)独自の新しいトランザクションで問合せを実行すると、EclipseLinkによってそのロック設定はオーバーライドされ、SQLにFOR UPDATEは追加されません。ただし、ユーザーがFOR UPDATEを含むSQL文字列でペシミスティック・ロック問合をカスタマイズしている場合、この最適化の使用によって不都合な結果が発生する可能性があります。この場合、最適化が必要な状況が存在すると、問合せは非ペシミスティック・ロックにリセットされますが、SQLは同じままのため、問合せのロック設定が問合せのSQL文字列と競合します。この問題を回避するには、次の2つのアプローチのいずれかを使用します。
選択基準として式を使用します(第11章「EclipseLinkの式の理解」)。これによって、EclipseLinkでSQLの生成を制御します。
最適化の条件をなくすため、ファインダをトランザクション内に配置します。
アプリケーションで適切にオブジェクトをロックするには、オブジェクトが編集のためにクライアントに送信される前に、ロックを取得する必要があります。
オプティミスティック・ロックを使用する場合、オブジェクトを正しくロックするには次の2つの選択肢があります。
オブジェクト内のオプティミスティック・ロック・フィールドを読取り専用以外にマップし、読取り時にバージョンをクライアントに渡し、更新時にサーバーに戻します。
更新のためにオブジェクトが読み取られるときに、元のバージョン値がクライアントに送信されることを確認します。クライアントは、その元のバージョン値を更新情報とともに戻す必要がありますが、このバージョンは、サーバー上の新しい作業ユニットで登録または読取りが行われた後に、更新対象のオブジェクトに設定される必要があります。
クライアントとの対話が完了するまで作業ユニットを保持します。
ステートフル・セッションBeanを介して、またはHTTPセッションにおいて、更新対象オブジェクトの読取りに使用する作業ユニットを、クライアントとの対話が完了するまで保管します。
オブジェクトをクライアントに更新用に渡す前に、この作業ユニットからオブジェクトを読み取っておく必要があります。これにより、作業ユニットのキャッシュまたは作業ユニットのクローンに元のバージョン値を保管しておけます。
この同じ作業ユニットを更新でも必ず使用します。
1番目の選択肢の方が一般的に使用され、ステートレス・アプリケーションを開発する場合には必須となります。
次の項では、Object-XMLディスクリプタでのデフォルトのルート要素の使用について説明します。
デフォルトのルート要素を使用してXMLディスクリプタを構成し、ディスクリプタで記述されているクラスに関連付けられたデータ・ソースのデータ・タイプをEclipseLinkランタイムに認識させます。
|
注意: 参照オブジェクトの未定義のドキュメント・ルート要素は、コレクション・マッピングおよびオブジェクト・マッピングによるマーシャリング中に無視されます。 |
この項では、デフォルトのルート要素の概要と、それがEclipseLinkでどのように使用されるかについて説明します。
例6-2に示すCustomerクラスとAddressクラス、およびそれらのマッピングについて検討します。
例6-2 CustomerクラスとAddressクラス
Class:CustomerDefault Root:customerAttributes and Mappings:name:String Direct Mapping name/text() billingAddress:Address Composite Object Mapping to billing-address shippingAddress:Address Composite Object Mapping to shipping-addressClass:AddressDefault Root:addressAttributes and Mappings:street:String Direct Mapping to street/text() city:String Direct Mapping to city/text()
これらのクラスは、例6-3に示すXMLスキーマに対応しています。
例6-3 CustomerおよびAddressスキーマ
<xsd:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsd:complexType name="address-type">
<xsd:sequence>
<element name="street" type="xsd:string"/>
<element name="city" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:element name="customer" type="customer-type"/>
<xsd:complexType name="customer-type">
<xsd:sequence>
<xsd:element name="name" type="xsd:string"/>
<xsd:element name="billing-address" type="address-type"/>
<xsd:element name="shipping-address" type="address-type"/>
</xsd:sequence>
</xsd:complexType>
</xsd:schema>
CustomerクラスのインスタンスをXMLに永続化する際には、EclipseLinkランタイムで次の処理が行われます。
デフォルトのルート要素が取得されます。
Customerクラス・インスタンスは、XML文書のルートに対応しています。EclipseLinkランタイムは、ディスクリプタ(customer)に指定されているデフォルトのルート要素を使用して、XML文書を開きます。次に、EclipseLinkはディスクリプタに含まれるマッピングを使用して、そのオブジェクトの属性をマーシャリングします。
<customer>
<name>…</name>
</customer>
EclipseLinkランタイムは、たとえば、オブジェクト属性billingAddressに遭遇すると、この属性に関連付けられているマッピングをチェックし、処理をどの要素(billing-address)に移すかを判別します。
<customer>
<name>…</name>
<billing-address/>
</customer>
EclipseLinkランタイムはマッピングの参照ディスクリプタ(Address)をチェックして、永続化する属性を判別します。
<customer>
<name>…</name>
<billing-address>
<street>…</street>
<city>…</city>
</billing-address>
</customer>
ディスクリプタAPIを使用すると、Javaコードを介してEclipseLinkディスクリプタを定義または修正できます。ディスクリプタAPIのクラスは、主としてorg.eclipse.persistence.descriptorsパッケージに含まれています。このようなクラスには次のものがあります。
ClassDescriptor (抽象汎用ディスクリプタAPI)
RelationalDescriptor(リレーショナル・プロジェクト固有API)
DescriptorEventManager (イベントAPI)
DescriptorQueryManager (問合せAPI)
InheritancePolicy
InterfacePolicy
ReturningPolicy
ロック・ポリシー(各種のオプティミスティック・ロック・ポリシー)
ディスクリプタAPIには、次の非リレーショナル・ディスクリプタも含まれます。
org.eclipse.persistence.oxm.XMLDescriptor
org.eclipse.persistence.eis.EISDescriptor
オブジェクト・リレーショナル・データ・タイプおよびXMLプロジェクト用のディスクリプタ・クラスは、それぞれorg.eclipse.persistence.mappings.structuresおよびorg.eclipse.persistence.oxmパッケージに含まれています。
次に、org.eclipse.persistence.descriptors.ClassDescriptorクラスから導出されたディスクリプタ・タイプの階層を示します。
class org.eclipse.persistence.descriptors.ClassDescriptor
class org.eclipse.persistence.descriptors.RelationalDescriptor
class org.eclipse.persistence.mappings.structures.ObjectRelationalDataTypeDescriptor
class org.eclipse.persistence.oxm.XMLDescriptor