ビジネス・ロジック層におけるキャッシュ・データ処理方法

ビジネス・ロジック層では、データベースおよびクライアントから受信したデータを、エンティティ・キャッシュおよびビュー・キャッシュの2つのタイプのキャッシュに格納します。この独自の高度なキャッシュ・システムでは、多数の複雑なデザイン・パターンおよびプログラムによるタスクが処理されます。このため、ユーザーは、アプリケーション固有のビジネス・ロジックおよびクライアント・インタフェースの実装に専念できます。

キャッシュ・システムがどのように機能するかを知らなくても、ビジネス・コンポーネント・アプリケーション全体を実装できます。ただし、主要な機能を理解すると、実装および最適化を行う上で適切な決定をするのに役立ちます。また、経験豊富なJavaプログラマは、アプリケーション固有の理由から、キャッシュ・システムをカスタマイズする、あるいは使用しないことも可能です(ただし、このような処理が必要になることはほとんどありません)。このトピックでは、キャッシュ・システムの機能、およびユーザーが制御できる部分について説明します。

具体的に説明するために、主要な概念にはそれぞれ、キャッシュでの変更の要因となる一般的なシナリオを示すコードSnippetを記載します。このサンプルでは、Oracle9i で提供されるHRスキーマが使用されます。別のバージョンのデータベースを使用している場合、表のインストール方法の詳細は、「サンプル・スキーマ表の作成および移入」を参照してください。

このトピックを読む前に、ビジネス・コンポーネントおよびフレームワークについて理解する必要があります。これらは、オンライン・ヘルプの「Business Components for Javaの概要」ブックで説明されています。

ビジネス・ロジック層でキャッシュを使用する理由

ビジネス・ロジック層では、主に次の5つの理由から、ビジネス・コンポーネントのインスタンスをメモリーにキャッシュします。

キャッシュを構成するクラス

oracle.jbo.serverパッケージの一部のフレームワーク・クラスは、キャッシュに影響します。コードでキャッシュを使用する方法を理解するには、この関係の概要を把握しておくと役立ちます。

セッションで使用される各EntityDefImplインスタンスについては、最上位レベルのアプリケーション・モジュールが、EntityImplインスタンスを含むエンティティ・キャッシュを管理します。

ビュー・オブジェクトがビュー・キャッシュを管理します。ビュー・キャッシュには行セットが含まれます。行セットには、ViewRowImplインスタンスが含まれます。

これらの関係をよく理解できるよう、アプリケーションが実行を開始し、データベースからデータを取得して、その後、データベースのデータを更新していくとします。

問合せによって最初に空のキャッシュに移入する方法

通常は、最初にアプリケーションの実行が開始されると、ビュー・オブジェクト問合せによってキャッシュにデータが移入されます。ビュー・オブジェクト問合せを実行すると、問合せの結果セットの各行がビュー行にキャッシュされます。

