永続オブジェクトの概要
この章では、永続クラスを使用した作業時の理解に役立つ概念について説明します。以下のトピックについて説明します。
“永続オブジェクトを使用した作業” の章、“永続クラスの定義” の章、および “永続クラスのその他のオプション” の章も参照してください。
このドキュメントをオンラインで表示している場合は、このドキュメントの "序文" を使用すると、他のトピックをすばやく見つけることができます。
永続クラス
永続クラスとは、%PersistentOpens in a new tab を継承するあらゆるクラスのことです。永続オブジェクトとは、そのようなクラスのインスタンスのことです。
%PersistentOpens in a new tab クラスは、%RegisteredObjectOpens in a new tab のサブクラスであるため、オブジェクト・クラスになります。前の章で説明したメソッドを提供することに加え、%PersistentOpens in a new tab クラスでは、永続インタフェースも定義します。これは、メソッドのセットです。これらのメソッドにより、データベースにオブジェクトを保存すること、データベースからオブジェクトをロードすること、オブジェクトを削除すること、および存在をテストすることができるようになります。
既定の SQL プロジェクションの概要
どのような永続クラスについても、コンパイラが SQL テーブル定義を生成します。この定義により、このドキュメントで説明するオブジェクト・インタフェースに加えて、保存されたデータに SQL でアクセスできます。
このテーブルには、保存されたオブジェクトごとに 1 つのレコードが格納されています。また、このテーブルは Caché SQL でクエリできます。以下に、Sample.PersonOpens in a new tab テーブルのクエリ結果を示します。
以下のテーブルに、既定のプロジェクションをまとめます。
投影元 (オブジェクト・コンセプト) | 投影先 (リレーショナル・コンセプト) |
---|---|
パッケージ | スキーマ |
クラス | テーブル |
OID | ID フィールド |
データ型プロパティ | フィールド |
参照プロパティ | 参照フィールド |
埋め込みオブジェクト | 一連のフィールド |
リスト・プロパティ | リスト・フィールド |
配列プロパティ | 子テーブル |
ストリーム・プロパティ | BLOB |
インデックス | インデックス |
クラス・メソッド | ストアド・プロシージャ |
後続の章では、詳細情報を示し、変更可能な内容について説明します。
-
テーブル名と、そのテーブルが属するスキーマの名前の詳細は、“永続クラスの定義” を参照してください。
その章では、サブクラスのプロジェクションを制御する方法についても説明しています。
-
リテラル・プロパティのプロジェクションについての詳細は、“リテラル・プロパティの定義と使用” を参照してください。
-
コレクション・プロパティのプロジェクションについての詳細は、“コレクションを使用した作業” を参照してください。
-
ストリーム・プロパティのプロジェクションについての詳細は、“ストリームを使用した作業” を参照してください。
-
オブジェクト値プロパティのプロジェクションについての詳細は、“オブジェクト値プロパティの定義と使用” を参照してください。
-
リレーションシップのプロジェクションについての詳細は、“リレーションシップの定義と使用” を参照してください。
保存したオブジェクトの識別子 : ID および OID
初めてオブジェクトを保存するとき、システムは、2 つの永続識別子を作成します。どちらの識別子も保存したオブジェクトに後でアクセスする場合や、保存したオブジェクトを削除する場合に使用できます。最も広範に使用される識別子は、オブジェクト ID です。ID は、テーブル内で一意である単純なリテラル値です。既定では、システムは ID として使用する整数を生成します。
OID は、より一般的です。OID にはクラス名も含まれていて、データベース内で一意です。実際には、アプリケーションは OID 値を使用する必要はありません。ID 値で十分に機能します。
%PersistentOpens in a new tab クラスには、ID または OID を使用するメソッドがあります。ID は、%OpenId()、%ExistsId()、%DeleteId() などのメソッドを使用するときに指定します。OID は、%Open()、%Exists()、%Delete() などのメソッドに引数として指定します。つまり、引数として ID を使用するメソッドは、そのメソッドの名前に Id が含まれているということです。引数として OID を使用するメソッドは、そのメソッドの名前に Id が含まれていません。このようなメソッドは、ほとんど使用されません。
永続オブジェクトがそのデータベースに保存されると、その参照属性 (つまり、他の永続オブジェクトへの参照) の値はすべて OID 値として保存されます。OID を持たないオブジェクト属性については、オブジェクトのリテラル値が、その他のオブジェクト状態と共に保存されます。
オブジェクト ID から SQL へのプロジェクション
オブジェクトの ID は、対応する SQL テーブルで使用できます。可能な場合、Caché は ID というフィールド名を使用します。また、Caché には、どのフィールド名を使用しているかわからない場合でも、ID にアクセスする方法があります。このシステムは、以下のとおりです。
-
オブジェクト ID は、オブジェクトのプロパティではないため、プロパティとは異なる扱いを受けます。
-
ID という名前のプロパティ (どのような大文字小文字の組合せであっても) がクラスに含まれていない場合、テーブルにはフィールド ID も含まれるようになり、そのフィールドにオブジェクト ID が格納されます。この例については、前のセクションを参照してください。
-
ID という名前 (どのような大文字小文字の組合せであっても) を SQL に投影したプロパティがクラスに含まれている場合、テーブルにはフィールド ID1 も含まれるようになり、このフィールドにオブジェクト ID の値が保持されます。
同様に、ID および ID1 として投影されたプロパティがクラスに含まれている場合、テーブルにはフィールド ID2 も含まれるようになり、このフィールドにオブジェクト ID の値が保持されます。
-
すべての場合で、テーブルに疑似フィールド %ID も用意され、オブジェクト ID の値が保持されます。
OID は SQL テーブルでは使用できません。
SQL でのオブジェクト ID
Caché は、ID フィールドに一意性を強制します (実際の名前は、どのようなものであってもかまいません)。また、Caché は、このフィールドの変更を禁止します。つまり、このフィールドに対する SQL UPDATE または INSERT 操作は実行できないということです。例として、新しいレコードをテーブルに追加するために必要な SQL を以下に示します。
INSERT INTO PERSON (FNAME, LNAME)VALUES (:fname, :lname)
この SQL は ID フィールドを参照しません。Caché は、ID フィールドの値を生成し、要求されたレコードの作成時にその値を挿入します。
永続クラスに固有のクラス・メンバ
Caché クラスには、永続クラスでのみ意味のある数種のクラス・メンバを含めることができます。これに該当するものは、ストレージ定義、インデックス、外部キー、およびトリガです。
ストレージ定義
ほとんどの場合 (後述するように)、永続クラスごとにストレージ定義が存在します。ストレージ定義の用途は、グローバル構造を記述することです。Caché では、クラスのデータを保存するときや、クラスの保存データを読み込むときに、この構造を使用します。スタジオでは、クラス定義の最後の部分にストレージ定義が表示されます (編集モードでクラスを表示している場合)。以下は、部分的な例です。
<Storage name="Default">
<Data name="PersonDefaultData">
<Value name="1">
<Value>%%CLASSNAME</Value>
</Value>
<Value name="2">
<Value>Name</Value>
</Value>
<Value name="3">
<Value>SSN</Value>
</Value>
<Value name="4">
<Value>DOB</Value>
</Value>
<Value name="5">
<Value>Home</Value>
</Value>
<Value name="6">
<Value>Office</Value>
</Value>
<Value name="7">
<Value>Spouse</Value>
</Value>
<Value name="8">
<Value>FavoriteColors</Value>
</Value>
</Data>
<DataLocation>^Sample.PersonD</DataLocation>
<DefaultData>PersonDefaultData</DefaultData>
<ExtentSize>200</ExtentSize>
<IdLocation>^Sample.PersonD</IdLocation>
<IndexLocation>^Sample.PersonI</IndexLocation>
<Property name="%%CLASSNAME">
<Selectivity>50.0000%</Selectivity>
</Property>
...
また、ほとんどの場合、コンパイラでもストレージ定義が生成および更新されます。ストレージの詳細は、“永続クラスの定義” の章を参照してください。
インデックス
その他の SQL テーブルのように、Caché SQL テーブルはインデックスOpens in a new tabを保持できます。インデックスを定義するには、対応するクラス定義にインデックス定義を追加します。
インデックスにより、特定のフィールドまたはフィールドの組合せの一意性を確保する制約を追加できます。このようなインデックスの詳細は、“永続クラスの定義” の章を参照してください。
インデックスのもう 1 つの用途は、クエリの実行速度を速くするために、クラスに関連付けられ頻繁に要求されるデータのソート済み特定サブセットを定義することです。例えば、一般に、特定のフィールドを使用する WHERE 節がクエリに含まれている場合は、そのフィールドにインデックスが作成されていると、クエリの実行速度が速くなります。一方、そのフィールドにインデックスがない場合、エンジンは、すべての行に対して指定の条件と一致しているかどうかを確認するフル・テーブル・スキャンを実行する必要があります。これは、テーブルが巨大になると、負荷の高い操作になります。“永続クラスのその他のオプション” の章を参照してください。
外部キー
Caché SQL テーブルは、外部キーOpens in a new tabも保持できます。これを定義するには、対応するクラス定義に外部キー定義を追加します。
外部キーは、新しいデータが追加された場合や、データが変更された場合に Caché が使用するテーブル間に、参照整合性制約を確立します。このドキュメントで後述するリレーションシップを使用すると、システムはリレーションシップを自動的に外部キーとして扱います。リレーションシップを使用しない場合や、追加する理由が別にある場合は、外部キーを追加することもできます。
外部キーの詳細は、“永続クラスのその他のオプション” の章を参照してください。
トリガ
Caché SQL テーブルは、トリガOpens in a new tabも保持できます。これを定義するには、対応するクラス定義にトリガ定義を追加します。
トリガでは、特定のイベントの発生時 (具体的には、レコードの挿入時、変更時または削除時) に自動的に実行されるコードを定義します。
トリガの詳細は、“永続クラスのその他のオプション” の章を参照してください。
その他のクラス・メンバ
クラス・メソッドやクラス・クエリは、ストアド・プロシージャOpens in a new tabとして呼び出せるように定義できます。このストアド・プロシージャは SQL から呼び出せます。
この章で説明していないクラス・メンバの場合は、SQL への投影がありません。つまり、Caché には、それらのクラス・メンバを SQL から直接使用する方法や、SQL から簡単に使用できるようにする方法がないということです。
エクステント
エクステントという用語は、特定の永続クラスのすべてのレコード (ディスク上のレコード) を表します。次の章で示すように、%PersistentOpens in a new tab クラスには、クラスのエクステントを操作する複数のメソッドが用意されています。
Caché は、従来にない強力な解釈によるオブジェクトテーブル・マッピングを使用します。既定では、特定の永続クラスのエクステントには、すべてのサブクラスのエクステントが含まれます。したがって、以下のようになります。
-
永続クラス Person にサブクラス Employee がある場合、Person エクステントには、Person のすべてのインスタンスと Employee のすべてのインスタンスが含まれます。
-
クラス Employee のどのインスタンスの場合も、そのインスタンスは Person エクステントおよび Employee エクステントに含まれます。
インデックスは、インデックスが定義されるクラスの全範囲を自動的にカバーします。Person に定義されているインデックスには、Person インスタンスと Employee インスタンスの両方が含まれます。Employee エクステントで定義されたインデックスは、Employee インデックスだけを含みます。
サブクラスは、そのスーパークラスで定義されていない追加のプロパティも定義できます。これらは、サブクラスのエクステントでは使用できますが、スーパークラスのエクステントでは使用できません。例えば、Employee エクステントには Department フィールドが含まれますが、これは Person エクステントには含まれません。
前述の点は、Caché では、同じタイプのレコードをすべて取得するクエリを比較的簡単に作成できることを意味します。例えば、すべてのタイプの人々をカウントする場合、Person テーブルに対してクエリを実行できます。従業員のみをカウントする場合、同じクエリを Employee テーブルに対して実行します。これとは対照的に、他のオブジェクト・データベースでは、すべてのタイプの人々をカウントするには、テーブルを結合する複雑なクエリを作成する必要があり、別のサブクラスが追加されるたびにこのクエリを更新する必要があります。
同様に、ID を使用するメソッドは、すべて多様な形態で動作します。つまり、渡された ID 値に応じて、さまざまなタイプのオブジェクトを操作できるということです。
例えば、Sample.PersonOpens in a new tab オブジェクトのエクステントには、Sample.PersonOpens in a new tab のインスタンスと、Sample.EmployeeOpens in a new tab のインスタンスが含まれます。Sample.PersonOpens in a new tab クラスの %OpenId() を呼び出すと、結果の OREF は、データベースに保存されている内容に応じて Sample.PersonOpens in a new tab か Sample.EmployeeOpens in a new tab のどちらかのインスタンスになります。
// Open person "10"
Set obj = ##class(Sample.Person).%OpenId(10)
Write $ClassName(obj),! // Sample.Person
// Open person "110"
Set obj = ##class(Sample.Person).%OpenId(110)
Write $ClassName(obj),! // Sample.Employee
Sample.EmployeeOpens in a new tab クラスの %OpenId() メソッドでは、ID 10 を開こうとしても、オブジェクトが返されません。これは、ID 10 は Sample.EmployeeOpens in a new tab のエクステントではないためです。
// Open employee "10"
Set obj = ##class(Sample.Employee).%OpenId(10)
Write $IsObject(obj),! // 0
// Open employee "110"
Set obj = ##class(Sample.Employee).%OpenId(110)
Write $IsObject(obj),! // 1
エクステントの管理
既定のストレージ・クラス (%Library.CacheStorageOpens in a new tab) を使用するクラスの場合、Caché は、エクステント・マネージャで使用するために登録されているエクステントのエクステント定義とグローバルを管理します。エクステント・マネージャに対するインタフェースには、%ExtentMgr.UtilOpens in a new tab クラスを使用します。この登録プロセスはクラスのコンパイル時に実行されます。何らかのエラーまたは名前の競合が発生すると、コンパイルに失敗します。コンパイルを成功させるには競合を解決します。このユーティリティでは、インデックスの名前を変更するか、データのストレージの場所を明確に追加する必要があります。
MANAGEDEXTENT クラス・パラメータの既定値は 1 です。この値では、グローバル名の登録および競合使用のチェックが実行されます。値が 0 の場合には、登録も競合のチェックも実行されません。
グローバル参照を意図的に共有しているクラスがアプリケーションに複数存在する場合は、関連するすべてのクラスの MANAGEDEXTENT に 0 を指定します (関連するクラスが既定のストレージを使用する場合)。そうしないと、再コンパイル時に以下のようなエラーが発生します。
ERROR #5564: Storage reference: '^This.App.Global used in 'User.ClassA.cls' is already registered for use by 'User.ClassB.cls'
エクステントのメタデータを削除するには、いくつかの方法があります。
-
##class(%ExtentMgr.Util).DeleteExtentDefinition(extent,extenttype) の呼び出しを使用します。extent は、通常、クラス名になります。extenttype はエクステントのタイプです (クラスの場合、この引数の既定値でもある cls)。
-
以下のいずれかの呼び出しを使用します。
-
$SYSTEM.OBJ.Delete(classname,flags)。classname は削除対象のクラスです。flags には e を指定します。
-
$SYSTEM.OBJ.DeletePackage(packagename,flags)。packagename は削除対象のクラスです。flags には e を指定します。
-
$SYSTEM.OBJ.DeleteAll(flags)。flags には e を指定します。
これらの呼び出しは、%SYSTEM.OBJOpens in a new tab クラスのメソッドです。
-
Extent クエリ
すべての永続クラスには、"Extent" というクラス・クエリが自動的に含まれます。このクラス・クエリは、エクステントに含まれるすべての ID を提示します。
クラス・クエリの使用に関する一般的な情報は、“クラス・クエリの定義と使用” の章を参照してください。以下の例では、クラス・クエリを使用して、Sample.PersonOpens in a new tab クラスのすべての ID を表示します。
set query = ##class(%SQL.Statement).%New()
set status= query.%PrepareClassQuery("Sample.Person","Extent")
if 'status {
do $system.OBJ.DisplayError(status)
}
set rset=query.%Execute()
While (rset.%Next()) {
Write rset.%Get("ID"),!
}
Sample.PersonOpens in a new tab エクステントは、Sample.PersonOpens in a new tab のすべてのインスタンスとそのサブクラスを含みます。これについての説明は、“永続クラスの定義” の章を参照してください。
"Extent" クエリは、以下の SQL クエリと同等です。
SELECT %ID FROM Sample.Person
これらのいずれの方法でも、返される ID 値の順序には依存できないことに注意してください。これは Caché が、この要求を満たすために、他のプロパティ値を使用して順序付けられたインデックスを使用する方が効率的であると判断する場合があるからです。必要に応じて、ORDER BY %ID 節を SQL クエリに追加できます。