XEP Event Persistence の使用法
XEP は、Java 構造化データの非常に高速な格納および取得を行い、TCP/IP 接続を介して Caché データベースとの通信を可能にします。XEP は、複雑なデータ構造の最適なマッピングのためのスキーマ生成を制御する方法を提供しますが、単純なデータ構造のスキーマは、多くの場合、変更することなく生成および使用できます。
この章で説明する項目は以下のとおりです。
-
Event Persistence の概要 — 永続イベントの概念および用語について紹介し、XEP API を使用するコードの簡単な例を示します。
-
EventPersister の作成と接続 — EventPersister クラスのインスタンスを作成する方法、およびそれを使用して TCP/IP データベース接続を開き、テストし、閉じる方法について説明します。
-
スキーマのインポート — Java クラスを分析するため、および対応する永続イベントのスキーマを生成するためのメソッドとアノテーションについて説明します。
-
イベントの格納と変更 — 永続イベントの格納、変更、および削除に使用する Event クラスのメソッドについて説明します。
-
クエリの使用法 — クエリ結果セットを作成および処理する XEP クラスのメソッドについて説明します。
-
XEP からの Caché メソッドの呼び出し — XEP アプリケーションから ObjectScript メソッド、関数、およびプロシージャを呼び出すことができる EventPersister メソッドについて説明します。
-
スキーマのカスタマイズとマッピング — Java クラスがイベント・スキーマにどのようにマップされるかについて詳しく説明するほか、最適なパフォーマンスを得るためのカスタマイズ・スキーマの生成方法についても詳細に説明します。
Event Persistence の概要
永続イベントは、Java オブジェクトにデータ・フィールドの永続コピーを格納する Caché データベース・オブジェクトです。既定では、XEP は、各イベントを標準の %PersistentOpens in a new tab オブジェクトとして格納します。ストレージは、その他の手段 (オブジェクト、SQL、直接グローバル・アクセスなど) でデータが Caché にアクセスできるように自動的に構成されます。
永続イベントを作成して格納するには、事前に XEP が対応する Java クラスを分析して、スキーマをインポートする必要があります。このスキーマは、Java オブジェクトのデータ構造を永続イベントにどのように投影するのかを定義します。スキーマは、以下の 2 つのオブジェクト・プロジェクション・モデルのうちのいずれかを使用できます。
-
既定のモデルは、フラット・スキーマです。そのスキーマでは、参照オブジェクトがすべてシリアル化されて、インポートされたクラスの一部として格納され、スーパークラスから継承したフィールドはすべて、それらが、インポートされたクラスのネイティブ・フィールドであるかのように格納されます。これは、最も高速かつ効率的なモデルですが、元の Java クラス構造に関する情報は何も保持されません。
-
構造情報を保持する必要がある場合は、フル・スキーマ・モデルを使用できます。このモデルでは、Java ソース・クラスと Caché の投影されたクラスとの間に 1 対 1 のリレーションシップを作成することで、完全な Java 継承構造が保持されますが、パフォーマンスがわずかに劣化することがあります。
両方のモデルの詳細は “スキーマ・インポート・モデル” を参照してください。各種のデータ型の投影方法の詳細は “スキーマ・マッピング・ルール” を参照してください。
XEP は、スキーマのインポート時に Java クラスを分析して基本情報を取得します。XEP がインデックスを生成 (“IdKey の使用法” を参照してください) して、フィールドのインポートに関する既定のルールをオーバーライド (“アノテーションの使用法” と “InterfaceResolver の実装” を参照してください) できるようにする追加情報を提供することができます。
スキーマがインポートされると、XEP を使用して非常に高いレートでデータの格納、照会、更新、および削除を行えます。格納されたイベントは、照会に対して、あるいは完全オブジェクトまたはグローバル・アクセスに対してすぐに使用できます。EventPersister、Event、および EventQuery<> クラスは、XEP API のメイン機能を提供します。これらのクラスは、以下の順序で使用されます。
-
EventPersister クラスは、TCP/IP データベース接続を確立して制御するメソッドを提供します (“EventPersister の作成と接続” を参照してください)。
-
接続が確立されたら、他の EventPersister メソッドを使用して、スキーマをインポートできます (“スキーマのインポート” を参照してください)。
-
Event クラスは、イベントの格納、更新、または削除、クエリの作成、インデックス更新の制御を行うメソッドを提供します (“イベントの格納と変更” を参照してください)。
-
EventQuery<> クラスは、データベースから一連のイベントを取得する単純な SQL クエリを実行するために使用されます。このクラスは、結果セットを繰り返し処理し、個別のイベントの更新および削除を行うためのメソッドを提供します (“クエリの使用法” を参照してください)。
次のセクションでは、これらの機能をすべてデモンストレーションする 2 つの非常に簡潔なアプリケーションについて説明します。
永続イベントを格納および照会する単純なアプリケーション
このセクションでは、XEP を使用して永続イベントを作成し、それにアクセスする 2 つの非常に単純なアプリケーションについて説明します。
-
StoreEvents プログラム — Caché データベースへの TCP/IP 接続を開き、イベントを格納するためのスキーマを作成し、Event のインスタンスを使用してオブジェクトの配列を永続イベントとして格納し、接続を閉じて終了します。
-
QueryEvents プログラム — StoreEvents と同じネームスペースにアクセスする新しい接続を開き、EventQuery<> のインスタンスを作成して以前に格納されたイベントを読み取りおよび削除し、接続を閉じて終了します。
これらのアプリケーションは、システムを排他的に使用し、2 つの連続したプロセスで実行されることが前提となっています。
どちらのプログラムも、XEP サンプル・プログラムで定義されているクラスの 1 つである xep.samples.SingleStringSample のインスタンスを使用します (サンプル・プログラムの詳細は “XEP のサンプル” を参照してください)。
StoreEvents プログラム
StoreEvents では、EventPersister の新しいインスタンスが作成され、特定の Caché ネームスペースに接続されます。SingleStringSample クラス用にスキーマがインポートされ、テスト・データベースが、そのクラスのエクステントから既存のイベントをすべて削除することで初期化されます。Event のインスタンスが作成され、SingleStringSample オブジェクトの配列を永続イベントとして格納するために使用されます。その後、接続が終了されます。その新しいイベントは Caché データベース内で永続化され、QueryEvents プログラムによってアクセスされるようになります (次のセクションで説明します)。
import com.intersys.xep.*;
import xep.samples.SingleStringSample;
public class StoreEvents {
private static String className = "xep.samples.SingleStringSample";
private static SingleStringSample[] eventItems = SingleStringSample.generateSampleData(12);
public static void main(String[] args) {
for (int i=0; i < eventItems.length; i++) {
eventItems[i].name = "String event " + i;
}
try {
System.out.println("Connecting and importing schema for " + className);
EventPersister myPersister = PersisterFactory.createPersister();
myPersister.connect("127.0.0.1",1972,"User","_SYSTEM","SYS");
try { // delete any existing SingleStringSample events, then import new ones
myPersister.deleteExtent(className);
myPersister.importSchema(className);
}
catch (XEPException e) { System.out.println("import failed:\n" + e); }
Event newEvent = myPersister.getEvent(className);
long[] itemIDs = newEvent.store(eventItems); // store array of events
System.out.println("Stored " + itemIDs.length + " of "
+ eventItems.length + " objects. Closing connection...");
newEvent.close();
myPersister.close();
}
catch (XEPException e) {System.out.println("Event storage failed:\n" + e);}
} // end Main()
} // end class StoreEvents
StoreEvents.main() が呼び出される前に、xep.samples.SingleStringSample.generateSampleData() メソッドが呼び出されて、サンプル・データ配列 eventItems が生成されます (サンプル・クラスの詳細は “XEP のサンプル・アプリケーション” を参照してください)。
この例では、XEP メソッドは以下のアクションを実行します。
-
PersisterFactory.createPersister() は、EventPersister の新しいインスタンスである myPersister を作成します。
-
EventPersister.connect() は、User ネームスペースへの TCP/IP 接続を確立します。
-
EventPersister.importSchema() は、SingleStringSample クラスを分析し、そのスキーマをインポートします。
-
EventPersister.deleteExtent() を呼び出すと、SingleStringSample エクステントから既存のテスト・データを削除することで、データベースがクリーンアップされます。
-
EventPersister.getEvent() は、SingleStringSample イベントを処理するために使用される Event の新しいインスタンスである newEvent を作成します。
-
Event.store() は、入力として eventItems 配列を受け入れ、その配列内のオブジェクトごとに新しい永続イベントを作成します。(代わりに、コードで eventItems 配列をループして個々のオブジェクトごとに store() を呼び出すこともできますが、この例ではそのようにする必要はありません。)
-
Event.close() および EventPersister.close() は、イベントが格納された後に newEvent および myPersister に対して呼び出されます。これは、ネイティブ・コード・リソースを解放し、メモリ・リークを回避するために常に必要です。
これらのメソッドについては、すべてこの章で後で詳しく説明します。接続を開き、テストし、閉じることに関する詳細は、“EventPersister の作成と接続” を参照してください。スキーマの作成の詳細は、“スキーマのインポート” を参照してください。Event クラスの使用とエクステントの削除の詳細は、“イベントの格納と変更” を参照してください。
QueryEvents プログラム
この例では、QueryEvents は StoreEvents プロセスの終了直後に実行されるものと想定しています (“StoreEvents プログラム” を参照してください)。QueryEvents は、StoreEvents と同じネームスペースにアクセスする新しい TCP/IP データベース接続を確立します。EventQuery<> のインスタンスが作成され、前に格納されたイベントに繰り返し処理を行い、それらのデータを出力し、それらを削除します。
import com.intersys.xep.*;
import xep.samples.SingleStringSample;
public class QueryEvents {
public static void main(String[] args) {
EventPersister myPersister = null;
EventQuery<SingleStringSample> myQuery = null;
try {
// Open a connection, then set up and execute an SQL query
System.out.println("Connecting to query SingleStringSample events");
myPersister = PersisterFactory.createPersister();
myPersister.connect("127.0.0.1",1972,"User","_SYSTEM","SYS");
try {
Event newEvent = myPersister.getEvent("xep.samples.SingleStringSample");
String sql = "SELECT * FROM xep_samples.SingleStringSample WHERE %ID BETWEEN 3 AND ?";
myQuery = newEvent.createQuery(sql);
newEvent.close();
myQuery.setParameter(1,12); // assign value 12 to SQL parameter 1
myQuery.execute();
}
catch (XEPException e) {System.out.println("createQuery failed:\n" + e);}
// Iterate through the returned data set, printing and deleting each event
SingleStringSample currentEvent;
currentEvent = myQuery.getNext(null); // get first item
while (currentEvent != null) {
System.out.println("Retrieved " + currentEvent.name);
myQuery.deleteCurrent();
currentEvent = myQuery.getNext(currentEvent); // get next item
}
myQuery.close();
myPersister.close();
}
catch (XEPException e) {System.out.println("QueryEvents failed:\n" + e);}
} // end Main()
} // end class QueryEvents
この例では、XEP メソッドは以下のアクションを実行します。
-
EventPersister.createPersister() および EventPersister.connect() が再び呼び出され (StoreEvents の場合と同様)、User ネームスペースへの新しい接続が確立されます。
-
EventPersister.getEvent() は、SingleStringSample エクステントに対するクエリを作成するために使用される Event のインスタンスである newEvent を作成します。クエリが作成された後、newEvent は、その close() メソッドを呼び出すことで破棄されます。
-
Event.createQuery() は、SingleStringSample イベントの EventQuery<> のインスタンスである myQuery を作成します。SQL 文は、3 と変数パラメータ値の間のオブジェクト ID を持つ永続 SingleStringSample イベントすべてを取得するクエリを定義します。
-
EventQuery<>.setParameter() は、値 12 を SQL パラメータに割り当てます。
-
EventQuery<>.execute() はこのクエリを実行します。クエリが正常に実行された場合、myQuery に、そのクエリに一致する SingleStringSample イベントすべてのオブジェクト ID をリストする結果セットが含まれるようになります。
-
EventQuery<>.getNext() は、引数として null を指定して呼び出され、それにより、結果セットの最初の項目がフェッチされ、変数 currentEvent に割り当てられます。
-
while ループ内で以下のように実行されます。
-
currentEvent の name フィールドが出力されます。
-
EventQuery<>.deleteCurrent() がデータベースから最後にフェッチされたイベントを削除します。
-
EventQuery<>.getNext() が引数として最後にフェッチされたイベントを指定して再び呼び出され、そのメソッドが、そのイベントの次のイベントをフェッチする必要があることが指定されます。
これ以上項目がない場合、getNext() が null を返し、ループが終了します。
-
-
EventQuery<>.close() および EventPersister.close() は、すべてのイベントが出力および削除された後に myQuery および myPersister に対して呼び出されます。
これらのメソッドについては、すべてこの章で後で詳しく説明します。接続を開き、テストし、閉じることに関する詳細は、“EventPersister の作成と接続” を参照してください。EventQuery<> のインスタンスの作成および使用の詳細は、“クエリの使用法” を参照してください。
EventPersister の作成と接続
EventPersister クラスは、XEP API の主なエントリ・ポイントです。その API は、データベースへの TCP/IP 接続のオープン、スキーマのインポート、トランザクションの処理、および Event のインスタンスの作成を行い、データベース内のイベントにアクセスするためのメソッドを提供します。
EventPersister のインスタンスは、次のメソッドによって作成され、破棄されます。
-
PersisterFactory.createPersister() — EventPersister の新しいインスタンスを返します。
-
EventPersister.close() — この EventPersister インスタンスを閉じ、それに関連するネイティブ・コード・リソースを解放します。
以下のメソッドを使用して、TCP/IP 接続を作成します。
-
EventPersister.connect() — host、port、namespace、username、password の各引数を受け取り、指定された Caché ネームスペースへの TCP/IP 接続を確立します。
以下の例では、接続を確立します。
// Open a TCP/IP connection
String host = "127.0.0.1";
int port = 1972;
String namespace = "USER";
String username = "_SYSTEM";
String password = "SYS";
EventPersister myPersister = PersisterFactory.createPersister();
myPersister.connect(host, port, namespace,username,password);
// perform event processing here . . .
myPersister.close();
PersisterFactory.createPersister() メソッドは、EventPersister の新しいインスタンスを作成します。1 つのプロセスで必要なインスタンスは 1 つのみです。
EventPersister.connect() メソッドは、ホスト・マシンの指定されたポートとネームスペースへの TCP/IP 接続を確立します。現在のプロセス内に接続が存在しない場合は、新しい接続が作成されます。接続が既に存在している場合は、そのメソッドによって既存の接続オブジェクトへの参照が返されます。
アプリケーションを終了する準備が整ったら、EventPersister.close() メソッドを呼び出して、基盤となるネイティブ・コードによって使用されたリソースを必ず解放する必要があります。
接続に関連するすべてのロック、ライセンス、およびその他のリソースを確実に解放するために、範囲外になる前に必ず EventPersister のインスタンスで close() を呼び出します。
スキーマのインポート
Java クラスのインスタンスを永続イベントとして格納するには、その前にそのクラスのスキーマをインポートする必要があります。スキーマによって、イベントが格納されるデータベース構造が定義されます。XEP は、フラット・スキーマとフル・スキーマの 2 つの異なるスキーマ・インポート・モデルを提供しています。これらのモデル間の主な相違は、Java オブジェクトが Caché イベントに投影される方法です。パフォーマンスが重要であり、イベント・スキーマが比較的単純である場合、フラット・スキーマが最適な選択になります。フル・オブジェクトは、より複雑なスキーマ用のより豊富な機能を提供しますが、パフォーマンスに影響を与える可能性があります。スキーマ・モデルと関連テーマの詳細は、“スキーマのカスタマイズとマッピング” を参照してください。
以下のメソッドを使用して、Java クラスを分析し、目的のタイプのスキーマをインポートします。
-
EventPersister.importSchema() — フラット・スキーマをインポートします。.jar ファイル名、完全修飾クラス名、またはクラス名の配列を指定する引数を取り、すべてのクラスおよび指定された場所で検出されたすべての依存性をインポートします。正常にインポートされたクラスすべての名前を含む String 配列を返します。
-
EventPersister.importSchemaFull() — フル・スキーマをインポートします。importSchema() と同じ引数を取り、同じクラス・リストを返します。このメソッドによってインポートされるクラスは、ユーザ生成 IdKey を宣言する必要があります (“IdKey の使用法” を参照してください)。
-
Event.isEvent() — 静的 Event メソッド。引数としてすべての型の Java オブジェクト名またはクラス名を受け取り、指定されたオブジェクトを有効な XEP イベント (“インポートされるクラスの要件” を参照) として投影可能かどうかを調べるためのテストを行い、それが有効でない場合は適切なエラーをスローします。
インポート・メソッドは、使用されるスキーマ・モデルを除いて同一です。以下の例は、単純なテスト・クラスおよびその依存クラスをインポートします。
パッケージ test の以下のクラスがインポートされます。
public class MainClass {
public MainClass() {}
public String myString;
public test.Address myAddress;
}
public class Address {
public String street;
public String city;
public String state;
}
以下のコードは、isEvent() を呼び出してメイン・クラス test.MainClass が投影可能であることを確認してから、importSchema() を使用してそのメイン・クラスをインポートします。test.MainClass がインポートされるときに、依存クラス test.Address も自動的にインポートされます。
try {
Event.isEvent("test.MainClass"); // throw an exception if class is not projectable
myPersister.importSchema("test.MainClass");
}
catch (XEPException e) {System.out.println("Import failed:\n" + e);}
この例では、依存クラス test.Address のインスタンスは、シリアル化されて test.MainClass の他のフィールドとして同じ Caché オブジェクトに埋め込まれます。代わりに importSchemaFull() が使用された場合、格納された test.MainClass のインスタンスには、別の Caché クラス・エクステントに格納されている test.Address のインスタンスへの参照が含まれます。
イベントの格納と変更
クラスのスキーマがインポートされると (“スキーマのインポート” を参照)、Event のインスタンスを作成して、そのクラスのイベントを格納し、それにアクセスできます。Event クラスは、永続イベントの格納、更新、または削除、そのクラス・エクステントに対するクエリの作成、インデックス更新の制御を行うメソッドを提供します。このセクションでは、以下の項目について説明します。
-
イベントの作成と格納 — Event のインスタンスの作成方法およびそれを使用して指定したクラスの永続イベントを格納する方法について説明します。
-
格納されたイベントへのアクセス — 指定したクラスの永続イベントをフェッチ、変更、および削除する Event メソッドについて説明します。
-
インデックス更新の制御 — インデックス・エントリを更新するタイミングを制御することで処理の効率性を高めることができる Event メソッドについて説明します。
イベントの作成と格納
Event クラスのインスタンスは、以下のメソッドによって作成され、破棄されます。
-
EventPersister.getEvent() — className String 引数を取り、指定したクラスのイベントを格納し、それにアクセスできる Event のインスタンスを返します。オプションで、インデックス・エントリを更新する既定の方法を指定する indexMode 引数を取ります (詳細は、“インデックス更新の制御” を参照してください)。
Note:ターゲット・クラスEvent のインスタンスは、getEvent() の呼び出しで className 引数によって指定されたクラスのイベントのみを格納、アクセス、または照会できます。この章では、クラス className を、ターゲット・クラスと呼びます。
-
Event.close() — この Event インスタンスを閉じ、それに関連するネイティブ・コード・リソースを解放します。
以下の Event メソッドは、ターゲット・クラスの Java オブジェクトを永続イベントとして格納します。
-
store() — ターゲット・クラスの 1 つ以上のインスタンスをデータベースに追加します。引数として 1 つのイベント、または複数のイベントからなる 1 つの配列を取り、格納されているイベントごとに long データベース ID (データベース ID を返すことができない場合は 0) を返します。
Important:イベントが格納されるときは、どのようなテストも行われず、既存のデータが変更されたり、上書きされることはありません。各イベントは、可能な限り速い速度でエクステントに追加されるか、指定されたキーを持つイベントが既にデータベースに存在する場合は暗黙的に無視されます。
以下の例では、ターゲット・クラスとして SingleStringSample を指定して Event のインスタンスを作成し、それを使用して Java SingleStringSample オブジェクトの配列を永続イベントとして投影します。この例では、myPersister が既に作成されて接続されており、SingleStringSample クラスのスキーマがインポートされていることを前提としています。この実行方法の例は、“永続イベントを格納および照会する単純なアプリケーション” を参照してください。SingleStringSample クラスと、このクラスを定義および使用するサンプル・プログラムの詳細は、“XEP のサンプル・アプリケーション” を参照してください。
SingleStringSample[] eventItems = SingleStringSample.generateSampleData(12);
try {
Event newEvent = myPersister.getEvent("xep.samples.SingleStringSample");
long[] itemIdList = newEvent.store(eventItems); // store all events
int itemCount = 0;
for (int i=0; i < itemIdList.length; i++) {
if (itemIdList[i]>0) itemCount++;
}
System.out.println("Stored " + itemCount + " of " + eventItems.length + " events");
newEvent.close();
}
catch (XEPException e) {System.out.println("Event storage failed:\n" + e);}
-
SingleStringSample の generateSampleData() メソッドは、12 個の SingleStringSample オブジェクトを生成し、それらを eventItems という名前の配列に格納します。
-
EventPersister.getEvent() メソッドは、ターゲット・クラスとして SingleStringSample を指定して newEvent という名前の Event インスタンスを作成します。
-
Event.store() メソッドが呼び出され、eventItems 配列内の各オブジェクトをデータベース内の永続イベントとして投影します。
このメソッドは、itemIdList という名前の配列を返します。この配列には、正常に格納された各イベントの場合は long オブジェクト ID が、格納できなかったオブジェクトの場合は 0 が含まれます。変数 itemCount は、itemIdList の 0 より大きい各 ID に対して 1 回インクリメントされ、その合計が出力されます。
-
ループが終了すると、Event.close() メソッドが呼び出され、基盤となるネイティブ・コードによって使用されたリソースを解放します。
接続に関連するすべてのロック、ライセンス、およびその他のリソースを確実に解放するために、範囲外になる前に必ず EventPersister のインスタンスで close() を呼び出します。
格納されたイベントへのアクセス
永続イベントが格納されると、そのターゲット・クラスの Event インスタンスが、イベントを読み取り、更新、および削除するための以下のメソッドを提供します。
-
deleteObject() — 引数としてデータベース・オブジェクト ID または IdKey を取り、データベースから指定したイベントを削除します。
-
getObject() — 引数としてデータベース・オブジェクト ID または IdKey を取り、指定したイベントを返します。
-
updateObject() — 引数としてデータベース・オブジェクト ID または IdKey およびターゲット・クラスの Object を取り、指定したイベントを更新します。
ターゲット・クラスが標準オブジェクト ID を使用する場合、それは long 値として指定されます (前のセクションで説明した store() メソッドで返されるとおり)。ターゲット・クラスが IdKey を使用する場合、それは Object の配列として指定され、その配列の各項目はその IdKey を構成するフィールドの 1 つの値です (“IdKey の使用法” を参照してください)。
以下の例では、配列 itemIdList には、前に格納された SingleStringSample イベントのオブジェクト ID 値のリストが含まれています。例では、リストの最初の 6 個の永続イベントを適宜更新し、残りを削除します。
itemIdList 配列を作成した例は、“イベントの作成と格納” を参照してください。この例では、myPersister という名前の EventPersister インスタンスが既に作成され、データベースに接続されていることも前提としています。
// itemIdList is a previously created array of SingleStringSample event IDs
try {
Event newEvent = myPersister.getEvent("xep.samples.SingleStringSample");
int itemCount = 0;
for (int i=0; i < itemIdList.length; i++) {
try { // arbitrarily update events for first 6 IDs and delete the rest
SingleStringSample eventObject = (SingleStringSample)newEvent.getObject(itemIdList[i]);
if (i<6) {
eventObject.name = eventObject.name + " (id=" + itemIdList[i] + ")" + " updated!";
newEvent.updateObject(itemIdList[i], eventObject);
itemCount++;
} else {
newEvent.deleteObject(itemIdList[i]);
}
}
catch (XEPException e) {System.out.println("Failed to process event:\n" + e);}
}
System.out.println("Updated " + itemCount + " of " + itemIdList.length + " events");
newEvent.close();
}
catch (XEPException e) {System.out.println("Event processing failed:\n" + e);}
-
EventPersister.getEvent() メソッドは、ターゲット・クラスとして SingleStringSample を指定して newEvent という名前の Event インスタンスを作成します。
-
配列 itemIdList には、前に格納された SingleStringSample イベントのオブジェクト ID 値のリストが含まれています (itemIdList を作成した例は、“イベントの作成と格納” を参照してください)。
ループでは、itemIdList の各項目が処理されます。最初の 6 つの項目は変更および更新され、残りの項目は削除されます。以下の処理が実行されます。
-
Event.getObject() メソッドが、itemIdList[i] に指定されたオブジェクト ID を持つ SingleStringSample オブジェクトをフェッチして、これを変数 eventObject に割り当てます。
-
eventObject name フィールドの値が変更されます。
-
eventObject がリストの最初の 6 項目の 1 つである場合は、Event.updateObject() が呼び出されてデータベース内でこれが更新されます。そうでない場合は、Event.deleteObject() が呼び出されて、データベースからオブジェクトが削除されます。
-
-
itemIdList のすべての ID が処理されると、ループが終了し、更新されたイベントの数を示すメッセージが表示されます。
-
Event.close() メソッドが呼び出され、基盤となるネイティブ・コードによって使用されたリソースを解放します。
SingleStringSample クラスを定義および使用するサンプル・プログラムの詳細は、“XEP のサンプル・アプリケーション” を参照してください。
簡単な SQL クエリによってフェッチされた永続イベントへのアクセス方法、およびその変更方法の詳細は、“クエリの使用法” を参照してください。
テスト・データベースを初期化する際は、多くの場合、クラス全体を削除するか、指定されたエクステントのすべてのイベントを削除すると便利です。以下の EventPersister メソッドは、クラスおよびエクステントを Caché データベースから削除します。
-
deleteClass() — 引数として className 文字列を取り、指定した Caché クラスを削除します。
-
deleteExtent() — 引数として className 文字列を取り、指定したクラスのエクステントのすべてのイベントを削除します。
これらのメソッドは主としてテスト用に提供されているので、実際に使用するコードでは使用しないようにしてください。これらの用語の詳しい定義については、"Caché プログラミング入門ガイド" の “クラスとエクステント” を参照してください。
インデックス更新の制御
既定では、データベース内のイベントに対して動作する Event メソッドの 1 つの呼び出しを実行するときに、インデックスは更新されません (“格納されたイベントへのアクセス” を参照してください)。インデックスは非同期に更新され、更新は、すべてのトランザクションが完了し、Event インスタンスが閉じられた後にのみ実行されます。一意のインデックスに対して一意性のチェックは実行されません。
このセクションは、標準オブジェクト ID または生成された IdKey を使用するクラスにのみ適用されます (“IdKey の使用法” を参照してください)。ユーザ割り当て IdKey を持つクラスは、同期でのみ更新できます。
この既定のインデックス作成動作を変更する方法はいくつかあります。Event インスタンスが EventPersister.getEvent() によって作成される場合 (“イベントの作成と格納” を参照)、オプションの indexMode パラメータを設定して既定のインデックス作成動作を指定できます。以下のオプションを使用できます。
-
Event.INDEX_MODE_ASYNC_ON — 非同期のインデックス作成を可能にします。これは、indexMode パラメータが指定されていない場合の既定の設定です。
-
Event.INDEX_MODE_ASYNC_OFF — startIndexing() メソッドが呼び出されない限り、インデックス作成は実行されません。
-
Event.INDEX_MODE_SYNC — インデックス作成は、エクステントが変更されるたびに実行されます。これは多数のトランザクションの場合は非効率的になる可能性があります。クラスにユーザ割り当て IdKey がある場合は、このインデックス・モードを指定する必要があります。
以下の Event メソッドを使用すると、ターゲット・クラスのエクステントに対する非同期インデックス更新を制御できます。
-
startIndexing() — ターゲット・クラスのエクステントに対して非同期インデックス作成を開始します。インデックス・モードが Event.INDEX_MODE_SYNC である場合は、例外をスローします。
-
stopIndexing() — エクステントに対する非同期インデックス作成を停止します。Event インスタンスを閉じるときにインデックスを更新しないようにする場合は、Event.close() を呼び出す前にこのメソッドを呼び出します。
-
waitForIndexing() — int timeout 値を引数として取り、非同期インデックス作成が完了するまで待機します。timeout 値は、待機する秒数を指定します (-1 の場合は永久に待機し、0 の場合は直ちに返します)。インデックス作成が完了した場合は true を返し、インデックス作成が完了する前に待機がタイムアウトした場合は false を返します。インデックス・モードが Event.INDEX_MODE_SYNC である場合は、例外をスローします。
クエリの使用法
Event クラスは、ターゲット・クラスのエクステントに対して制限付き SQL クエリを実行できる EventQuery<> のインスタンスを作成する方法を提供します。EventQuery<> メソッドは、SQL クエリの実行、クエリ結果セット内の個別の項目の取得、更新、または削除を行うために使用します。
以下の項目について説明します。
-
クエリの作成と実行 — EventQuery<> クラスのメソッドを使用してクエリを実行する方法について説明します。
-
クエリ・データの処理 — EventQuery<> 結果セットの項目にアクセスし、それを変更する方法について説明します。
-
フェッチ・レベルの定義 — クエリによって返されるデータの量の制御方法について説明します。
このセクションの例では、EventPersister オブジェクトの myPersister が既に作成されて接続されており、SingleStringSample クラスのスキーマがインポートされていることを前提としています。この実行方法の例は、“永続イベントを格納および照会する単純なアプリケーション” を参照してください。
クエリの作成と実行
以下のメソッドは、EventQuery<> のインスタンスを作成および破棄します。
-
Event.createQuery() — SQL クエリのテキストを含む String 引数を取り、EventQuery<E> のインスタンスが返されます。パラメータ E は、親 Event のターゲット・クラスです。
-
EventQuery<>.close() — この EventQuery<> インスタンスを閉じ、それに関連するネイティブ・コード・リソースを解放します。
EventQuery<E> のインスタンスによって実行されるクエリは、指定した汎用タイプ E (クエリ・オブジェクトを作成した Event インスタンスのターゲット・クラス) の Java オブジェクトを返します。EventQuery<> クラスによってサポートされるクエリは、以下に示すとおり、SQL の SELECT 文のサブセットです。
-
クエリは、SELECT 節、FROM 節、および (オプションで) WHERE、ORDER BY などの標準 SQL 節から構成される必要があります。
-
SELECT 節および FROM 節は、構文的に正当ですが、現実的にはクエリ実行中には無視されます。マップされているすべてのフィールドは、常に、ターゲット・クラス E のエクステントからフェッチされます。
-
SQL 式は、任意の型の配列、組み込みオブジェクト、または組み込みオブジェクトのフィールドのいずれも参照できません。
-
Caché システム生成オブジェクト ID は、%ID と呼ばれることがあります。先頭の % のおかげで、これはフィールドが呼び出した id と Java クラス内で競合しません。
以下の EventQuery<> メソッドは、クエリを定義および実行します。
-
setParameter() — この EventQuery<> と関連付けられた SQL クエリのパラメータを結合します。int index および Object value を引数として取ります。ここで index は設定するパラメータを指定し、value は、指定されているパラメータに結合する値です。
-
execute() は、この EventQuery<> と関連付けられた SQL クエリを実行します。クエリが正常に実行された場合、この EventQuery<> には、後で説明するメソッドでアクセスできる結果セットが含まれます (“クエリ・データの処理” を参照してください)。
以下の例では、xep.samples.SingleStringSample エクステント内のイベントに対して単純なクエリを実行します (SingleStringSample クラスを定義および使用するサンプル・プログラムの詳細は、“XEP のサンプル・アプリケーション” を参照してください)。
Event newEvent = myPersister.getEvent("xep.samples.SingleStringSample");
String sql =
"SELECT * FROM xep_samples.SingleStringSample WHERE %ID BETWEEN ? AND ?";
EventQuery<SingleStringSample> myQuery = newEvent.createQuery(sql);
myQuery.setParameter(1,3); // assign value 3 to first SQL parameter
myQuery.setParameter(2,12); // assign value 12 to second SQL parameter
myQuery.execute(); // get resultset for IDs between 3 and 12
EventPersister.getEvent() メソッドは、ターゲット・クラスとして SingleStringSample を指定して newEvent という名前の Event インスタンスを作成します。
Event.createQuery() メソッドは、SQL クエリを実行してその結果セットを保持する myQuery という名前の EventQuery<> のインスタンスを作成します。sql 変数には、2 つのパラメータ値の間の ID を持つターゲット・クラス内のすべてのイベントを選択する SQL 文が含まれています。
EventQuery<>.setParameter() メソッドが 2 回呼び出されて、2 つのパラメータに値を割り当てます。
EventQuery<>.execute() メソッドが呼び出されると、ターゲット・クラスのエクステントに対して指定されたクエリが実行され、結果セットが myQuery に格納されます。
既定では、結果セットの各オブジェクトのすべてのデータがフェッチされ、各オブジェクトが完全に初期化されます。各オブジェクトでフェッチされるデータの量および型を制限するオプションについては、“フェッチ・レベルの定義” を参照してください。
クエリ・データの処理
クエリが実行された後、ここに記載されているメソッドを使用して、クエリ結果セット内の項目にアクセスし、データベース内の対応する永続イベントを更新または削除できます。EventQueryIterator<> クラスは、java.util.Iterator<E> を実装します (ここで E は親 EventQuery<E> インスタンスのターゲット・クラスです)。次の EventQuery<> メソッドは、EventQueryIterator<E> のインスタンスを作成します。
-
getIterator() — 現在の結果セットの EventQueryIterator<E> 反復子を返します。
EventQueryIterator<> は、java.util.Iterator<E> メソッドである hasNext()、next()、および remove() と以下のメソッドを実装します。
-
set() — ターゲット・クラスのオブジェクトを取り、それを使用して next() によって最後にフェッチされた永続イベントを更新します。
以下の例は、EventQueryIterator<> のインスタンスを作成し、それを使用して結果セットの各項目を更新します。
myQuery.execute(); // get resultset
EventQueryIterator<xep.samples.SingleStringSample> myIter = myQuery.getIterator();
while (myIter.hasNext()) {
currentEvent = myIter.next();
currentEvent.name = "in process: " + currentEvent.name;
myIter.set(currentEvent);
}
EventQuery<>.execute() の呼び出しによって、前の例で説明したクエリが実行されます (“クエリの作成と実行” を参照してください)。また、結果セットは myQuery に格納されます。結果セットの各項目は SingleStringSample オブジェクトです。
getIterator() を呼び出すと、現在 myQuery に格納されている結果セットの反復子 myIter が作成されます。
while ループにおいて、hasNext() は、結果セットのすべての項目が処理されるまで true を返します。
代替クエリ反復メソッド
EventQuery<> クラスも、EventQueryIterator<> を使用せずに結果セットを処理するのに使用できるメソッドを提供します(これは、ObjectScript で提供されるものと類似する反復メソッドを好む開発者向けの代替メソッドです)。クエリが実行された後、以下の EventQuery<> メソッドを使用して、クエリ結果セット内の項目にアクセスし、データベース内の対応する永続イベントを更新または削除できます。
-
getNext() — ターゲット・クラスの次のオブジェクトを結果セットから返します。結果セットにそれ以上項目がない場合は、null を返します。引数として null またはターゲット・クラスのオブジェクトが必要です (このリリースでは、引数はクエリに影響しないプレースホルダです)。
-
updateCurrent() — 引数としてターゲット・クラスのオブジェクトを取り、それを使用して getNext() によって最後に返された永続イベントを更新します。
-
deleteCurrent() — データベースから getNext() によって最後に返された永続イベントを削除します。
-
getAll() — getNext() を使用して結果セットからすべての項目を取得し、それらの項目を List で返します。更新または削除には使用できません。getAll() および getNext() は同じ結果セットにアクセスできません。一方のメソッドが呼び出されると、他方のメソッドは execute() が再び呼び出されるまで使用できません。
Id または IdKey によって特定された永続イベントへのアクセス方法、およびその変更方法の詳細は、“格納されたイベントへのアクセス” を参照してください。
クエリ結果には、EventQuery<> メソッドを直接呼び出すか、または EventQueryIterator<> のインスタンスを取得してそのメソッドを使用することによってアクセスできますが、これらのアクセス・メソッド両方を同時に使用しないでください。反復子を取得してそのメソッドを呼び出し、同時に EventQuery<> メソッドも直接呼び出すと、予期できない結果を招く可能性があります。
myQuery.execute(); // get resultset
SingleStringSample currentEvent = myQuery.getNext(null);
while (currentEvent != null) {
if (currentEvent.name.startsWith("finished")) {
myQuery.deleteCurrent(); // Delete if already processed
} else {
currentEvent.name = "in process: " + currentEvent.name;
myQuery.updateCurrent(currentEvent); // Update if unprocessed
}
currentEvent = myQuery.getNext(currentEvent);
}
myQuery.close();
この例では、EventQuery<>.execute() の呼び出しによって前の例で説明したクエリが実行されることが前提になっています (“クエリの作成と実行” を参照)。また、結果セットは myQuery に格納されます。結果セットの各項目は SingleStringSample オブジェクトです。
getNext() の最初の呼び出しによって、結果セットから最初の項目が取得され、それが currentEvent に割り当てられます。
while ループで、以下の処理が結果セットの各項目に適用されます。
-
currentEvent.name が文字列 "finished" で始まる場合、deleteCurrent() は対応する永続イベントをデータベースから削除します。
-
それ以外の場合、currentEvent.name の値が変更され、updateCurrent() が呼び出されます。これは currentEvent を引数として取り、これを使用してデータ・ベースの永続イベントを更新します。
-
getNext() の呼び出しによって、結果セットから次の SingleStringSample オブジェクトが返され、それが currentEvent に割り当てられます。
ループが終了した後、close() が呼び出され、myQuery に関連付けられたネイティブ・コード・リソースを解放します。
接続に関連するすべてのロック、ライセンス、およびその他のリソースを確実に解放するために、範囲外になる前に必ず EventPersister のインスタンスで close() を呼び出します。
フェッチ・レベルの定義
フェッチ・レベルは、クエリを実行するときに返されるデータの量を制御するために使用できる Event プロパティです。これは、基礎となるイベントが複雑で、かつイベント・データの小さいサブセットのみが必要な場合に特に便利です。
以下の EventQuery<> メソッドは、現在のフェッチ・レベルを設定し、返します。
-
getFetchLevel() — Event の現在のフェッチ・レベルを示す int を返します。
-
setFetchLevel() — 引数として Event フェッチ・レベル列挙の値の 1 つを取り、そのフェッチ・レベルを Event に対して設定します。
以下のフェッチ・レベル値がサポートされます。
-
Event.OPTION_FETCH_LEVEL_ALL — これは既定です。すべてのデータがフェッチされ、返されるイベントは完全に初期化されます。
-
Event.OPTION_FETCH_LEVEL_DATATYPES_ONLY — データ型フィールドのみがフェッチされます。これには、すべてのプリミティブ型、すべてのプリミティブ・ラッパ、java.lang.String、java.math.BigDecimal、java.util.Date、java.sql.Date、java.sql.Time、java.sql.Timestamp、および enum 型が含まれます。その他のフィールドはすべて null に設定されます。
-
Event.OPTION_FETCH_LEVEL_NO_ARRAY_TYPES — 配列を除くすべてのタイプがフェッチされます。ディメンジョンに関係なく、配列タイプのすべてのフィールドは null に設定されます。すべてのデータ型、オブジェクト・タイプ (シリアル化タイプを含む) およびコレクションがフェッチされます。
-
Event.OPTION_FETCH_LEVEL_NO_COLLECTIONS — java.util.List、java.util.Map、および java.util.Set の実装を除くすべてのタイプがフェッチされます。
-
Event.OPTION_FETCH_LEVEL_NO_OBJECT_TYPES — オブジェクト・タイプを除くすべてのタイプがフェッチされます。シリアル化タイプもオブジェクト・タイプとみなされ、フェッチされません。すべてのデータ型、配列タイプ、およびコレクションがフェッチされます。
XEP からの Caché メソッドの呼び出し
以下の EventPersister メソッドは、Caché クラス・メソッドを呼び出します。
-
callClassMethod() — 指定した ObjectScript クラス・メソッドを呼び出します。className および methodName に対して String 引数を取り、ほかにクラス・メソッドに渡される 0 個以上の引数を取ります。int、long、double、または String 型である Object を返します。
-
callBytesClassMethod() — 文字列値が byte[] のインスタンスとして返されること以外は callClassMethod() と同一です。
-
callListClassMethod() — 文字列値が ValueList のインスタンスとして返されること以外は callClassMethod() と同一です。
-
callVoidClassMethod() — 何も返されないこと以外は callClassMethod() と同一です。
以下の EventPersister メソッドは Caché 関数およびプロシージャを呼び出します ("Caché ObjectScript の使用法" の “ユーザ定義コード” を参照してください)。
-
callFunction() — 指定した ObjectScript 関数を呼び出します。functionName および routineName に対して String 引数を取り、ほかにこの関数に渡される 0 個以上の引数を取ります。int、long、double、または String 型である Object を返します。
-
callBytesFunction() — 文字列値が byte[] のインスタンスとして返されること以外は callFunction() と同一です。
-
callListFunction() — 文字列値が ValueList のインスタンスとして返されること以外は callFunction() と同一です。
-
callProcedure() — 指定した ObjectScript プロシージャを呼び出します。procedureName および routineName に対して String 引数を取り、ほかにこのプロシージャに渡される 0 個以上の引数を取ります。
スキーマのカスタマイズとマッピング
このセクションでは、Java クラスが Caché イベント・スキーマにどのようにマップされるか、また最適なパフォーマンスを得るためにスキーマをどのようにカスタマイズできるかについて詳細に説明します。多くの場合、メタ情報を提供することなく、単純なクラスのスキーマをインポートできます。しかし、状況によって、スキーマのインポート方法のカスタマイズが必要となったり要望されたりすることがあります。以下のセクションには、カスタマイズ・スキーマおよびその生成方法に関する情報が掲載されています。
-
スキーマ・インポート・モデル — XEP によってサポートされる 2 つのスキーマ・インポート・モデルについて説明します。
-
アノテーションの使用法 — XEP アノテーションを Java クラスに追加して、作成する必要があるインデックスを指定できます。また、アノテーションを追加して、インポートしないフィールドまたはシリアル化する必要があるフィールドを指定してパフォーマンスを最適化することもできます。
-
IdKey の使用法 — アノテーションを使用して、IdKey (既定のオブジェクト ID の代わりに使用するインデックス値) を指定できます。IdKey はフル・スキーマをインポートする場合に必要です。
-
InterfaceResolver の実装 — 既定では、フラット・スキーマは、インタフェースとして宣言されたフィールドをインポートしません。スキーマのインポート時に、InterfaceResolver インタフェースの実装を使用して、インタフェースとして宣言されたフィールドの実際のクラスを指定することができます。
-
スキーマ・マッピング・ルール — Java クラスが Caché イベント・スキーマにどのようにマップされるかについて詳しく説明します。
スキーマ・インポート・モデル
XEP は、フラット・スキーマとフル・スキーマの 2 つの異なるスキーマ・インポート・モデルを提供しています。これらのモデル間の主な相違は、Java オブジェクトが Caché イベントに投影される方法です。
-
埋め込みオブジェクト・プロジェクション・モデル (フラット・スキーマ) — フラット・スキーマ をインポートします。そのスキーマでは、インポートされたクラスによって参照されるオブジェクトがすべてシリアル化されて埋め込まれ、すべての祖先クラスで宣言されたフィールドはすべて、それらが、インポートされたクラス自体で宣言されているかのように収集され、投影されます。そのクラスのインスタンスのすべてのデータは、単一の Caché %Library.PersistentOpens in a new tab オブジェクトとして格納され、元の Java クラス構造に関する情報は保持されません。
-
フル・オブジェクト・プロジェクション・モデル (フル・スキーマ) — フル・スキーマをインポートします。そのスキーマでは、インポートされたクラスによって参照されるオブジェクトがすべて別の Caché %PersistentOpens in a new tab オブジェクトとして投影されます。継承されたフィールドは、同様に Caché %PersistentOpens in a new tab クラスにインポートされている祖先クラスのフィールドへの参照として投影されます。Java ソース・クラスと Caché の投影されたクラスの間には 1 対 1 の対応があるため、Java クラス継承構造が保持されます。
フル・オブジェクト・プロジェクションでは、元の Java クラスの継承構造が保持されますが、パフォーマンスに影響する場合があります。パフォーマンスが重要であり、イベント・スキーマが比較的単純である場合、フラット・オブジェクト・プロジェクションが最適な選択になります。パフォーマンスの低下を許容できる場合、より豊富な機能および複雑なスキーマのために、フル・オブジェクト・プロジェクションを使用できます。
埋め込みオブジェクト・プロジェクション・モデル (フラット・スキーマ)
既定では、XEP は、平坦化することで参照オブジェクトを投影するスキーマをインポートします。つまり、インポートされたクラスによって参照されるオブジェクトがすべてシリアル化されて埋め込まれ、すべての祖先クラスで宣言されたフィールドはすべて、それらが、インポートされたクラス自体で宣言されているかのように収集され、投影されます。対応する Caché イベントは、%Library.PersistentOpens in a new tab を拡張し、シリアル化されて埋め込まれたオブジェクト (元の Java オブジェクトに外部オブジェクトへの参照が含まれていた) を含んでいます。
つまり、フラット・スキーマでは、厳密な意味での継承は Caché 側に保持されません。例えば、以下の 3 つの Java クラスを考えてみます。
class A {
String a;
}
class B extends class A {
String b;
}
class C extends class B {
String c;
}
クラス C をインポートすると、以下の Caché クラスができます。
Class C Extends %Persistent ... {
Property a As %String;
Property b As %String;
Property c As %String;
}
対応する Caché イベントは、特別にインポートしない限り、A と B のいずれのクラスに対しても生成されません。Caché 側のイベント C は、A も B も拡張しません。インポートされると A および B は、以下の構造になります。
Class A Extends %Persistent ... {
Property a As %String;
}
Class B Extends %Persistent ... {
Property a As %String;
Property b As %String;
}
すべての処理は、対応する Caché イベントに対してのみ実行されます。例えば、タイプ C のオブジェクトに対して store() を呼び出すと、対応する C Caché イベントのみが格納されます。
Java の子クラスが、そのスーパークラスでも宣言される同じ名前のフィールドを非表示にすると、XEP エンジンは常にその子フィールドの値を使用します。
フル・オブジェクト・プロジェクション・モデル (フル・スキーマ)
フル・オブジェクト・モデルでは、一致する継承構造を Caché に作成することで、Java 継承モデルを保持するスキーマをインポートします。すべてのオブジェクト・フィールドをシリアル化してすべてのデータを単一の Caché オブジェクトに格納するのではなく、スキーマによって Java ソース・クラスと Caché の投影されたクラスとの間に 1 対 1 のリレーションシップが確立されます。フル・オブジェクト・プロジェクション・モデルでは、参照されるクラスがそれぞれ別々に格納され、指定されているクラスのフィールドが、対応する Caché クラスのオブジェクトへの参照として投影されます。
参照されるクラスには、ユーザ定義 IdKey (@Id または @Index — “IdKey の使用法” を参照) を作成するアノテーションが含まれている必要があります。オブジェクトが格納されるときは、参照されるすべてのオブジェクトが最初に格納され、結果の IdKey がその親オブジェクトに格納されます。残りの XEP と同様に、一意性チェックは実行されず、既存のデータの変更または上書きは試みられません。データは単に、可能な限り速い速度で追加されます。IdKey 値が、既に存在しているイベントを参照している場合、それは単にスキップされ、その既存のイベントの上書きが試みられることはありません。
@Embedded クラス・レベル・アノテーションを使用すると、アノテーションで指定されたクラスのインスタンスを別々に格納するのではなく、シリアル化されたオブジェクトとして埋め込むことで、フル・スキーマを最適化できます。
フル・オブジェクト・モデルの使用法の例は、FlightLog サンプル・プログラム (“XEP のサンプル・アプリケーション” にリストされている) を参照してください。
アノテーションの使用法
XEP エンジンは、Java クラスを調べることで、XEP イベント・メタデータを推測します。追加情報は、アノテーションを使用して Java クラスで指定できます。アノテーションは com.intersys.xep.annotations パッケージにあります。Java オブジェクトは、XEP 永続イベントの定義に準拠している限り (“インポートされるクラスの要件” を参照)、Caché イベントとして投影され、カスタマイズは必要ありません。
アノテーションには、投影されるクラス内の個別のフィールドに適用されるものと、クラス全体に適用されるものがあります。
-
フィールド・アノテーション — インポートされるクラスのフィールドに適用されます。
-
@Id — そのフィールドが IdKey として機能するようになることを指定します。
-
@Serialized — フィールドをシリアル化形式で格納および取得する必要があることを示します。
-
@Transient — フィールドをインポートから除外する必要があることを示します。
-
-
クラス・アノテーション — インポートされるクラス全体に適用されます。
@Id でマークされたフィールドの値は、標準のオブジェクト ID を置換する IdKey として使用されます (“IdKey の使用法” を参照してください)。このアノテーションは、クラスごとに 1 つのフィールドでのみ使用でき、そのフィールドは String、int、または long である必要があります (double は許容されますが推奨されません)。複合 IdKey を作成するには、代わりに @Index アノテーションを使用します。@Id でマークされたクラスは、@Index を使用して複合プライマリ・キーを宣言することもできません。両方のアノテーションが同じクラスに対して使用されている場合、例外がスローされます。
以下のパラメータを指定する必要があります。
-
generated — XEP がキー値を生成する必要があるかどうかを指定する boolean。
-
generated = true — (既定の設定) キー値は Caché によって生成され、@Id でマークされたフィールドは Long である必要があります。このフィールドは、挿入または保存の前は null であることが前提となっており、そのような操作の完了時に XEP によって自動的に入力されます。
-
generated=false — マークされたフィールドのユーザ割り当て値が IdKey 値として使用されます。フィールドは、String、int、Integer、long、または Long にできます。
-
次の例では、ssn フィールドのユーザ割り当て値がオブジェクト ID として使用されます。
import com.intersys.xep.annotations.Id;
public class Person {
@Id(generated=false)
Public String ssn;
public String name;
Public String dob;
}
@Serialized アノテーションは、該当のフィールドをシリアル化形式で格納および取得する必要があることを示します。
このアノテーションは、java.io.Serializable インタフェース (配列を含みます。これは暗黙的にシリアル化可能です) を実装するフィールドの格納を最適化します。XEP エンジンは、データの格納または取得のための既定のメカニズムを使用するのではなく、シリアル・オブジェクト用の関連する読み取りまたは書き込みメソッドを呼び出します。マークされたフィールドがシリアル化可能でない場合は、例外がスローされます。シリアル化フィールドの投影の詳細は、“データ型のマッピング” を参照してください。
例 :
import com.intersys.xep.annotations.Serialized;
public class MyClass {
@Serialized
public xep.samples.Serialized serialized;
@Serialized
public int[][][][] quadIntArray;
@Serialized
public String[][] doubleStringArray;
}
// xep.samples.Serialized:
public class Serialized implements java.io.Serializable {
public String name;
public int value;
}
@Transient アノテーションは、該当のフィールドをインポートから除外する必要があることを示します。アノテーションを付けられたフィールドは、Caché に投影されず、イベントが格納される、またはロードされるときに無視されます。
例 :
import com.intersys.xep.annotations.Transient;
public class MyClass {
// this field will NOT be projected:
@Transient
public String transientField;
// this field WILL be projected:
public String projectedField;
}
@Embedded アノテーションは、フル・スキーマが生成される場合に使用できます (“スキーマ・インポート・モデル” を参照してください)。Caché に投影するときに、このクラスのフィールドを、参照ではなく (フラット・スキーマと同様に) シリアル化して埋め込む必要があることを示します。
例 :
import com.intersys.xep.annotations.Embedded;
@Embedded
public class Address {
String street;
String city;
String zip;
String state;
}
import com.intersys.xep.annotations.Embedded;
public class MyOuterClass {
@Embedded
public static class MyInnerClass {
public String innerField;
}
}
@Index アノテーションを使用して、インデックスを宣言できます。
以下のパラメータに引数を指定する必要があります。
-
name — 複合インデックスの名前を含む String。
-
fields — 複合インデックスを構成するフィールドの名前を含む String の配列。
-
type — インデックス・タイプ。xep.annotations.IndexType 列挙には、以下の使用可能なタイプがあります。
-
IndexType.none — 既定値。インデックスがないことを示します。
-
IndexType.bitmap — ビットマップ・インデックス ("Caché SQL の使用法" の “ビットマップ・インデックス” を参照してください)。
-
IndexType.bitslice — ビットスライス・インデックス ("Caché SQL の使用法" の “概要” を参照してください)。
-
IndexType.simple — 1 つ以上のフィールドに対する標準インデックス。
-
IndexType.idkey — 標準 ID の代わりに使用されるインデックス (“IdKey の使用法” を参照してください)。
-
例 :
import com.intersys.xep.annotations.Index;
import com.intersys.xep.annotations.IndexType;
@Index(name="indexOne",fields={"ssn","dob"},type=IndexType.idkey)
public class Person {
public String name;
public Date dob;
public String ssn;
}
@Indices アノテーションを使用すると、1 つのクラスに対してさまざまなインデックスからなる 1 つの配列を指定できます。その配列の各要素は、@Index タグです。
例 :
import com.intersys.xep.annotations.Index;
import com.intersys.xep.annotations.IndexType;
import com.intersys.xep.annotations.Indices;
@Indices({
@Index(name="indexOne",fields={"myInt","myString"},type=IndexType.simple),
@Index(name="indexTwo",fields={"myShort","myByte","myInt"},type=IndexType.simple)
})
public class MyTwoIndices {
public int myInt;
public Byte myByte;
public short myShort;
public String myString;
}
IdKey の使用法
IdKey は、既定のオブジェクト ID の代わりに使用されるインデックス値です。単純 IdKey と複合 IdKey のどちらも XEP によってサポートされており、フル・スキーマでインポートされる Java クラスにはユーザ生成 IdKey が必要です (“スキーマのインポート” を参照してください)。単一フィールドに対する IdKey は @Id アノテーションで作成できます。複合 IdKey を作成するには、IndexType idkey と共に @Index アノテーションを追加します。例えば、以下のクラスを指定したとします。
class Person {
String name;
Integer id;
Date dob;
}
既定のストレージ構造では、添え字として標準オブジェクト ID を使用します。
^PersonD(1)=$LB("John",12,"1976-11-11")
以下のアノテーションは、name および id フィールドを使用して、標準オブジェクト ID を置き換える newIdKey という名前の複合 IdKey を作成します。
@Index(name="newIdKey",fields={"name","id"},type=IndexType.idkey)
これは、以下のグローバル構造になります。
^PersonD("John",12)=$LB("1976-11-11")
XEP では、SQL コマンドなど他の方法で追加された IdKey も処理されます ("Caché SQL の使用法" の “インデックスでの Unique、PrimaryKey、IDKey キーワードの使用” を参照してください)。XEP エンジンは、基本クラスに IdKey が含まれているかどうかを自動的に判別し、適切なグローバル構造を生成します。
IdKey の使用法にはいくつかの制限があります。
-
IdKey 値は一意である必要があります。IdKey がユーザ生成である場合、一意性は呼び出し元のアプリケーションで確保する必要があり、XEP によって強制されることはありません。アプリケーションで、データベースに既に存在しているキー値を持つイベントの追加が試みられると、その試みは暗黙的に無視され、既存のイベントは変更されません。
-
IdKey を宣言するクラスには、それが他のインデックスも宣言している場合、非同期にインデックスを付けることはできません。
-
複合 IdKey ではフィールドの数に制限はありませんが、フィールドが String、int、Integer、long、または Long であることが必要です。double も使用できますが、非推奨です。
-
非常に高い継続的な挿入レートを必要とするまれな特定の状況では、パフォーマンスが低下することがあります。
IdKey に基づくイベントの取得、更新、および削除を可能にする Event メソッドの説明は、“格納されたイベントへのアクセス” を参照してください。
IdKey および標準 Caché ストレージ・モデルの詳細は、"Caché グローバルの使用法" の “多次元ストレージの SQL およびオブジェクトの使用法” を参照してください。SQL での IdKey の詳細は、"Caché SQL の使用法" の “インデックスの定義と構築” を参照してください。
サンプル・プログラム IdKeyTest および FlightLog は、IdKey の使用例を示しています (サンプル・プログラムの詳細は、“XEP のサンプル・アプリケーション” を参照してください)。
InterfaceResolver の実装
フラット・スキーマがインポートされるとき、継承階層に関する情報は保持されません (“スキーマ・インポート・モデル” を参照してください)。これにより、インタフェースとして宣言されているタイプを持つフィールドを処理するときに問題が発生します。これは、XEP エンジンは、フィールドの実際のクラスを把握する必要があるためです。既定では、そのようなフィールドはフラット・スキーマにインポートされません。この動作は、com.intersys.xep.InterfaceResolver の実装を作成し、処理中の特定のインタフェース・タイプを解決することで、変更できます。
InterfaceResolver は、フラット・スキーマ・インポート・モデルにのみ関連しており、Java クラス継承構造は保持されません。フル・スキーマ・インポート・モデルでは、Java と Caché のクラスの間に 1 対 1 のリレーションシップが確立されるため、インタフェースの解決に必要な情報が保持されます。
InterfaceResolver の実装は、フラット・スキーマ・インポート・メソッド importSchema() を呼び出す前に EventPersister に渡されます (“スキーマのインポート” を参照してください)。これにより、XEP エンジンに、処理中にインタフェース・タイプを解決する方法が提供されます。以下の EventPersister メソッドは、使用される実装を指定します。
-
EventPersister.setInterfaceResolver() — 引数として InterfaceResolver のインスタンスを取ります。importSchema() が呼び出されると、指定されたインスタンスを使用して、インタフェースとして宣言されたフィールドを解決します。
以下の例は、クラスごとに InterfaceResolver の異なるカスタマイズされた実装を呼び出して、2 つの異なるクラスをインポートします。
try {
myPersister.setInterfaceResolver(new test.MyFirstInterfaceResolver());
myPersister.importSchema("test.MyMainClass");
myPersister.setInterfaceResolver(new test.MyOtherInterfaceResolver());
myPersister.importSchema("test.MyOtherClass");
}
catch (XEPException e) {System.out.println("Import failed:\n" + e);}
setInterfaceResolver() の最初の呼び出しによって、インポート・メソッドの呼び出し中に使用する実装として MyFirstInterfaceResolver (次の例で説明) の新しいインスタンスが設定されます。この実装は、setInterfaceResolver() が再び呼び出されて別の実装が指定されるまで、importSchema() のすべての呼び出しで使用されます。
importSchema() の最初の呼び出しで、クラス test.MyMainClass がインポートされます。これには、インタフェース test.MyFirstInterface として宣言されたフィールドが含まれています。MyFirstInterfaceResolver のインスタンスが、そのインポート・メソッドで使用され、このフィールドの実際のクラスが解決されます。
setInterfaceResolver() の 2 回目の呼び出しによって、importSchema() が再び呼び出されたときに使用する新しい実装として別の InterfaceResolver クラスのインスタンスが設定されます。
InterfaceResolver のすべての実装で、以下のメソッドを定義する必要があります。
-
InterfaceResolver.getImplementationClass() は、インタフェースとして宣言されたフィールドの実際のデータ型を返します。このメソッドには以下のパラメータがあります。
-
interfaceClass — 解決されるインタフェース。
-
declaringClass — interfaceClass として宣言されたフィールドが含まれているクラス。
-
fieldName — インタフェースとして宣言された declaringClass のフィールドの名前が含まれている文字列。
-
次の例は、インタフェース、そのインタフェースの実装、およびそのインタフェースのインスタンスを解決する InterfaceResolver の実装を定義します。
この例では、解決されるインタフェースは、test.MyFirstInterface です。
package test;
public interface MyFirstInterface{ }
test.MyFirstImpl クラスは、InterfaceResolver によって返される必要がある test.MyFirstInterface の実装です。
package test;
public class MyFirstImpl implements MyFirstInterface {
public MyFirstImpl() {};
public MyFirstImpl(String s) { fieldOne = s; };
public String fieldOne;
}
InterfaceResolver の以下の実装は、インタフェースが test.MyFirstInterface である場合はクラス test.MyFirstImpl を返し、それ以外の場合は null を返します。
package test;
import com.intersys.xep.*;
public class MyFirstInterfaceResolver implements InterfaceResolver {
public MyFirstInterfaceResolver() {}
public Class<?> getImplementationClass(Class declaringClass,
String fieldName, Class<?> interfaceClass) {
if (interfaceClass == xepdemo.MyFirstInterface.class) {
return xepdemo.MyFirstImpl.class;
}
return null;
}
}
MyFirstInterfaceResolver のインスタンスが setInterfaceResolver() によって指定されている場合、importSchema() の後続の呼び出しでは自動的にそのインスタンスが使用されて、test.MyFirstInterface として宣言されているすべてのフィールドが解決されます。そのようなフィールドごとに、パラメータ declaringClass をそのフィールドを含むクラスに、fieldName をそのフィールドの名前に、interfaceClass を test.MyFirstInterface に設定して、getImplementationClass() メソッドが呼び出されます。このメソッドは、インタフェースを解決し、test.MyFirstImpl か null のいずれかを返します。
スキーマ・マッピング・ルール
このセクションでは、XEP スキーマがどのように構築されるかについて説明します。以下の項目について説明します。
-
インポートされるクラスの要件 — 永続イベントとして投影できるオブジェクトを作成するために Java クラスが満たす必要がある構造ルールについて説明します。
-
名前付け規約 — Java クラスおよびフィールドの名前を Caché の名前付け規則に従うように変換する方法について説明します。
-
データ型のマッピング — 使用できる Java データ型をリストし、これらが対応する Caché のデータ型にどのようにマップされるかについて説明します。
インポートされるクラスの要件
XEP スキーマ・インポート・メソッドは、以下の要件を満たさない限り、Java クラスに対して有効なスキーマを作成できません。
-
インポートされる Caché クラスまたは派生クラスが、格納されたイベントにクエリを実行してそれにアクセスするために使用される場合、Java ソース・クラスは引数のないパブリック・コンストラクタを明示的に宣言する必要があります。
-
Java ソース・クラスは、java.lang.Object として宣言されたフィールド、または java.lang.Object を宣言の一部として使用する配列、リスト、セットまたはマップを含むことはできません。XEP エンジンがこのようなフィールドを検出すると、例外がスローされます。@Transient アノテーション (“アノテーションの使用法” を参照してください) を使用して、これらがインポートされないようにします。
Event.isEvent() メソッドを使用して、Java クラスまたはオブジェクトを分析し、それが XEP の意味で有効なイベントを作成できるかどうかを判定できます。前述の条件に加えて、以下の条件が検出された場合、このメソッドは XEPException をスローします。
-
循環した依存関係
-
決まった形式のない List または Map
-
String、プリミティブ、プリミティブ・ラッパのいずれでもない Map キー値
永続イベントのフィールドは、プリミティブで、そのラッパ、一時データ型、オブジェクト (組み込み/連続オブジェクトとして投影)、列挙、および java.util.List, java.util.Set、および java.util.Map から派生したデータ型にできます。これらのデータ型を配列、入れ子になったコレクション、および配列のコレクションに含めることもできます。
既定では、投影されるフィールドは、Java クラスの一部の機能を保持できません。特定のフィールドが以下の方法で変更されます。
-
Java クラスに静的フィールドが含まれていても、これらは既定でプロジェクションから除外されます。対応する Caché プロパティはありません。追加のフィールドは、@Transient アノテーションを使用して除外できます (“アノテーションの使用法” を参照)。
-
フラット・スキーマ (“スキーマ・インポート・モデル” を参照) では、内部 (入れ子にされた) Java クラスを含むすべてのオブジェクト・タイプは、%SerialObjectOpens in a new tab クラスとして Caché に投影されます。オブジェクト内のフィールドは、別の Caché プロパティとして投影されません。このオブジェクトは、ObjectScript の観点からは不明瞭です。
-
フラット・スキーマは、すべての継承されたフィールドを、それらが子クラスで宣言されたかのように投影します。
さまざまなデータ型を投影する方法の詳細は、“データ型のマッピング” を参照してください。
名前付け規約
対応する Caché クラスおよびプロパティの名前は Java での名前と同じです。ただし、Java では許可されているが Caché では許可されていない以下の 2 つの特別な文字は例外です。
-
$ (ドル記号) は、Caché 側では "d" の 1 文字に投影されます。
-
_ (アンダースコア) は、Caché 側では "u" の 1 文字に投影されます。
クラス名は 255 文字に制限されます。この文字数はほとんどのアプリケーションで十分のはずです。ただし、対応するグローバル名には 31 文字の制限があります。これは通常 1 対 1 マッピングには不十分なため、XEP エンジンは、31 文字よりも長いクラス名用に透過的に一意のグローバル名を生成します。生成されるグローバル名は元の名前と同じではありませんが、それでも簡単に認識できるはずです。例えば、xep.samples.SingleStringSample クラスは、グローバル名 xep.samples.SingleStrinA5BFD を受け取ります。
データ型のマッピング
永続イベントのフィールドは、以下のデータ型のいずれかにできます。
-
プリミティブ・タイプ、プリミティブ・ラッパおよび java.lang.String
-
一時データ型 (java.sql.Time、java.sql.Date、java.sql.Timestamp、および java.util.Date)
-
オブジェクト・タイプ (フラット・スキーマで埋め込み/シリアル・オブジェクトとして投影される)
-
Java enum データ型
-
java.util.List、java.util.Set および java.util.Map から派生したデータ型
-
入れ子になったコレクション (例えばマップのリスト) および配列のコレクション
-
上記のいずれかの配列
以下のセクションでは、現在サポートされている Java タイプおよび対応する Caché タイプをリストします。
以下の Java プリミティブおよびラッパは Caché %StringOpens in a new tab としてマップされます。
-
char、java.lang.Character、java.lang.String
以下の Java プリミティブおよびラッパは Caché %IntegerOpens in a new tab としてマップされます。
-
boolean、java.lang.Boolean
-
byte、java.lang.Byte
-
int、java.lang.Integer
-
long、java.lang.Long
-
short、java.lang.Short
以下の Java プリミティブおよびラッパは Caché %FloatOpens in a new tab としてマップされます。
-
double、java.lang.Double
-
float、java.lang.Float
以下の Java 一時データ型は Caché %StringOpens in a new tab としてマップされます。
-
java.sql.Date
-
java.sql.Time
-
java.sql.Timestamp
-
java.util.Date
インポートされる Java クラス (importSchema() または importFullSchema() の呼び出しで指定されるターゲット・クラス) は、Caché %PersistentOpens in a new tab クラスとして投影されます。必須情報も、スーパークラスおよび依存クラスからインポートされますが、スキーマ・インポート・モデル (“スキーマ・インポート・モデル” を参照) によってこの情報が Caché にどのように格納されるのかが決定されます。
-
フラット・スキーマでは、インポートされたクラスでフィールド・タイプとして現れたクラスは、%SerialObjectOpens in a new tab Caché クラスとして投影され、親 %PersistentOpens in a new tab クラスに埋め込まれます。インポートされたクラスのスーパークラスは投影されません。代わりに、スーパークラスから継承されたすべてのフィールドが、インポートされたクラスのネイティブ・フィールドであるかのように投影されます。
-
フル・スキーマでは、スーパークラスおよび依存クラスは、独立した %PersistentOpens in a new tab Caché クラスとして投影され、インポートされるクラスには、それらのクラスへの参照が含まれるようになります。
java.lang.Object クラスはサポートされていないクラスです。XEP エンジンが java.lang.Object として宣言されたフィールド、またはこれを使用する配列、リスト、セット、マップを検出すると例外がスローされます。
@Serialized アノテーション (“アノテーションの使用法” を参照してください) のマークが付けられたフィールドはすべて %BinaryOpens in a new tab としてシリアル化形式で投影されます。
以下の規則が配列に適用されます。
-
バイト配列および文字配列を除いて、プリミティブ型、プリミティブ・ラッパ型、および一時データ型のすべての 1 次元配列は、基本ベース型のリストとしてマップされます。
-
1 次元バイト配列 (byte[] および java.lang.Byte[]) は %BinaryOpens in a new tab としてマップされます。
-
1 次元文字配列 (char[] および java.lang.Character[]) は %StringOpens in a new tab としてマップされます。
-
オブジェクトの 1 次元配列はオブジェクトのリストとしてマップされます。
-
すべての多次元配列は、%BinaryOpens in a new tab としてマップされ、ObjectScript の観点からは不明瞭です。
-
配列は暗黙的にシリアル化可能であり、@Serialized のアノテーションを付けることができます。
Java enum 型は、Caché %StringOpens in a new tab として投影され、名前のみが格納されます。Caché から取得されると、Java enum オブジェクト全体が再構成されます。列挙の配列、リスト、およびその他のコレクションもサポートされます。
java.util.List および java.util.Set から派生したクラスは、Caché リストとして投影されます。java.util.Map から派生したクラスは、Caché 配列として投影されます。決まった形式のない Java リスト、セットおよびマップはサポートされません (タイプ・パラメータを使用する必要があります)。入れ子になったリスト、セットおよびマップ、配列のリスト、セットおよびマップ、ならびにリスト、セットまたはマップの配列はすべて、%BinaryOpens in a new tab として投影され、Caché に関しては不明瞭と見なされます。