キャッシュへの移入処理は、通常次のように行われます。

  1. クライアントにより、最上位レベルのアプリケーション・モジュールが作成されます。
    ビジネス・ロジック層を使用するには、クライアントは最上位レベルのアプリケーション・モジュール・インスタンス(ルート・アプリケーション・モジュールとも呼ばれる)を作成します。このインスタンスには、ビュー・オブジェクト・インスタンス、ビュー・リンク・インスタンス、およびネストされたアプリケーション・モジュール・インスタンスが含まれています。最上位レベルの各アプリケーション・モジュール・インスタンスには、それぞれのキャッシュ、およびデータベースへの接続(セッション)があります。インスタンスは、その中に含まれるビュー・オブジェクト・インスタンス(ネストされたアプリケーション・モジュール・インスタンスに含まれるものも含む)のトランザクション・コンテキストも提供します。したがって、最上位レベルのアプリケーション・モジュール・インスタンスは、ビュー・オブジェクト・インスタンスのコンテナであり、またネストされているアプリケーション・モジュール・インスタンス(キャッシュ、接続およびトランザクションを共有しているインスタンス)のコンテナとしても機能します。

  2. クライアントにより、ビューオブジェクトの検索または作成が行われます。

    ビュー・オブジェクトがアプリケーション・モジュールによって作成されている場合は、クライアントはビュー・オブジェクトを検索します。それ以外の場合は、クライアントはビュー・オブジェクトを作成します。

  3. ビュー・オブジェクトにより、問合せが実行されます。

    通常、各ビュー・オブジェクトにはSQL問合せが含まれています。クライアントにより問合せを実行するメソッドがコールされるか、クライアントで1つ以上の行が要求されます(問合せがまだ実行されていない場合は、これにより、ビュー・オブジェクトが問合せを実行します)。

  4. ビュー・オブジェクトにより、要求されたデータの最初の行がデータベースから取得されます。

    または、クライアントがすべての行を要求することもできます。

    特に指定されない場合は、ビュー・オブジェクトはデフォルトでは結果セットのすべての行を一度に取得しません。たとえば、最初のメソッドでは、最初の行のみをキャッシュに格納し、残りの行は格納しません。同様に、次のメソッドでは次の行のみをキャッシュに格納し、残りの行は格納しません。

  5. ビュー・オブジェクトにより、取得した各データベース行に対して空白のビュー行が作成されます。ビュー・オブジェクトにより、このビュー・オブジェクトに関連する各エンティティ・オブジェクトについて空のEntityImplインスタンスが作成されます。

  6. ビュー・オブジェクトにより、属性値がインスタンスに追加されます。

    ビュー・オブジェクトにより、マップされた属性がEntityImplインスタンスに移入されます。ビュー・オブジェクトにより、データベースに永続的に存在しない属性がビューに移入されます。

  7. ビュー行により、EntityImplインスタンスがエンティティ・キャッシュに追加されます。

    インスタンスは主キーによって識別されます。

  8. ビュー行が、ビュー・キャッシュの行セットに追加されます。

たとえば、次のコード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メソッドを使用してエンティティ・オブジェクト全体を検索すると、すべての属性値がエンティティ・キャッシュに追加されます。(この場合には、キャッシュ内のデータに優先順位が設定され、後述するように、データが存在しない場合はデータベースから取得されます。)ビュー・オブジェクトを使用してデータを検索すると、そのタスクに関連するすべての属性を取得し、その他の属性は除外できるため、より柔軟に処理できます。ビュー・オブジェクトは問合せを実行し、属性にデータが含まれていない場合には、その属性にデータを追加します。

新しい行を作成する方法

新しい行の挿入は、通常次のように行われます。
  1. クライアントにより、createRowメソッドがコールされます。

    ビュー・オブジェクトにより、空白のビュー行が作成されます。ビュー行およびEntityImplはまだキャッシュには入っていません。

  2. クライアントにより、主キーが設定されます。

    新しいEntityImplがエンティティ・キャッシュに追加されます。新しい行がトランザクション・リストに入り、エンティティ・キャッシュにも入ります。

    ROWIDが主キーの場合は、フレームワークによって一時値が割り当てられます。

  3. クライアントにより、ビュー・オブジェクトに対してinsertRowメソッドがコールされます。

    新しいビュー行がビュー・キャッシュに追加されます。

この処理を表すコード・サンプルを次に示します。また、ここでは、必須フィールドのビルトインの検証および変更のポストも示されます。


// 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");

フォルトインが発生する場合

たとえば、特定のエンティティ属性にマップされるビュー属性がビュー・オブジェクトにないために、属性が移入されていない場合など、エンティティ・キャッシュに属性がない場合があります。ビジネス・ロジックが、データベース行に保存されている属性値にアクセスしようとして、その値がエンティティ・キャッシュにない場合には、フォルトインが発生します。行の空の属性がすべてデータベースから移入され、ビジネス・ロジックが完了します。次に例を示します。

  1. Employeesビュー・オブジェクトには、Employee_Id属性およびLast_Name属性があります。
  2. Employeesエンティティ・オブジェクトには、Employee_Id属性、Last_Name属性およびSalary属性があります。
  3. ビュー・オブジェクト問合せにより、Employee_Id属性およびLast_Name属性がエンティティ・キャッシュに移入されますが、Salary属性は移入されません。
  4. 検証メソッドがSalaryにアクセスします。
  5. ビジネス・ロジック層では、データベースから行のすべての属性を取得します。

