ビジネス・ロジック層では、データベースおよびクライアントから受信したデータを、エンティティ・キャッシュおよびビュー・キャッシュの2つのタイプのキャッシュに格納します。この独自の高度なキャッシュ・システムでは、多数の複雑なデザイン・パターンおよびプログラムによるタスクが処理されます。このため、ユーザーは、アプリケーション固有のビジネス・ロジックおよびクライアント・インタフェースの実装に専念できます。
キャッシュ・システムがどのように機能するかを知らなくても、ビジネス・コンポーネント・アプリケーション全体を実装できます。ただし、主要な機能を理解すると、実装および最適化を行う上で適切な決定をするのに役立ちます。また、経験豊富なJavaプログラマは、アプリケーション固有の理由から、キャッシュ・システムをカスタマイズする、あるいは使用しないことも可能です(ただし、このような処理が必要になることはほとんどありません)。このトピックでは、キャッシュ・システムの機能、およびユーザーが制御できる部分について説明します。
具体的に説明するために、主要な概念にはそれぞれ、キャッシュでの変更の要因となる一般的なシナリオを示すコードSnippetを記載します。このサンプルでは、Oracle9i で提供されるHRスキーマが使用されます。別のバージョンのデータベースを使用している場合、表のインストール方法の詳細は、「サンプル・スキーマ表の作成および移入」を参照してください。
このトピックを読む前に、ビジネス・コンポーネントおよびフレームワークについて理解する必要があります。これらは、オンライン・ヘルプの「Business Components for Javaの概要」ブックで説明されています。
ビジネス・ロジック層では、主に次の5つの理由から、ビジネス・コンポーネントのインスタンスをメモリーにキャッシュします。
アプリケーションのパフォーマンスを改善するため。アプリケーションでデータを処理する場合、アプリケーションはデータベースのデータを取得し、そのデータにアクセスして、必要な場合はデータを更新します。処理が終了すると、アプリケーションにより、永続データへの変更がデータベースに追加されます。データがキャッシュされていない場合は、何度もアプリケーションでデータを取得してポストする必要があるため、パフォーマンスが低下します。
ビジネス・コンポーネントの状態を管理するため。プログラムがビジネス・コンポーネントのインスタンスに対して処理を行う際には、キャッシュはインスタンスの状態を管理します。インスタンスの状態により、そのインスタンスが新しく作成された、未修正である、削除されているなどがわかります。プログラムで、データベースにコミットできる状態にある場合は、ビジネス・ロジック層でビジネス・コンポーネントの状態を使用し、データベースでどのような処理を行うかを決定します。状態を管理したり、変更をデータベースに保存するために複雑なコードを作成する必要はありません。ビジネス・コンポーネント・フレームワークによってこの処理は実行されます。
ビジネス・コンポーネントの明確なライフサイクルを実装するため。キャッシュを使用すると、適切に定義されたライフサイクル・モデル(ビジネス・コンポーネントのインスタンスがメモリー内でどのように作成され、アクセス、修正、検証されるか、またデータベースにどのように書き込まれるか)をフレームワークによって実装できます。ユーザーは、ライフサイクルを理解した上でアプリケーションを作成できます。必要な場合には、ライフサイクルの特定のポイントを、プログラムに適合するようにカスタマイズできます。
複数のビューによるデータおよびビジネス・ロジックの使用を調整するため。ビジネス・ロジック層のすべてのクライアントは、キャッシュおよびビジネス・ロジックを共有します。あるビューでの変更は、同じデータを参照している別のビューでも認識できます。
oracle.jbo.serverパッケージの一部のフレームワーク・クラスは、キャッシュに影響します。コードでキャッシュを使用する方法を理解するには、この関係の概要を把握しておくと役立ちます。
セッションで使用される各EntityDefImplインスタンスについては、最上位レベルのアプリケーション・モジュールが、EntityImplインスタンスを含むエンティティ・キャッシュを管理します。
ビュー・オブジェクトがビュー・キャッシュを管理します。ビュー・キャッシュには行セットが含まれます。行セットには、ViewRowImplインスタンスが含まれます。
これらの関係をよく理解できるよう、アプリケーションが実行を開始し、データベースからデータを取得して、その後、データベースのデータを更新していくとします。
通常は、最初にアプリケーションの実行が開始されると、ビュー・オブジェクト問合せによってキャッシュにデータが移入されます。ビュー・オブジェクト問合せを実行すると、問合せの結果セットの各行がビュー行にキャッシュされます。
エンティティ属性に基づく(マップされている)ビュー属性は、1つ以上のエンティティ・キャッシュに格納されます。これらの属性では、ビュー・キャッシュはエンティティ・キャッシュを示します。
キャッシュへの移入処理は、通常次のように行われます。
たとえば、次のコードSnippetでは、クライアントはアプリケーション・モジュール・インスタンスを作成し、ビュー・オブジェクトを検索して、ビュー・オブジェクト問合せを調整し、最初の行を取得して、最初の行の給与を取得します。エンティティ属性に基づくすべてのビュー属性は、エンティティ・キャッシュに追加されます。エンティティ属性に基づいていないすべてのビュー属性は、ビュー・キャッシュに追加されます。Managersビュー・オブジェクトでは、すべての属性は永続属性であるため、ビュー・オブジェクトはすべての属性値をエンティティ・キャッシュに格納します。
// Create an instance of the application module by name, using local mode ApplicationModule am = create(appModuleName,jdbcURL); // Find the Managers view object by name in the application module ViewObject mgrVO = am.findViewObject("Managers"); show("Setting WHERE clause of Managers VO to find only commissioned managers."); mgrVO.setWhereClause("commission_pct is not null"); show("Setting ORDER BY clause of Managers VO to order descending by Salary"); mgrVO.setOrderByClause("salary desc"); show("About to retrieve first Row from 'Managers' VO"); // Retrieve the first row from the query ManagersRow mgrRow = (ManagersRow) mgrVO.first(); show("Got first row:"+rowValues(mgrRow,mgrVO)); Number prevSalVal = mgrRow.getSalary(); show("NOTE: Managers VO does not include CommissionPct Attribute");
アプリケーションは、ビュー・キャッシュまたはエンティティ・キャッシュを使用する必要はありません。
エンティティ属性に基づくビュー属性がない場合は、エンティティ・キャッシュは使用されません。
データがビュー・キャッシュに移入されないようにするには、ビュー・オブジェクトでsetForwardOnlyメソッドを使用します。これは、順方向のみのモードと呼ばれます。順方向のみのモードを使用すると、メモリーには一度に1つのビュー行のみが保持されるためメモリー・リソースと時間を節約できます。順方向のみのモードは、データをフェッチしていったん読み込む場合(Webページ用のデータの書式設定、または線形的に処理を行うバッチ処理などの場合)に使用すると便利です。ビュー・オブジェクトが1つ以上のエンティティ・オブジェクトに基づいている場合には、通常、データはエンティティ・キャッシュに渡されますが、ビュー・キャッシュには行は追加されません。
また、エンティティ・オブジェクトでは、ビュー・キャッシュを介さずに、エンティティ・キャッシュに直接データを移入できます。findByPrimaryKeyメソッドを使用してエンティティ・オブジェクト全体を検索すると、すべての属性値がエンティティ・キャッシュに追加されます。(この場合には、キャッシュ内のデータに優先順位が設定され、後述するように、データが存在しない場合はデータベースから取得されます。)ビュー・オブジェクトを使用してデータを検索すると、そのタスクに関連するすべての属性を取得し、その他の属性は除外できるため、より柔軟に処理できます。ビュー・オブジェクトは問合せを実行し、属性にデータが含まれていない場合には、その属性にデータを追加します。
この処理を表すコード・サンプルを次に示します。また、ここでは、必須フィールドのビルトインの検証および変更のポストも示されます。
// Illustrate creating a new employee row using the Employees view object show("About to create a new Row in 'Employees' VO"); EmployeesRow newEmp = (EmployeesRow)empsVO.createRow(); newEmp.setAttribute("EmployeeId", new Number(1001)); newEmp.setAttribute("LastName","Bunger"); newEmp.setAttribute("ManagerId",mgrRow.getEmployeeId()); empsVO.insertRow(newEmp); // The attempt to validate the row will fail because the // Salary attribute is required. try { show("Validating the new row"); newEmp.validate(); } catch (Exception ex) { show(FormatException(ex)); } // Set the Salary attribute and try to post (but not commit) // the changed data in the cache. show("Setting the Salary to '3000'"); newEmp.setAttribute("Salary", new Number(3000)); // Post the pending changes in the cache. Should be one updated // entity and one inserted entity try { show("Posting, but not Committing, Changes"); am.getTransaction().postChanges(); } catch (Exception ex) { show(FormatException(ex)); } show("New Employee 1001 Posted Successfully");
(順序およびGUIDなどとして)主キーを自動作成できます。新しい行がデータベースにポストされたときにデータベースにより主キーの値が生成されます。たとえば、エンティティ属性をデータベース順序として使用する場合は、EntityImplのcreateメソッドにコードを追加するか、「リフレッシュ」の「挿入後」属性設定を選択するか、またはDBSequenceドメインを使用します。createメソッドに追加するコードは、新しい行が挿入されるとただちに値を返す必要があります。「リフレッシュ」の「挿入後」設定では、値は行がデータベースに保存された後で返されます。新しい行の列に値を挿入するデータベース・トリガーが必要です。この種の属性は、エンド・ユーザーが変更できないように読取り専用にできます。
1つの問合せで、バインド変数の使用により複数行セットが返される場合は、複数行セットを作成できます。たとえば、SELECT DEPTNO, DNAME, LOC FROM DEPT WHERE LOC=:1という問合せを行う際に、SAN JOSE、CHICAGOおよびNEW YORKというバインド値を使用すると、(それぞれの場所に対して1つずつ)3つの行セットをビュー・キャッシュに返すことができます。
ビュー・オブジェクト・インスタンスには、それぞれのビュー・キャッシュがあります。問合せが終了すると、返されたデータベース行は、それぞれのビュー・キャッシュの行に格納されます。各列は、行の対応する属性によって示されます。行内の値を取得していない属性は、空のままになります。
エンティティ・オブジェクト・インスタンスには、それぞれのエンティティ・キャッシュがあります。ビュー・オブジェクト問合せが実行され、行が返されると、エンティティ属性にマップされているビュー属性の値が適切なエンティティ・キャッシュに格納されます。ビュー・オブジェクトは、複数のエンティティ・オブジェクトにマップできることに注意してください。つまり、ある行の様々な属性を複数のエンティティ・キャッシュに対応させることができます。エンティティ・キャッシュの各行は、主キーにより一意に定義されています。(したがって、あるビュー・オブジェクトに対して複数のエンティティ・オブジェクトを指定する場合は、各エンティティ・オブジェクトの主キーを必ず指定します。)問合せの結果、行内の値が移入されていない属性は、空白のままになります。エンティティ・オブジェクトにマップされているビュー属性が取得または設定される場合には、エンティティ・オブジェクトのゲッターまたはセッターに委任されます。
最上位レベルのアプリケーション・モジュールに含まれている各ビュー・オブジェクト・インスタンスは、最上位レベルのアプリケーション・モジュール内の他のすべてのビュー・オブジェクト・インスタンスとエンティティ・キャッシュを共有します。(これには、ネストされているアプリケーション・モジュールのすべてのビュー・オブジェクト・インスタンスも含まれます。)同じエンティティ・オブジェクトを参照するビュー・オブジェクトが複数ある場合は、それらが同じエンティティ・キャッシュを参照します。つまり、あるビュー・オブジェクト・インスタンスを介して共有データが変更されると、その他のすべてのビュー・オブジェクト・インスタンスにその変更が通知され、クライアント・フォームに変更を表示できます。
ビュー・オブジェクトで問合せを実行するとき、データをエンティティ・キャッシュにマージする必要があります。エンティティ・キャッシュ内に修正されたデータがあれば、ビジネス・ロジック層はそのデータを使用します。あるエンティティ・オブジェクト・インスタンスが修正されていると、問合せでは、データベースの新しいデータを返すのではなく、エンティティ・キャッシュのデータを返します。エンティティ・オブジェクト・インスタンスが存在しない、または修正されていない場合は、問合せでは、データベースのデータを返します。これにより、同じ行が他のビュー・オブジェクト・インスタンスによってデータベースから取得されている場合に、ポストされていない変更が上書きされないことが保証されます。エンティティ・オブジェクトは、ロック時に、キャッシュされたデータとデータベースのデータを比較します。
次に例を示します。
次のコードSnippetは、同一のキャッシュされたデータが、様々なビュー・オブジェクトでどのように表示されるかを示します。
// Illustrate that you can use a *different* view object // to view data related to the same employee that we modified // through the Managers view object previously, and that the // pending changes made previously to the Salary are consistently // visible through this other view object. ViewObject empsVO = am.findViewObject("Employees"); Key k = empsVO.createKey(mgrRow); show("Using findByKey to find current Emp through Employees view"); Row[] results = empsVO.findByKey(k,-1); show(results.length+" row(s) found."); EmployeesRow empsRow = (EmployeesRow)results[0]; show("Got Row:"+rowValues(empsRow,empsVO)); show("Notice that changed Sal attribute = "+empsRow.getSalary()+ " and NOT "+ prevSalVal+" through this VO as well");
たとえば、特定のエンティティ属性にマップされるビュー属性がビュー・オブジェクトにないために、属性が移入されていない場合など、エンティティ・キャッシュに属性がない場合があります。ビジネス・ロジックが、データベース行に保存されている属性値にアクセスしようとして、その値がエンティティ・キャッシュにない場合には、フォルトインが発生します。行の空の属性がすべてデータベースから移入され、ビジネス・ロジックが完了します。次に例を示します。
フォルトインにより、ビジネス・ロジックに必要な値が含まれることが保証されます。ビジネス・ロジックで必要なすべての属性がビュー・オブジェクト問合せに必ず含まれるようにして、フォルトインを回避し、最適化を図ることができます。このようにすると、必要以上にSQL文が実行されず、エンティティ・オブジェクトとビュー・オブジェクトを明確に区別できます。ただし、ほとんどアクセスされない属性をビュー・オブジェクトから除外してメモリーを節約する場合もあります。
EmployeeImpl.javaに含まれる、次のエンティティ・オブジェクトのコードSnippetでは、フォルトインが発生する理由が示されます。CommissionPct属性がアクセスされますが、キャッシュにないため、フォルトインが発生します。
/** * Sets <code>value</code> as the attribute value for Salary */ public void setSalary(Number value) { // Commissioned employees cannot have salary > 15000 show("About to reference CommissionPct attribute","setSalary"); if (getCommissionPct() != null && value.floatValue() > 15000) { show("Business logic failed.","setSalary"); throw new JboException("Commissioned employees cannot have salary > 15000"); } setAttributeInternal(SALARY, value); }
TestClient.javaに含まれる、次のコードSnippetでは、フォルトインが発生する場所が示されます。
// Illustrate setting the value of an attribute of an existing row // to a value that will cause Employee entity object validation to FAIL. show("Calling setSal('24000'), on first row of Managers VO"); try { mgrRow.setSalary( new Number(24000)); show("Set attribute succeeded."); } catch (JboException ex) { show("Set attribute failed: "+ ex.getMessage()); } // Illustrate setting the value of an attribute of an existing row // to a value that will succeed. The succesful modification of the // first attribute of an entity object will automatically lock the // entity object. show("Calling setSal('10000'), on first row of Managers VO"); try { mgrRow.setSalary( new Number(10000)); show("Set attribute succeeded."); } catch (JboException ex) { show("Set attribute failed: "+ ex.getMessage()); }
エンティティ・キャッシュに行のすべての属性が挿入されるのは、次の4つの場合です。
ビュー・オブジェクト問合せにすべての属性が含まれている場合
フォルトインが発生した場合
findByPrimaryKeyメソッドが使用された場合
ビジネス・ロジック層で、データベースの対応する行について(即時ロックまたはRow.lockメソッドにより)ロックが取得された場合
ロックには、即時ロックとコミット時ロックの2種類があります。また、ロックを使用しないように選択することもできますが、これは推奨されません。アプリケーションの特性上、2人のユーザーが同じ行を同時に修正することがほとんどないと思われる場合は、コミット時ロックを使用します。即時ロックは、最も一般的なロックの方法です。
アプリケーションでロックが使用される場合、ビジネス・ロジック層は次のような場合に行をロックしようとします。
即時ロックで、既存の行にデータを設定しようとする場合。クライアントまたはビジネス・ロジック層で、その行で初めてマップされる属性についてsetAttributeを正常にコールします。最初に、ビジネス・ロジック検証がトリガーされます。次に、エンティティ・オブジェクトは、データベース行に対してロックを取得しようとします。
アプリケーションでロックを使用しない場合、行が別のユーザーによってロックされていると、エンティティ・オブジェクトは、ロックしたユーザーが行を解放するまで待機します。その後、エンティティ・オブジェクトが変更内容を書き込みます。これによって、前のユーザーの変更が上書きされることもあります。
ビジネス・ロジック層でのロックの取得は、次のように行われます。
最上位レベルのアプリケーション・モジュールに関連付けられているトランザクションで(デフォルトの)即時ロックを使用しており、その行がデータベースの既存の行である場合、ビジネス・ロジック層は、いずれかの属性が最初に正常に修正された場合(検証が問題なく終了したときなど)にデータベース行をロックしようとします。(エンティティ・オブジェクトがコンポジットの一部である場合は、フレームワークは、最上位レベルのエンティティ・オブジェクトに関連付けられている行を最初にロックしようとします。)行を正常にロックできた場合は、ビジネス・ロジック層で行を修正できます。ビジネス・ロジック層で行をロックできない場合は、例外がスローされ、値は修正前の状態に戻ります。
データをポストする際に、最上位レベルのアプリケーション・モジュールのトランザクションでコミット時ロックを使用している場合は、ビジネス・ロジック層では、削除または修正済のエンティティ・オブジェクト・インスタンスに対応する各行をロックしようとします。(エンティティ・オブジェクトがコンポジットの一部である場合は、フレームワークは、最上位レベルのエンティティ・オブジェクトに関連付けられている行を最初にロックしようとします。)ロックに対するこれらの試行のうち、いずれかが失敗した場合は、例外がスローされます。
セッション指向プログラムでは、アプリケーションがすぐにエラーを検出し、コミット時にトランザクションが異常終了する可能性が低いため、即時ロックが最適です。セッション指向以外のプログラムでは、コミット時ロックが適切です。たとえば、ユーザーが数日間にわたってWebページを画面に開いたままにするときなどは、レコードをロックする必要はありません。
ロックは、oracle.jbo.Transactionインタフェースで定義されています。ロック・モードを設定するにはsetLockingModeメソッドを使用し、現在のロック・モードを取得するにはgetLockingModeメソッドを使用します。(ロックの取得後に)エンティティ・キャッシュ内のエンティティ・オブジェクト・インスタンスの修正が正常に行われると、行にデータを問い合せたすべてのビュー・オブジェクト・インスタンスにそのことが通知され、クライアントは表示しているデータをリフレッシュできます。
行をロックした後で、エンティティ・オブジェクトは、ロックの取得中にデータベースから取得したデータが、エンティティ・キャッシュ内の修正前のデータと一致していることを検証します。一致していない場合は、oracle.jbo.RowInconsistExceptionがスローされます。これにより、他のデータベース・セッションのプログラムが行を修正した場合に、アプリケーションで気付かずに修正を上書きしないようにします。
ロック、フォルトインおよび更新には、様々なデータ解決プロセスがあります。
ロックの場合、ビジネス・ロジック層がデータを解決する方法は、次のとおりです。
差異がない場合は、データベースのデータがキャッシュに格納されます。差異がある場合は、RowInconsistExceptionがスローされます。コードで例外を捕捉して、差異を解決できます。
たとえば、共同当座預金口座に100ドルの残高があるとし、2人の名義人がATMを使用するとします。
フォルトインの場合、ビジネス・ロジック層がキャッシュ内の属性を変更すると、変更された値が保持されます。ビジネス・ロジック層が値を変更しなかった場合は、データベースから取得された値が使用されます。
更新の場合、ビジネス・ロジック層は更新の前にロックして値を比較します。このため、データが変更された場合はすぐにわかります。
最上位レベルのアプリケーション・モジュールには、トランザクション・オブジェクトが関連付けられています。このオブジェクトにより、エンティティ・キャッシュとデータベースの対話が管理されます。このため、トランザクション・オブジェクトは、そのトランザクション中に変更されたエンティティ・オブジェクト・インスタンスのリストであるトランザクション・リストを保持します。
コミット・サイクルとロールバック・サイクルは、通常次のように行われます。
クライアントがTransactionのpostChangesまたはcommitをコールすると、トランザクション・オブジェクトはトランザクション・リストを反復処理し、各エンティティ・オブジェクトについてdoDMLメソッドをコールして、データベースにおいて削除、データ更新および挿入の操作を実行します。
コミットにより、postChangesがコールされることに注意してください。
通常、トランザクション・オブジェクトは、トランザクション・リストのエンティティ・オブジェクトを、そのエンティティ・オブジェクトが最初にこのトランザクションで変更された順に処理します。ただし、変更が処理される順序は保証されていません。ただし、コンポジットでは、トランザクション・オブジェクトは、親エンティティ・オブジェクトを最初に処理してから子エンティティ・オブジェクトを処理します。
データのポストが終了した後、トランザクション・リストの内容が消去されます。
データが、データベースにコミットまたはロールバックされます。
ロックが解放されます。
データベースに行を挿入する順序は、Associationの場合、重要です。たとえば、EmployeesとDepartmentsの間にAssociationがあり、2つのエンティティ・オブジェクトを関連付けるキーがDepartment_Idであるとします。まず、まだ作成されていない部門のDepartment_Idを持つ従業員を作成します。次に、その新しいDepartment_Idの部門を作成します。これらの変更をポストすると、トランザクション・オブジェクトによって、最初に新しい従業員(新規Department_Idを含む)が作成され、その後で部門が作成されます。データベースに、Employees表の外部キー参照整合性制約がある場合には、データベース・エラーが発生します。これは、トランザクション・オブジェクトがEmployees表に行を作成しようとしたが、関連する部門がまだ作成されていなかったためです。これはポスト順序の問題で、関連付けられた行が適切な順序で作成されなかったためです。
ポスト順序の問題を回避する1つの方法は、コンポジットを使用することです。コンポジットでは、トランザクション・オブジェクトで、必ず親エンティティ・オブジェクトを最初にデータベースに書き込んでから子を書き込みます。EmployeesとDepartmentsの例では、Associationをコンポジットとし、Departmentsを親、Employeesを子としてマークしておくと、新しい部門が最初に作成され、参照整合性エラーは発生しません。
ポストの対象となるのは有効な行のみです。エンティティ・オブジェクトは、ポストの前に各行が正しいことを確認します。
「新しい行を作成する方法」のコードSnippetで、検証およびポストについて示しています。
次のコードSnippetでは、(データベースを変更するサンプルは必要としないため)トランザクションのロールバックを示します。
状態の図は、「エンティティ・オブジェクト行の状態」を参照してください。
// Illustrate how to roll back the transaction, making sure that // running this client doesn't *actually* change any data // in the database. show("Rolling back the transaction"); am.getTransaction().rollback(); show("Showing the values of the modified Emp after rollback"); show(rowValues(mgrRow,mgrVO)); show("NOTE: Salary is back to original value"); // Disconnect the application module am.getTransaction().disconnect();
次のコードSnippetでは行が削除されます。行が削除された後で、その属性を操作しようとするとエラーになります。
// Illustrate removing a row from the Employees view object show("About to Remove New Employee 1001"); newEmp.remove(); // Illustrate that an attempt to work with a row after it has // been deleted will raise an error! show("About to try setting attribute on removed row"); try { newEmp.setAttribute("CommissionPct",new Number(0)); } catch (Exception ex) { show(FormatException(ex)); }
ビュー・オブジェクトまたはアプリケーション・モジュールが破棄されると、関連するビュー・キャッシュも破棄されます。アプリケーション・モジュールでのclearVOCaches、トランザクションでのclearEntityCache、ビュー・オブジェクトでのclearCacheなど、特定のメソッドをコールしてビュー・キャッシュの内容を明示的に消去することもできます。
エンティティ・キャッシュのインスタンスを示しているビュー・キャッシュが存在せず、エンティティ・インスタンスが修正されていない場合は、そのインスタンスは消去の対象とみなされます。行が削除されるタイミングは、使用しているJVMによって決まります。通常、JVMは、メモリーが不足した際に行を削除します。
Business Components for Javaでは、キャッシュの内容を消去するメソッドが提供されます。これらのメソッドは、主に次の2つの目的で使用されます。
キャッシュの内容を消去するには、次のメソッドを使用できます。
最後の2組のメソッドでは、コミットまたはロールバックの後でエンティティ・キャッシュの内容を自動的に消去するかどうかを制御するフラグが設定(または確認)されます。たとえば、setClearCacheOnCommit(true)がトランザクションでコールされた場合、トランザクションがコミットされると必ずすべてのエンティティ・キャッシュの内容が消去されます。その後、エンティティ・オブジェクトにアクセスすると、データベースのデータでリフレッシュされます。
デフォルトでは、isClearCacheOnCommitはfalse、isClearCacheOnRollbackはtrueであることに注意してください。このため、デフォルトではロールバック後にエンティティ・キャッシュの内容が消去されます。ただし、コミット後はエンティティ・キャッシュは保持されます(内容は消去されません)。
同一アプリケーション・モジュール・インスタンスで、ビュー・オブジェクトEmpViewの2つのインスタンス(EmpView1およびEmpView2)があり、2つともEmpエンティティ・オブジェクトを使用する場合、動作は次のようになります。
挿入がこのような動作になるのは、次の2つの理由からです。
データを取得するために同じAssociationまたはビュー・リンクに何度もアクセスする場合、取得しようとしているデータはすでにキャッシュされています。データの問合せを行った後で他の接続によってデータが変更された場合は、データベースの新しいデータは表示されません。このため、validateEntityメソッドを使用して、取得したデータの検証を実行する場合に、失効したデータを誤って使用して検証を実行する可能性があります。
検証を実行するときに常に最新データを認識することがアプリケーションにとって重要な場合は、最新データの取得に、プログラミング上のテクニックを要します。アクセッサがRowIteratorを返す場合、RowSetにキャストし、executeQueryメソッドをコールして、検証で使用する属性値を取得する前に、データベースからの再問合せを強制実行できます。この方法は、次のような場合に使用できます。
次に例を示します。
public void validateEntity() {
String list = "";
RowIterator ri = getEmp();
boolean found = false; ((RowSet) ri).executeQuery();
while (ri.hasNext()) {
String ename = (String)ri.next().getAttribute("Ename");
if (ename.equalsIgnoreCase("STEVE")) {
found = true;
}
else {
list += " "+ename;
}
}
if (found) {
System.out.println("Found STEVE among employees in this department");
throw new JboException("Cannot have employee named STEVE");
}
else {
System.out.println("Found ("+list+") but no STEVE");
}
}