永続クラスの定義
永続クラスは、永続オブジェクトを定義するクラスです。この章では、このようなクラスの作成方法について説明します。以下のトピックについて説明します。
“永続オブジェクトの概要” の章、“永続オブジェクトを使用した作業” の章、および “永続クラスのその他のオプション” の章も参照してください。
このドキュメントをオンラインで表示している場合は、このドキュメントの "序文" を使用すると、他のトピックをすばやく見つけることができます。
永続クラスの定義
永続オブジェクトを定義するクラスを定義するには、そのクラスの プライマリ (最初の) スーパークラスを %PersistentOpens in a new tab にするかその他の永続クラスであることを確認してください。
例 :
Class MyApp.MyClass Extends %Persistent
{
}
パッケージからスキーマへのプロジェクション
永続クラスの場合は、パッケージが SQL スキーマとして SQL で表現されます。例えば、クラスが Team.Player (“Team” パッケージの Player クラス) である場合、対応するテーブルは “Team.Player” (“Team” スキーマの Player テーブル) です。
既定のパッケージは “User” です。これが “SQLUser” スキーマとして SQL で表現されます。したがって、User.PersonOpens in a new tab という名前のクラスは、SQLUser.Person という名前のテーブルに対応します。
パッケージ名にピリオドが含まれる場合、ピリオドの代わりにアンダースコア (_) を使用します。例えば、MyTest.Test.MyClass クラス (“MyTest.Test” パッケージの MyClass クラス) は、MyTest_Test.MyClass テーブル (“MyTest_Test” スキーマの MyClass テーブル) になります。
SQL テーブル名がスキーマ名なしで参照される場合、既定のスキーマ名 (SQLUser) が使用されます。例えば、以下のコマンドがあります。
Select ID, Name from Person
上記は、以下と同じです。
Select ID, Name from SQLUser.Person
永続クラスのテーブル名の指定
永続クラスの場合は、短いクラス名がテーブル名になります (既定)。
別のテーブル名を指定する場合は、SqlTableName クラス・キーワードを使用します。以下に例を示します。
Class App.Products Extends %Persistent [ SqlTableName = NewTableName ]
Caché には、クラス名に対する制限はありませんが、SQL 予約語を SQL テーブルの名前に使用することはできません。そのため、予約語の名前を付けた永続クラスを作成すると、クラス・コンパイラによりエラー・メッセージが生成されます。この場合は、ここで説明する方法を使用して、クラス名を変更するか、クラス名とは異なるテーブル名をプロジェクションに指定する必要があります。
ストレージ定義とストレージ・クラス
%PersistentOpens in a new tab クラスは、データベース内のオブジェクト保存と検索用の、高レベルのインタフェースを提供します。オブジェクトの保存とロードの実際の作業は、ストレージ・クラスによって実行されます。
すべての永続オブジェクトおよびすべてのシリアル・オブジェクトはストレージ・クラスを使用して、データベースのオブジェクトの保存、ロード、および削除に使用される実際のメソッドを生成します。これらの内部メソッドは、ストレージ・インタフェースと呼ばれます。ストレージ・インタフェースには、%LoadData()、%SaveData()、および %DeleteData() などのメソッドが含まれます。これらのメソッドはアプリケーションから直接呼ばれることはなく、永続インタフェース (%OpenId() や %Save() など) のメソッドによって適宜呼び出されます。
永続クラスによって使用されるストレージ・クラスは、ストレージ定義によって指定されます。ストレージ定義には、ストレージ・インタフェースによって使用される追加のパラメータや、ストレージ・クラスを定義する、一連のキーワードや値が含まれます。
永続クラスには、1 つ以上のストレージ定義を含みますが、一度に 1 つしかアクティブになりません。アクティブなストレージ定義は、クラスの StorageStrategy キーワードを使用して指定されます。既定では、永続クラスは “Default” と呼ばれるストレージ定義を 1 つ持っています。
ストレージ定義については、"Caché プログラミング入門ガイド" の “永続オブジェクトと Caché SQL” の章にある “ストレージ” を参照してください。特に、クラスのデータを格納するグローバルの名前の詳細は、そのセクションを参照してください。
ストレージ定義の更新
クラスのストレージ定義は、クラスを初めてコンパイルしたときに作成されます。SQL や MultiValue などのためクラス・プロジェクションは、コンパイル後に実行されます。クラスが正しくコンパイルされ、その後、プロジェクションに失敗した場合、Caché は、ストレージ定義を削除しません。また、クラスの変更がストレージ定義に影響を及ぼす可能性がある場合、アプリケーション開発者の責任により、ストレージ定義が更新されているか判断し、必要に応じて、ストレージ定義を変更して変更を反映します。“ストレージ定義の再設定” を参照してください。
%CacheStorage ストレージ・クラス
%CacheStorageOpens in a new tab は、永続オブジェクトから使用される既定のストレージ・クラスです。これは、永続クラスに対する既定のストレージ構造を自動的に作成し、保持します。
新しい永続クラスは、自動的に %CacheStorageOpens in a new tab ストレージ・クラスを使用します。%CacheStorageOpens in a new tab クラスでは、ストレージ定義のさまざまなキーワードを使用して、クラスに使用されるストレージ構造の特定の機能を制御できます。
さまざまなストレージ・キーワードに関する詳細は、"クラス定義リファレンス" を参照してください。
MANAGEDEXTENT クラス・パラメータの詳細は、前の章の “エクステントの管理” も参照してください。
%CacheSQLStorage ストレージ・クラス
%CacheSQLStorageOpens in a new tab クラスは、生成された SELECT、INSERT、UPDATE、および DELETE の各 SQL 文を使用してオブジェクトの永続性を提供する特別なストレージ・クラスです。
%CacheSQLStorageOpens in a new tab は一般的に、以下の用途で使用されます。
-
従来のアプリケーションによって使用された、以前に存在したグローバル構造へのオブジェクトのマッピング
-
SQL ゲートウェイを使用した、外部リレーショナル・データベース内のオブジェクトの保存
%CacheSQLStorageOpens in a new tab は、%CacheStorageOpens in a new tab よりも制限されています。特に、複数のクラスのエクステントでのスキーマ進化の自動的なサポートを行いません。
スキーマ進化
%CacheStorageOpens in a new tab ストレージ・クラスは、自動的なスキーマ進化をサポートします。
既定の %CacheStorageOpens in a new tab ストレージ・クラスを使用する永続クラス (シリアル・クラス) をコンパイルするとき、クラス・コンパイラはクラスによって定義されたプロパティを解析し、自動的にそのプロパティを追加または削除します。
実際のスキーマ進化を行う方法は、以下のとおりです。
-
スタジオを起動して、1 つまたは複数のプロパティを持つ新規の永続クラスを生成します。
-
クラスをコンパイルし、全体としてのクラス定義内で、そのクラスに対して自動的に生成されたストレージ定義 (XML テキスト) を表示します。また、クラス・インスペクタを使用して、ストレージをグラフィックで表示することもできます。インスペクタで [Storage] をクリックし、ストレージ定義のリストから [Default] を選択します。次にキーワード・リストから [Data Nodes] を選択して、参照ボタン ([...]) をクリックします。グラフィカル・ストレージ・エディタが表示されます。
クラスに対して生成されたストレージでは、擬似プロパティ %%CLASSNAME が表示されます。これは、クラスのプレースホルダです。これは、クラスから将来的に派生するサブクラスのクラス名で、データベースに保存されているオブジェクトのタイプを区別するために使用されます。エクステントのルート・クラスでは、この値は常に空です。
-
1 つ、または複数の新規のプロパティをクラスに追加し、クラスを再コンパイルします。これらの新規プロパティは、以前に存在していたストレージと互換性がある方法で、自動的にストレージ定義に追加されていることに注意してください。
ストレージ定義の再設定
開発プロセス中は、永続クラスにプロパティの追加、変更、および削除などの変更をいくらでも加えることができます。クラス・コンパイラは互換性構造を維持しようと試みるため、ストレージ定義はより複雑になっていきます。すっきりしたストレージ構造がクラス・コンパイラで生成されるようにするには、ストレージ定義を削除して、そのクラスをリコンパイルします。
以下の操作を行います。
-
スタジオでクラスを開きます。
-
クラス・インスペクタの [Storage] から [Default] を選択し、右クリックします。
-
ポップアップ・メニューで [削除] コマンドを実行します。
-
クラスをコンパイルします。これで、クラス・コンパイラは、このクラスに対する新規のストレージ定義を生成します。
ID の生成を制御する方法
オブジェクトを初めて保存するときに、そのオブジェクトの ID はシステムによって生成されます。ID は永続的になります。
既定では、Caché は ID に整数を使用します。この整数は、最後に保存したオブジェクトから 1 だけ増分されます。
特定の永続クラスを定義する際に、以下のいずれかの方法で ID が生成されるように定義できます。
-
ID は、クラスの特定のプロパティを基にすることができます (そのプロパティがインスタンスごとに一意である場合)。例えば、ドラッグ・コードを ID として使用することも可能です。この方法でクラスを定義するには、以下のようなインデックスをクラスに追加します。
Index IndexName On PropertyName [ IdKey ];
または (同様に)
Index IndexName On PropertyName [ IdKey, Unique ];
IndexName はインデックスの名前、PropertyName はプロパティの名前です。
この方法でクラスを定義すると、Caché は最初にオブジェクトを保存するときに、そのプロパティの値を ID として使用するようになります。さらに、Caché はプロパティの値を要求して、そのプロパティの一意性を強制します。指定されたプロパティに同じ値を持つ別のオブジェクトを作成して、新しいオブジェクトを保存しようとすると、Caché は以下のエラーを発行します。
ERROR #5805: ID key not unique for extent
さらに、Cache により、そのプロパティは将来にわたって変更できなくなります。つまり、保存したオブジェクトを開き、そのプロパティ値を変更し、変更したオブジェクトを保存しようとすると、Caché は以下のエラーを発行するようになります。
ERROR #5814: Oid previously assigned
このメッセージは、ID ではなく OID について言及しています。これは、基礎となるロジックが OID の変更を防止しているためです。OID は ID を基にしています。
-
ID は複数のプロパティに基づいたものにできます。この方法でクラスを定義するには、以下のようなインデックスをクラスに追加します。
Index IndexName On (PropertyName1,PropertyName2,...) [ IdKey, Unique ];
または (同様に)
Index IndexName On (PropertyName1,PropertyName2,...) [ IdKey ];
IndexName はインデックスの名前、PropertyName1、PropertyName2 などはプロパティの名前です。
この方法でクラスを定義すると、Caché は最初にオブジェクトを保存するときに、以下のように ID を生成するようになります。
PropertyName1||PropertyName2||...
さらに、Caché はプロパティの値を要求して、プロパティの指定の組合せの一意性を強制します。さらに、それらのプロパティはいずれも将来にわたって変更できなくなります。
リテラル・プロパティ (つまり、属性) に、連続する 1 対の垂直バー (||) が含まれている場合、そのプロパティを使用する IdKey インデックスは追加しないでください。この制限は、Caché SQL のメカニズムが動作する方法によるものです。IdKey プロパティ で || を使用すると、予測できない動作を起こす場合があります。
システムは OID も生成します。いずれの場合でも、OID は以下の形式になります。
$LISTBUILD(ID,Classname)
ID は生成された ID です。また、Classname はクラスの名前です。
サブクラスの SQL プロジェクションの制御
スーパークラス/サブクラスの階層内に複数の永続クラスが存在する場合、Caché は、それらのデータを 2 通りの方法で保存します。既定のシナリオは、極めて一般的です。
サブクラスの既定の SQL プロジェクション
クラス・コンパイラは、永続クラスを “平坦化” した表現を投影します。投影されたテーブルには、そのクラスの該当するフィールドのすべて (継承されたフィールドを含む) が含まれるようになります。したがって、サブクラスの SQL プロジェクションは、以下の項目で構成されたテーブルになります。
-
スーパークラスのエクステントに含まれるすべての列
-
サブクラスだけにあるプロパティに基づいたその他の列
-
サブクラスの保存済みインスタンスを表す行
さらに、既定のシナリオでは、スーパークラスのエクステントには、スーパークラスおよびそのスーパークラスのすべてのサブクララスの保存済みオブジェクトごとに 1 つのレコードが格納されます。各サブクラスのエクステントは、スーパークラスのエクステントのサブセットです。
例えば、SAMPLES 内には、永続クラスの Sample.PersonOpens in a new tab と Sample.EmployeeOpens in a new tab があるとします。Sample.EmployeeOpens in a new tab クラスは Sample.PersonOpens in a new tab を継承し、いくつかのプロパティが追加されます。SAMPLES では、どちらのクラスもデータを保存しています。
-
Sample.PersonOpens in a new tab の SQL プロジェクションは、Sample.PersonOpens in a new tab クラスの適切なプロパティをすべて含んでいるテーブルになります。Sample.PersonOpens in a new tab テーブルには、Sample.PersonOpens in a new tab クラスの保存済みインスタンスごとに 1 つのレコード、および Sample.EmployeeOpens in a new tab クラスの保存済みインスタンスごとに 1 つのレコードが格納されています。
-
Sample.EmployeeOpens in a new tab テーブルには、Sample.PersonOpens in a new tab と同じ列が含まれています。さらに、Sample.EmployeeOpens in a new tab クラスに固有の列も含まれています。Sample.EmployeeOpens in a new tab テーブルには、Sample.EmployeeOpens in a new tab クラスの保存済みインスタンスごとに 1 つのレコードが格納されています。
これを表示するには、以下の SQL クエリを使用します。まず、Sample.PersonOpens in a new tab のすべてのインスタンスをリストして、それらのプロパティを表示します。
SELECT * FROM Sample.Person
2 番目のクエリでは、Sample.EmployeeOpens in a new tab のすべてのインスタンスと、それらのプロパティをリストします。
SELECT * FROM Sample.Employee
Sample.PersonOpens in a new tab テーブルには、1 から 200 の範囲の ID を持つレコードが含まれることに注意してください。101 から 200 までの範囲に含まれる ID を持つレコードは従業員であり、Sample.EmployeeOpens in a new tab テーブルは同じ従業員 (同じ ID と同じ追加列を持つ従業員) を示します。Sample.PersonOpens in a new tab テーブルには、2 つの見かけ上 “グループ” のみが配置されています。これは、SAMPLES データベースが人為的な方法で構築されているためです。Sample.PersonOpens in a new tab テーブルが移入された後で、Sample.EmployeeOpens in a new tab テーブルが移入されます。
一般的に、サブクラスのテーブルには親クラスよりも多数の列と、少数の行が含まれます。サブクラスが親クラスを拡張するとき、サブクラスは通常、プロパティを追加するので、サブクラス内にはより多くの列があります。サブクラスには親よりもサブクラスのインスタンスが少ないので、多くの場合行数も少なくなります。
サブクラスの代替の SQL プロジェクション
既定のプロジェクションは最も便利なものですが、場合によっては、代替の SQL プロジェクションを使用することが必要になります。このシナリオでは、各クラスに独自のエクステントがあります。この形式のプロジェクションを行うには、スーパークラスの定義に以下を含めます。
[ NoExtent ]
例 :
Class MyApp.MyNoExtentClass [ NoExtent ]
{
//class implementation
}
このクラスの各サブクラスは、独自のエクステントを受け取ります。
この方法でクラスを作成し、その他のクラスのプロパティとして使用する場合は、“オブジェクト値プロパティの定義と使用” の章の “バリエーション : CLASSNAME パラメータ” を参照してください。
データを格納した永続クラスの再定義
開発プロセス中にクラスを再定義することはよくあることです。既にクラスのサンプル・データを作成している場合は、以下の点に注意してください。
-
コンパイラはクラスのデータを格納するグローバルに影響しません。
実際には、クラス定義を削除してもクラス定義のデータ・グローバルはそのまま残されます。これらのグローバルが不要になった場合は、手動で削除してください。
-
クラスのプロパティを追加または削除して、クラスのストレージ定義は変更しない場合、そのクラスのデータにアクセスするコードはすべて、これまでと同様に機能します。この章で前述した “スキーマ進化” を参照してください。
-
クラスのストレージ定義を変更する場合、データにアクセスするコードがこれまでと同様に機能するかしないかは、変更の種類によって異なります。
-
プロパティ検証がより制限的になるようにプロパティ定義を変更した場合、検証に合格しなくなったオブジェクト (またはレコード) で作業するとエラーが発生します。例えば、プロパティの MAXLEN パラメータを小さくすると、長すぎることになったそのプロパティの値を持っているオブジェクトで作業したときに、検証エラーが発生します。