フォルトインにより、ビジネス・ロジックに必要な値が含まれることが保証されます。ビジネス・ロジックで必要なすべての属性がビュー・オブジェクト問合せに必ず含まれるようにして、フォルトインを回避し、最適化を図ることができます。このようにすると、必要以上に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つの場合です。

ロックが行われる仕組み

ロックには、即時ロックとコミット時ロックの2種類があります。また、ロックを使用しないように選択することもできますが、これは推奨されません。アプリケーションの特性上、2人のユーザーが同じ行を同時に修正することがほとんどないと思われる場合は、コミット時ロックを使用します。即時ロックは、最も一般的なロックの方法です。

アプリケーションでロックが使用される場合、ビジネス・ロジック層は次のような場合に行をロックしようとします。

アプリケーションでロックを使用しない場合、行が別のユーザーによってロックされていると、エンティティ・オブジェクトは、ロックしたユーザーが行を解放するまで待機します。その後、エンティティ・オブジェクトが変更内容を書き込みます。これによって、前のユーザーの変更が上書きされることもあります。

ビジネス・ロジック層でのロックの取得は、次のように行われます。

  1. ビジネス・ロジック層が、行をロックするためのDML文をデータベースに送信します。
    次の一般的な構文が使用されます。
    select ... from ... where PK = value for update nowait
  2. ロックが正常に実行されると、(同じ文によって)データベースの最新データが取得されます。
  3. 次のセクションで説明するように、ビジネス・ロジック層が、キャッシュ内のデータを使用してデータベースのデータを解決します。

最上位レベルのアプリケーション・モジュールに関連付けられているトランザクションで(デフォルトの)即時ロックを使用しており、その行がデータベースの既存の行である場合、ビジネス・ロジック層は、いずれかの属性が最初に正常に修正された場合(検証が問題なく終了したときなど)にデータベース行をロックしようとします。(エンティティ・オブジェクトがコンポジットの一部である場合は、フレームワークは、最上位レベルのエンティティ・オブジェクトに関連付けられている行を最初にロックしようとします。)行を正常にロックできた場合は、ビジネス・ロジック層で行を修正できます。ビジネス・ロジック層で行をロックできない場合は、例外がスローされ、値は修正前の状態に戻ります。

データをポストする際に、最上位レベルのアプリケーション・モジュールのトランザクションでコミット時ロックを使用している場合は、ビジネス・ロジック層では、削除または修正済のエンティティ・オブジェクト・インスタンスに対応する各行をロックしようとします。(エンティティ・オブジェクトがコンポジットの一部である場合は、フレームワークは、最上位レベルのエンティティ・オブジェクトに関連付けられている行を最初にロックしようとします。)ロックに対するこれらの試行のうち、いずれかが失敗した場合は、例外がスローされます。

セッション指向プログラムでは、アプリケーションがすぐにエラーを検出し、コミット時にトランザクションが異常終了する可能性が低いため、即時ロックが最適です。セッション指向以外のプログラムでは、コミット時ロックが適切です。たとえば、ユーザーが数日間にわたってWebページを画面に開いたままにするときなどは、レコードをロックする必要はありません。

ロックは、oracle.jbo.Transactionインタフェースで定義されています。ロック・モードを設定するにはsetLockingModeメソッドを使用し、現在のロック・モードを取得するにはgetLockingModeメソッドを使用します。(ロックの取得後に)エンティティ・キャッシュ内のエンティティ・オブジェクト・インスタンスの修正が正常に行われると、行にデータを問い合せたすべてのビュー・オブジェクト・インスタンスにそのことが通知され、クライアントは表示しているデータをリフレッシュできます。

行をロックした後で、エンティティ・オブジェクトは、ロックの取得中にデータベースから取得したデータが、エンティティ・キャッシュ内の修正前のデータと一致していることを検証します。一致していない場合は、oracle.jbo.RowInconsistExceptionがスローされます。これにより、他のデータベース・セッションのプログラムが行を修正した場合に、アプリケーションで気付かずに修正を上書きしないようにします。

データベースのデータを既存のキャッシュに格納する方法

ロック、フォルトインおよび更新には、様々なデータ解決プロセスがあります。

ロックの場合、ビジネス・ロジック層がデータを解決する方法は、次のとおりです。

差異がない場合は、データベースのデータがキャッシュに格納されます。差異がある場合は、RowInconsistExceptionがスローされます。コードで例外を捕捉して、差異を解決できます。

たとえば、共同当座預金口座に100ドルの残高があるとし、2人の名義人がATMを使用するとします。

  1. 名義人1が残高を照会して100ドルを確認すると、これがエンティティ・キャッシュに移入されます。
  2. 名義人1が20ドルの引出しを要求します。
  3. 名義人2が残高を照会して100ドルを確認すると、これが別のエンティティ・キャッシュに移入されます。
  4. 名義人1のトランザクションがロックを取得し、setAttributeをコールして残高を80ドルに設定して、トランザクションをコミットします。
  5. 名義人2が50ドルの引出しを選択します。エンティティ・キャッシュの値が変更されているため、RowInconsistExceptionがスローされます。

フォルトインの場合、ビジネス・ロジック層がキャッシュ内の属性を変更すると、変更された値が保持されます。ビジネス・ロジック層が値を変更しなかった場合は、データベースから取得された値が使用されます。

更新の場合、ビジネス・ロジック層は更新の前にロックして値を比較します。このため、データが変更された場合はすぐにわかります。

コミット・サイクルとロールバック・サイクル

最上位レベルのアプリケーション・モジュールには、トランザクション・オブジェクトが関連付けられています。このオブジェクトにより、エンティティ・キャッシュとデータベースの対話が管理されます。このため、トランザクション・オブジェクトは、そのトランザクション中に変更されたエンティティ・オブジェクト・インスタンスのリストであるトランザクション・リストを保持します。

コミット・サイクルとロールバック・サイクルは、通常次のように行われます。

  1. クライアントがTransactionのpostChangesまたはcommitをコールすると、トランザクション・オブジェクトはトランザクション・リストを反復処理し、各エンティティ・オブジェクトについてdoDMLメソッドをコールして、データベースにおいて削除、データ更新および挿入の操作を実行します。

    コミットにより、postChangesがコールされることに注意してください。

    通常、トランザクション・オブジェクトは、トランザクション・リストのエンティティ・オブジェクトを、そのエンティティ・オブジェクトが最初にこのトランザクションで変更された順に処理します。ただし、変更が処理される順序は保証されていません。ただし、コンポジットでは、トランザクション・オブジェクトは、親エンティティ・オブジェクトを最初に処理してから子エンティティ・オブジェクトを処理します。

  2. データのポストが終了した後、トランザクション・リストの内容が消去されます。

  3. データが、データベースにコミットまたはロールバックされます。

  4. ロックが解放されます。

データベースに行を挿入する順序は、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();

状態の図は、「エンティティ・オブジェクト行の状態」を参照してください。

行を削除する方法

行の削除は、通常次のように行われます。
  1. クライアントが行を削除するときは、ViewRowのremoveメソッドまたはRowSetIteratorのremoveCurrentRowメソッドをコールします。

    後者では、行セット・イテレータの現在の行についてremoveメソッドがコールされることに注意してください。

  2. 即時ロックおよびコミット時ロックのどちらの場合でも、ビジネス・ロジック層はビュー行を構成するエンティティ行をロックします。次に、EntityImplのremoveメソッドをコールします。

    このコールによって、エンティティ行が削除済としてマークされ、そのエンティティ行を使用するすべてのビュー行を無効としてマークするイベントが送信されます。無効とマークされたビュー行はビュー・キャッシュから削除されます。

  3. ユーザーがpostChangesメソッド(またはcommit)をコールすると、削除済としてマークされた各エンティティ行について、対応するデータベース行が削除されます。

  4. トランザクションがコミットされると、エンティティ行が無効としてマークされ、キャッシュから削除されます。

次のコード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");
}
}


関連項目
検証ロジックについて
エンティティ・オブジェクトとは
ビュー・オブジェクトとは
アプリケーション・モジュールとは