Skip to main content

This documentation is for an older version of this product. See the latest version of this content.Opens in a new tab

永続オブジェクトを使用した作業

%PersistentOpens in a new tab クラスは、保存可能な (ディスクに書き込める) オブジェクトに対応した API です。この章では、この API の使用方法について説明します。この章の情報は、%PersistentOpens in a new tab のすべてのサブクラスに適用されます。

永続オブジェクトの概要” の章、“永続クラスの定義” の章、および “永続クラスのその他のオプション” の章も参照してください。

この章で示されているサンプルのほとんどが、Samples-Data サンプル (https://github.com/intersystems/Samples-DataOpens in a new tab) からのものです。(例えば) SAMPLES という名前の専用ネームスペースを作成し、そのネームスペースにサンプルをロードすることをお勧めします。一般的な手順は、"InterSystems IRIS® で使用するサンプルのダウンロード" を参照してください。

オブジェクトの保存

オブジェクトをデータベースに保存するには、そのオブジェクトの %Save() メソッドを呼び出します。以下に例を示します。

 Set obj = ##class(MyApp.MyClass).%New()
 Set obj.MyValue = 10

 Set sc = obj.%Save()

%Save() メソッドは、保存操作が成功したか失敗したかを示す %StatusOpens in a new tab 値を返します。例えば、オブジェクトが無効なプロパティ値を持つ場合や一意性に違反した場合、エラーが発生することがあります。前の章の “オブジェクトの検証” を参照してください。

オブジェクトで %Save() を呼び出すと、保存されているオブジェクトから “アクセス” できる、すべての変更済みオブジェクトが自動的に保存されます。つまり、すべての埋め込みオブジェクト、コレクション、ストリーム、参照オブジェクト、およびオブジェクトに関与するリレーションシップが必要に応じて自動的に保存されます。保存操作全体が 1 つのトランザクションとして実行されます。保存できないオブジェクトがある場合、トランザクション全体が失敗し、ロール・バックされます (ディスクには変更がなく、すべてのメモリ内オブジェクト値が、%Save() メソッドが呼び出される前の値に戻ります)。

初めてオブジェクトが保存されるとき、%Save() メソッドの既定の動作は、自動的にそのオブジェクトにオブジェクト ID 値を割り当てます。この ID 値は後に、データベース内でオブジェクトの検索時に使用されます。既定の場合、ID は、$Increment 関数を使用して生成されます。または、クラスは、idkey index を持つプロパティ値に基づくユーザ提供オブジェクト ID を使用できます (この場合、プロパティ値は、“||” 文字列を含むことはできません)。一度割り当てられると、(これがユーザ提供の ID でも) 特定のオブジェクト・インスタンスに対するオブジェクト ID 値を変更できません。

%Id() メソッドを使用して、保存されたオブジェクトに対するオブジェクト ID を検索できます。

 // Open person "22"
 Set person = ##class(Sample.Person).%OpenId(22)
 Write "Object ID: ",person.%Id(),!

%Save() メソッドが行う詳細は、以下のとおりです。

  1. 最初に、“SaveSet” として知られる一時的な構造を構築します。SaveSet は単純なグラフで、保存されたオブジェクトから到達可能なすべてのオブジェクトに対する参照を含みます。(一般に、オブジェクト・クラス A に、別のオブジェクト・クラス B の値を保持しているプロパティがあれば、A のインスタンスは B のインスタンスに “到達” できます。) SaveSet の目的は、関連するオブジェクトの複雑なセットに関連する保存操作が、可能な限り効果的に実行できるようにすることです。SaveSet は、オブジェクト間の保存順序の依存関係も解決します。

    各オブジェクトが SaveSet に追加されると、その %OnAddToSaveSet() コールバック・メソッドが存在する場合は呼び出されます。

  2. SaveSet 内の各オブジェクトを順番に検索し、それらが変更されていないかをチェックします (つまり、オブジェクトを開いた後で、または最後に保存されて以降、そのプロパティ値が変更されていないかをチェックします)。変更されているオブジェクトがある場合、そのオブジェクトは保存されます。

  3. 保存される前に、変更された各オブジェクトは検証されます (そのプロパティ値がテストされます。その %OnValidateObject() メソッドが存在する場合は呼び出され、一意制約がテストされます)。オブジェクトが有効な場合、保存操作が継続します。無効なオブジェクトがある場合は、%Save() の呼び出しが失敗し、現在のトランザクションがロール・バックされます。

  4. 各オブジェクトの保存前、または保存後、%OnBeforeSave() メソッドと %OnAfterSave() コールバック・メソッドが存在する場合は実行されます。同様に、定義されている BEFORE INSERT、BEFORE UPDATE、AFTER INSERT、および AFTER UPDATE のトリガがある場合は、それらも呼び出されます。

    これらのコールバックおよびトリガに Insert 引数が渡されます。この引数は、オブジェクトが挿入されているか (初めての保存の場合)、または更新されているかを示すものです。

    これらのコールバック・メソッドまたはトリガのいずれかが失敗した場合 (エラー・コードを返した場合)、%Save() への呼び出しは失敗し、現在のトランザクションがロール・バックされます。

  5. 最後に、%OnSaveFinally() コールバック・メソッドが存在する場合は呼び出されます。

    このコールバック・メソッドは、トランザクションが完了し、保存のステータスが確定した後に呼び出されます。

現在のオブジェクトが変更されていない場合、ディスクには %Save() メソッドによって書き込みは行われません。オブジェクトの保存は必要がなく、そのため保存は失敗しないため、成功が返されます。 実際のところ、%Save() の返り値では、保存操作で要求がすべて実行されたか、それとも要求どおりには実行されなかったか (および明確にではなく何かがディスクに書き込まれたかどうか) が示されます。

Important:

マルチプロセス環境では、適切な並行処理の制御を使用していることを確認してください。“オブジェクト同時処理のオプション” を参照してください。

Rollback

%Save() メソッドは、SaveSet 内のすべてのオブジェクトを単独のトランザクションとして自動的に保存します。これらのオブジェクトのいずれかの保存が失敗した場合、トランザクション全体がロール・バックされます。このロールバックの場合、InterSystems IRIS は以下を実行します。

  1. 割り当てられた ID を元に戻します。

  2. 削除された ID を回復します。

  3. 変更されたビットを回復します。

  4. 正常にシリアル化されたオブジェクトに対して、%OnRollBack() コールバック・メソッドを呼び出します (実装されている場合)。

    InterSystems IRIS は、正常にシリアル化されていないオブジェクト (つまり、無効なオブジェクト) に対しては、このメソッドを呼び出しません。

オブジェクトとトランザクションの保存

前述したように、%Save() メソッドは、SaveSet 内のすべてのオブジェクトを単独のトランザクションとして自動的に保存します。これらのオブジェクトのいずれかの保存が失敗した場合、トランザクション全体がロール・バックされます。

複数の関連しないオブジェクトを 1 つのトランザクションとして保存する場合は、%Save() の呼び出しを 1 つの明示的なトランザクション内にまとめる必要があります。つまり、TSTART コマンドを使用してトランザクションを開始し、TCOMMIT コマンドを使用してトランザクションを終了する必要があります。

例 :

 // start a transaction
 TSTART

 // save first object
 Set sc = obj1.%Save()

 // save second object (if first was save)
 If ($$$ISOK(sc)) {
     Set sc = obj2.%Save()
 }

 // if both saves are ok, commit the transaction
 If ($$$ISOK(sc)) {
     TCOMMIT
 }

上記の例では、注意すべきことが 2 つあります。

  1. %Save() メソッドは、トランザクション内で呼び出されたかどうかを知っています (システム変数 $TLEVEL が 0 より大きいため)。

  2. トランザクション内の %Save() メソッドが失敗した場合、トランザクション全体がロール・バックされます (TROLLBACK コマンドが実行されます)。これは、アプリケーションは明示的なトランザクション内のすべての %Save() をテストし、いずれか 1 つが失敗したら他のオブジェクトでの %Save() の呼び出しをスキップし、最終的な TCOMMIT コマンドの呼び出しもスキップする必要があることを意味しています。

保存したオブジェクトの存在のテスト

特定のオブジェクト・インスタンスがデータベース内に保存されているかをテストするには、2 つの基本的な方法があります。

これらの例では、ID は整数です (InterSystems IRIS が既定で生成する ID)。次の章では、ID がオブジェクトの固有プロパティに基づくようにクラスを定義する方法について説明しています。

ObjectScript によるオブジェクトの存在のテスト

%ExistsId() クラス・メソッドは、指定された ID を確認します。指定されたオブジェクトがデータベース内に存在する場合は True (1) の値を返し、それ以外の場合は False (0) を返します。これは、%PersistentOpens in a new tab から継承されたすべてのクラスで使用できます。以下に例を示します。

 Write ##class(Sample.Person).%ExistsId(1),!   // should be 1 
 Write ##class(Sample.Person).%ExistsId(-1),!  // should be 0

ここでは、最初の行が 1 を返します。これは、Sample.Person%PersistentOpens in a new tab を継承していて、SAMPLES データベースは、このクラスにデータを提供するためです。

%Exists() メソッドも使用できます。このメソッドには、ID ではなく OID が必要になります。OID はデータベースに対して一意の永続識別子で、オブジェクトの ID とクラス名の両方が含まれます。詳細は、"保存したオブジェクトの識別子 : ID および OID" を参照してください。

SQL によるオブジェクトの存在のテスト

保存したオブジェクトの存在を SQL でテストするには、指定した ID と一致する %ID フィールドを持つ行を選択する SELECT 文を使用します (保存したオブジェクトの identity プロパティは、%ID フィールドとして投影されます)。

例えば、以下のように埋め込み SQL を使用します。

 &sql(SELECT %ID FROM Sample.Person WHERE %ID = '1')
 Write SQLCODE,!  // should be 0: success

 &sql(SELECT %ID FROM Sample.Person WHERE %ID = '-1')
 Write SQLCODE,!  // should be 100: not found

ここでは、最初のケースは結果として 0 の SQLCODE を返します (成功を意味します)。これは、Sample.Person%PersistentOpens in a new tab を継承していて、SAMPLES データベースは、このクラスにデータを提供するためです。

2 つ目のケースは、文の実行は成功したもののデータは返されないことを示す SQLCODE 100 になります。システムは 0 未満の ID を自動的には生成しないため、このように想定されます。

埋め込み SQL の詳細は、"InterSystems SQL の使用法" の “埋め込み SQL” を参照してください。SQLCODE の詳細は、"InterSystems IRIS エラー・リファレンス" の “SQLCODE 値とエラー・メッセージ” を参照してください。

保存したオブジェクトのオープン

オブジェクトを開く (ディスクからメモリにオブジェクト・インスタンスをロードする) には、以下のように %OpenId() メソッドを使用します。

classmethod %OpenId(id As %String, 
                    concurrency As %Integer = -1, 
                    ByRef sc As %Status = $$$OK) as %ObjectHandle

以下はその説明です。

  • id は、開くオブジェクトの ID です。

    この例では、ID は整数です。次の章では、ID がオブジェクトの固有プロパティに基づくようにクラスを定義する方法について説明しています。

  • concurrency は、オブジェクトを開くために使用する並行処理レベル (ロック) です。

  • 参照渡しの sc は、%StatusOpens in a new tab の値です。この値は、呼び出しが成功したか、失敗したかを表します。

指定のオブジェクトを開くことができる場合は、このメソッドが OREF を返します。オブジェクトが見つからない場合やオブジェクトが開けない場合は、NULL 値 ("") が返されます。

例 :

 // Open person "10"
 Set person = ##class(Sample.Person).%OpenId(10)

 Write "Person: ",person,!    // should be an object reference

 // Open person "-10"
 Set person = ##class(Sample.Person).%OpenId(-10)

 Write "Person: ",person,!    // should be a null string

%Open() メソッドも使用できます。このメソッドには、ID ではなく OID が必要になります。OID はデータベースに対して一意の永続識別子で、オブジェクトの ID とクラス名の両方が含まれます。詳細は、"保存したオブジェクトの識別子 : ID および OID" を参照してください。

オブジェクトが開くときに実行される追加の処理を行うには、%OnOpen() および %OnOpenFinally() のコールバック・メソッドを定義します。詳細は、"コールバック・メソッドの定義" を参照してください。

%OpenId() に対する複数の呼び出し

%OpenId() が 1 つの InterSystems IRIS プロセス内で同じ ID と同じオブジェクトに対して複数回呼び出された場合、メモリに作成されるオブジェクト・インスタンスは 1 つのみです。それ以降の %OpenId() の呼び出しはすべて、既にメモリにロードされているオブジェクトへの参照が返されます。

例えば、同じオブジェクトへの複数回の %OpenId() 呼び出しを特徴とする以下のクラスを見てみましょう。

Class User.TestOpenId Extends %RegisteredObject
{

ClassMethod Main()
{
   set A = ##class(Sample.Person).%OpenId(1)
   write "A: ", A.Name

   set A.Name = "David"
   write !, "A after set: ", A.Name

   set B = ##class(Sample.Person).%OpenId(1)
   write !, "B: ", B.Name

   do ..Sub()

   job ..JobSub()
   hang 1
   write !, "D in JobSub: ", ^JobSubD
   kill ^JobSubD

   kill A, B
   set E = ##class(Sample.Person).%OpenId(1)
   write !, "E:", E.Name
}

ClassMethod Sub()
{
   set C = ##class(Sample.Person).%OpenId(1)
   write !, "C in Sub: ", C.Name
}

ClassMethod JobSub()
{
   set D = ##class(Sample.Person).%OpenId(1)
   set ^JobSubD = D.Name
}

}

Main() メソッドを呼び出すと、以下のような結果になります。

  1. 最初の %OpenId() 呼び出しは、インスタンス・オブジェクトをメモリにロードします。A 変数はこのオブジェクトを参照しており、この変数によって、ロードされたオブジェクトを変更できます。

       set A = ##class(Sample.Person).%OpenId(1)
       write "A: ", A.Name
    

    A: Uhles,Norbert F.

       set A.Name = "David"
       write !, "A after set: ", A.Name
    

    A after set: David

  2. 2 つ目の %OpenId() 呼び出しは、データベースからオブジェクトを再ロードすることも、変数 A を使用して加えられた変更を上書きすることもありません。その代わり、変数 B が変数 A と同じオブジェクトを参照します。

       set B = ##class(Sample.Person).%OpenId(1)
       write !, "B: ", B.Name
    

    B: David

  3. 3 つ目の %OpenId() 呼び出しは、Sub() メソッド内にあり、これも既にロードされているオブジェクトを参照します。このメソッドは、Main() メソッドと同じ InterSystems IRIS プロセスに含まれています。変数 C は、変数 A および変数 B と同じオブジェクトを参照します。ただし、変数 A と B はこのメソッドのスコープ内では使用できません。Sub() メソッドの最後に、プロセスは Main() メソッドに戻り、変数 C は破棄され、変数 A および B はスコープ内に戻ります。

       do ..Sub()
    
    ClassMethod Sub()
    {
       set C = ##class(Sample.Person).%OpenId(1)
       write !, "C in Sub: ", C.Name
    }

    C in Sub: David

  4. 4 つ目の %OpenId() 呼び出しは、JobSub() メソッド内にあり、JOB コマンドを使用して別個の InterSystems IRIS プロセスで実行されます。この新しいプロセスは、新しいインスタンス・オブジェクトをメモリにロードし、変数 D でこの新しいオブジェクトを参照します。

       job ..JobSub()
       hang 1
       write !, "D in JobSub: ", ^JobSubD
       kill ^JobSubD
    
    ClassMethod JobSub()
    {
       set D = ##class(Sample.Person).%OpenId(1)
       set ^JobSubD = D.Name
    }

    D in JobSub: Uhles,Norbert F.

  5. 5 つ目の %OpenId() 呼び出しが実行されるのは、ロード済みのオブジェクトを元のプロセスで参照している変数 (A および B) をすべて削除し、それによってそのオブジェクトをメモリから削除した後です。直前の %OpenId() 呼び出しと同様に、この呼び出しも新しいインスタンス・オブジェクトをメモリにロードし、変数 E でこのオブジェクトを参照します。

       kill A, B
       set E = ##class(Sample.Person).%OpenId(1)
       write !, "E:", E.Name

    E: Uhles,Norbert F.

複数回ロードされるオブジェクトを強制的に再ロードするには、%Reload() メソッドを使用します。オブジェクトを再ロードすると、そのオブジェクトに対する未保存の変更は元に戻されます。以前に割り当てられていた変数は、再ロードされたバージョンを参照します。以下に例を示します。

 set A = ##class(Sample.Person).%OpenId(1)
 write "A: ", A.Name // A: Uhles,Norbert F.
 
 set A.Name = "David"
 write !, "A after set: ", A.Name // David
 
 set B = ##class(Sample.Person).%OpenId(1)
 write !, "B before reload: ", B.Name // David

 do B.%Reload()
 write !, "B after reload: ", B.Name // Uhles,Norbert F.
 write !, "A after reload: ", A.Name // Uhles,Norbert F.

並行処理

%OpenId() メソッドは、入力としてオプションの concurrency 引数を取ります。この引数は、オブジェクト・インスタンスを開くために使用する並行処理レベル (ロックのタイプ) を指定します。

%OpenId() メソッドがオブジェクトのロックを取得できない場合、メソッドは失敗します。

オブジェクトに対する現在の並行処理レベルの設定を上げ下げするには、そのオブジェクトを %OpenId() で再度開いて別の並行処理レベルを指定します。以下はその例です。

 set person = ##class(Sample.Person).%OpenId(6,0)

並行処理レベル 0 でperson を開き、以下のようにして並行処理レベルを事実上 4 まで引き上げます。

 set person = ##class(Sample.Person).%OpenId(6,4)

並行処理レベル 3 (共有保持ロック) または 4 (共有排他ロック) を指定すると、オブジェクトを開くときにそのオブジェクトが強制的に再ロードされます。可能なオブジェクト並行処理レベルの詳細は、"オブジェクト同時処理のオプション" を参照してください。

スウィズリング

永続オブジェクトのインスタンスを開いて (メモリにロードして)、そのインスタンスが参照するオブジェクトを使用する場合、この参照されるオブジェクトは自動的に開かれます。このプロセスをスウィズリングといいます。また、“遅延ロード” ということもあります。

例えば、以下のコードは Sample.Employee オブジェクトのインスタンスを開き、ドット構文を使用して関連する Sample.Company オブジェクトを自動的にスウィズルします (開きます)。

 // Open employee "101"
 Set emp = ##class(Sample.Employee).%OpenId(101)

 // Automatically open Sample.Company by referring to it:
 Write "Company: ",emp.Company.Name,!

オブジェクトをスウィズルすると、オブジェクトは、それをスウィズルするオブジェクトの並行処理値ではなく、それが属するクラスの既定の並行処理値を使用して開かれます。この章で後述する “オブジェクト同時処理のオプション” を参照してください。

スウィズルされたオブジェクトは、それを参照するオブジェクトや変数がなくなるとすぐに、メモリから削除されます。

保存された値の読み取り

永続オブジェクトのインスタンスを開き、そのプロパティを変更したと仮定します。また、そのオブジェクトを保存する前に、データベースに保存された元の値を表示するとします。これを行う最も簡単な方法は、SQL 文を使用することです (SQL は、メモリ内のオブジェクトではなく、常にデータベースに対して実行されるものです)。

例 :

 // Open person "1"
 Set person = ##class(Sample.Person).%OpenId(1)
 Write "Original value: ",person.Name,!

 // modify the object
 Set person.Name = "Black,Jimmy Carl"
 Write "Modified value: ",person.Name,!

 // Now see what value is on disk
 Set id = person.%Id()
 &sql(SELECT Name INTO :name
         FROM Sample.Person WHERE %ID = :id)

 Write "Disk value: ",name,!

保存したオブジェクトの削除

永続インタフェースには、データベースからオブジェクトを削除するためのメソッドがあります。

%DeleteId() メソッド

%DeleteId() メソッドは、データベース内に保存されているオブジェクトを削除します。これには、オブジェクトに関連付けられているストリーム・データも含まれます。このメソッドは、以下のとおりです。

classmethod %DeleteId(id As %String, concurrency As %Integer = -1) as %Status

以下はその説明です。

  • id は、開くオブジェクトの ID です。

    この例では、ID は整数です。次の章では、ID がオブジェクトの固有プロパティに基づくようにクラスを定義する方法について説明しています。

  • concurrency は、オブジェクト削除時に使用する並行処理レベル (ロック) です。

例 :

 Set sc = ##class(MyApp.MyClass).%DeleteId(id)

%DeleteId() は、オブジェクトが削除されたか否かを示す %StatusOpens in a new tab 値を返します。

オブジェクトが削除される前に、%OnDelete() コールバック・メソッドが存在していれば、%DeleteId() はそれを呼び出します。%OnDelete()%StatusOpens in a new tab 値を返します。%OnDelete() がエラー値を返した場合、オブジェクトは削除されず、現在のトランザクションがロール・バックされ、%DeleteId() はエラー値を返します。

オブジェクトが削除された後に、%OnAfterDelete() コールバック・メソッドが存在していれば、%DeleteId() はそれを呼び出します。この呼び出しは、%DeleteData() がエラーを返さない限り、%DeleteData() が呼び出された直後に行われます。%OnAfterDelete() がエラーを返す場合、現在のトランザクションはロール・バックされ、%DeleteId() はエラー値を返します。

最後に、%DeleteId() は、%OnDeleteFinally() コールバック・メソッドが存在する場合は呼び出します。このコールバック・メソッドは、トランザクションが完了し、削除のステータスが確定した後に呼び出されます。

%DeleteId() メソッドは、メモリ内のオブジェクト・インスタンスには効果がありません。

%Delete() メソッドも使用できます。このメソッドには、ID ではなく OID が必要になります。OID はデータベースに対して一意の永続識別子で、オブジェクトの ID とクラス名の両方が含まれます。詳細は、"保存したオブジェクトの識別子 : ID および OID" を参照してください。

%DeleteExtent() メソッド

%DeleteExtent() メソッドは、エクステント内のすべてのオブジェクト (およびオブジェクトのサブクラス) を削除します。具体的には、エクステント全体を反復処理して、各インスタンスで %DeleteId() メソッドを起動します。

%KillExtent() メソッド

%KillExtent() メソッドは、オブジェクトのエクステントを保存するグローバルを直接削除します (ストリームに関連付けられたデータは含まれません)。%DeleteId() メソッドは呼び出されず、参照整合性アクションも実行されません。このメソッドは、単に開発段階で開発者を支援することを目的としています (従来のリレーショナル・データベース製品にある TRUNCATE TABLE コマンドに類似したものです)。関連付けられているストリーム・データを含め、エクステント内のすべてのオブジェクトを削除する必要がある場合は、代わりに %DeleteExtent() を使用してください。

Caution:

%KillExtent() は、開発環境でのみ使用するためのものであるため、実際のアプリケーションで使用しないでください。%KillExtent() は制約やユーザ実装のコールバックを回避して、データ整合性の問題を引き起こす可能性があります。

オブジェクト識別子のアクセス

オブジェクトが保存されている場合、そのオブジェクトには ID と OID があります。これらは、ディスクで使用する永久識別子です。オブジェクトの OREF があれば、その OREF を使用して、これらの識別子を取得できます。

OREF に関連付けられた ID を調べるには、オブジェクトの %Id() メソッドを呼び出します。以下に例を示します。

 write oref.%Id()

OREF に関連付けられた OID を調べる場合は、以下の 2 つの選択肢があります。

  1. オブジェクトの %Oid() メソッドを呼び出します。以下に例を示します。

     write oref.%Oid()
  2. オブジェクトの %%OID プロパティにアクセスします。このプロパティ名には % 文字が含まれているため、その名前を二重引用符で囲む必要があります。以下に例を示します。

     write oref."%%OID"

オブジェクト同時処理のオプション

オブジェクトを開く場合や削除する場合には、適切に並行処理を指定することが重要です。並行処理はいくつかの異なるレベルで指定できます。

  1. 使用するメソッドの concurrency 引数を指定できます。

    %PersistentOpens in a new tab クラスの多くのメソッドで、この引数 (整数) を指定できます。この引数により、並行処理の制御に使用されるロックの方式が決定されます。後述のサブセクションに、指定可能な並行処理の値の一覧があります。

    メソッドの呼び出しで concurrency 引数の指定を省略すると、この引数はメソッドによって -1 に設定されます。つまり、操作するクラスに対して DEFAULTCONCURRENCY クラス・パラメータの値が使用されます。次の項目を参照してください。

  2. 関連するクラスの DEFAULTCONCURRENCY クラス・パラメータを指定できます。すべての永続クラスは、プロセスに既定の並行処理を取得する式としてパラメータを定義する %PersistentOpens in a new tab から、このパラメータを継承します。次の項目を参照してください。

    クラス内で、このパラメータをオーバーライドして、ハードコードされた値を指定することも、独自のルールによって並行処理を決定する式を指定することもできます。どちらの場合も、パラメータの値は、このセクションで後述する指定可能な並行処理の値のいずれかにする必要があります。

  3. プロセスの既定の並行処理モードを設定できます。そのためには、$system.OBJ.SetConcurrencyMode() メソッドを使用します (このメソッドは、%SYSTEM.OBJOpens in a new tab クラスの SetConcurrencyMode()Opens in a new tab メソッドです)。

    その他の場合と同じように、指定可能な並行処理の値のいずれかを使用する必要があります。プロセスの並行処理モードを明示的に設定しない場合、既定値は 1 です。

    $system.OBJ.SetConcurrencyMode() メソッドは、DEFAULTCONCURRENCY クラス・パラメータに明示的な値を指定するクラスには効果がありません。

簡単な例を挙げてみましょう。永続オブジェクトを、その %OpenId() メソッドを使用して、並行処理の値を指定せずに (または値 -1 を明示的に指定して) 開いた場合、クラスで DEFAULTCONCURRENCY クラス・パラメータが指定されておらず、コードで並行処理モードを設定していないときは、オブジェクトの並行処理の既定値は 1 に設定されます。

Note:

システム・クラスによっては、DEFAULTCONCURRENCY クラス・パラメータが値 0 に設定されているものがあります。シャード・クラスでは常に並行処理の値 0 が使用されます。特定のオブジェクトの並行処理の値を確認するには、そのオブジェクトの %Concurrency プロパティを調べます。

並行処理を指定する理由

以下のシナリオでは、オブジェクトの読み取り時または書き込み時に、適切に並行処理を制御することが重要になる理由についての例を示しています。以下の例を考えてみます。

  1. プロセス A は、並行処理を指定せずにオブジェクトを開きます。

    SAMPLES>set person = ##class(Sample.Person).%OpenId(5)
     
    SAMPLES>write person
    1@Sample.Person
    
  2. プロセス B は、同じオブジェクトを並行処理値 4 で開きます。

    SAMPLES>set person = ##class(Sample.Person).%OpenId(5, 4)
     
    SAMPLES>write person
    1@Sample.Person
    
  3. プロセス A がオブジェクトのプロパティを変更し、%Save() を使用してそれを保存しようして、エラー・ステータスを受け取ります。

    SAMPLES>do person.FavoriteColors.Insert("Green")
    
    SAMPLES>set status = person.%Save()
     
    SAMPLES>do $system.Status.DisplayError(status)
     
    ERROR #5803: Failed to acquire exclusive lock on instance of 'Sample.Person'
    

これは、適切な並行処理の制御がない場合の同時処理の例です。例えば、プロセス A がオブジェクトをディスクに保存する可能性がある場合、他のプロセスと競合せずにオブジェクトを保存できるように、並行処理 4 でオブジェクトを開く必要があります。(これらの値については、この章で後述します)。この場合、プロセス B はアクセスを拒否される (並行処理違反で失敗する)、またはプロセス A がオブジェクトを解放するまで待機する必要があります。

並行処理の値

このセクションでは、有効な並行処理の値について説明します。まず、以下の詳細に注意してください。

  • アトミック書き込みは、並行処理が 0 より大きいときに保証されます。

  • InterSystems IRIS は、オブジェクトの保存や削除などの処理中にロックの取得と解放を実行します。詳細は、並行処理の値 (どのような制約があるのか、ロック・エスカレーションのステータス、およびストレージ構造) によって異なります。

  • どのような場合でも、オブジェクトがメモリから削除されると、そのオブジェクトのロックも削除されます。

有効な並行処理の値は、以下のとおりです。このリストも示すように、それぞれの値には名前が付いています。

並行処理の値 0 (ロックなし)

ロックを使用しません。

並行処理の値 1 (アトミック読み込み)

ロックは必要に応じて取得および解放され、オブジェクトの読み込みがアトミック処理として実行されることを保証します。

新規オブジェクトの作成時に、InterSystems IRIS がロックを取得することはありません。

オブジェクトを開いている間は、InterSystems IRIS はオブジェクトに対する共有ロックを取得します (アトミック読み込みを保証するために必要な場合)。読み込み処理が完了すると、InterSystems IRIS はロックを解放します。

以下のテーブルに、各シナリオに現れるロックを示します。

  オブジェクトが作成されるとき オブジェクトが開かれている間 オブジェクトが開かれた後 保存処理が完了した後
新規オブジェクト ロックなし N/A N/A ロックなし
既存のオブジェクト N/A 共有ロック (アトミック読み込みを保証するために必要な場合) ロックなし ロックなし
並行処理の値 2 (共有ロック)

1 (アトミック読み込み) と同じですが、オブジェクトを開くときには常に共有ロックを取得します (アトミック読み込みを保証するために必要とされない場合でも、このロックを取得します)。以下のテーブルに、各シナリオに現れるロックを示します。

  オブジェクトが作成されるとき オブジェクトが開かれている間 オブジェクトが開かれた後 保存処理が完了した後
新規オブジェクト ロックなし N/A N/A ロックなし
既存のオブジェクト N/A 共有ロック ロックなし ロックなし
並行処理の値 3 (共有/保持ロック)

新規オブジェクトの作成時に、InterSystems IRIS がロックを取得することはありません。

既存のオブジェクトを開いている間は、InterSystems IRIS はオブジェクトの共有ロックを取得します。

新規オブジェクトの保存後、InterSystems IRIS はオブジェクトの共有ロックを保持します。

以下のテーブルは、シナリオのリストです。

  オブジェクトが作成されるとき オブジェクトが開かれている間 オブジェクトが開かれた後 保存処理が完了した後
新規オブジェクト ロックなし N/A N/A 共有ロック
既存のオブジェクト N/A 共有ロック 共有ロック 共有ロック
並行処理の値 4 (排他/保持ロック)

既存のオブジェクトが開かれるとき、または新規オブジェクトが初めて保存されるときに、InterSystems IRIS は排他ロックを取得します。

以下のテーブルは、シナリオのリストです。

  オブジェクトが作成されるとき オブジェクトが開かれている間 オブジェクトが開かれた後 保存処理が完了した後
新規オブジェクト ロックなし N/A N/A 排他ロック
既存のオブジェクト N/A 排他ロック 排他ロック 排他ロック

並行処理とスウィズルされたオブジェクト

プロパティによって参照されるオブジェクトは、スウィズルされるオブジェクトのクラスで定義された既定の並行処理を使用してアクセス時にスウィズルされます。そのクラスの既定値が定義されていない場合、オブジェクトは、プロセスの既定の並行処理モードを使用してスウィズルされます。スウィズルされるオブジェクトは、それをスウィズルするオブジェクトの並行処理値は使用しません。

スウィズルされるオブジェクトが既にメモリ内にある場合は、実際にスウィズルによってオブジェクトが開かれることはありません。それは、既存のメモリ内のオブジェクトを参照するだけであり、その場合、そのオブジェクトの現在の状態が保持され、並行処理は変更されません。

この既定の動作をオーバーライドするには、以下の 2 つの方法があります。

  • スウィズルされたオブジェクトに対する同時処理を、新しい同時処理を指定する %Open() メソッドの呼び出しでアップグレードします。以下に例を示します。

     Do person.Spouse.%OpenId(person.Spouse.%Id(),4,.status) 

    %OpenId()Opens in a new tab の最初の引数では ID を指定し、2 番目の引数では新しい並行処理を指定し、3 番目の引数 (参照渡し) はメソッドのステータスを受け取ります。

  • オブジェクトをスウィズルする前に、プロセスの既定の並行処理モードを設定します。以下に例を示します。

     Set olddefault = $system.OBJ.SetConcurrencyMode(4) 

    このメソッドは、その引数として新しい同時処理モードを取り、前の同時処理モードを返します。

    異なる並行処理モードが必要なくなった場合は、以下のように既定の並行処理モードをリセットします。

     Do $system.OBJ.SetConcurrencyMode(olddefault)

バージョン確認 (並行処理引数の代替)

オブジェクトを開いたり削除したりするときに、並行処理引数を指定する代わりに、バージョン確認を実装できます。そのためには、VERSIONPROPERTY というクラス・パラメータを指定します。すべての永続クラスに、このパラメータがあります。永続クラスを定義するときに、バージョン確認を有効にする手順は次のとおりです。

  1. 更新可能なバージョン番号を保持した %IntegerOpens in a new tab 型のプロパティを、クラスのインスタンスごとに作成します。

  2. そのプロパティについては、InitialExpression キーワードの値を 0 に設定します。

  3. クラスについては、そのプロパティの名前と等しくなるように、VERSIONPROPERTY クラス・パラメータの値を設定します。VERSIONPROPERTY の値は、サブクラスからは別のプロパティに変更できません。

これでバージョン確認が、クラス内のインスタンスへの更新に組み込まれます。

バージョン確認が実装されると、クラス内のインスタンスがオブジェクトまたは SQL で更新されるたびに VERSIONPROPERTY で指定したプロパティが自動的にインクリメントされます。プロパティがインクリメントされる前に、InterSystems IRIS によってメモリ内の値と保存されている値が比較されます。この値が異なる場合は、同時処理の競合が起こりエラーが表示されます。この値が同じ場合は、プロパティがインクリメントされて保存されます。

Note:

この機能一式を使用して、楽観的同時実行制御を実装できます。

VERSIONPROPERTYInstanceVersion と呼ばれるプロパティを指す場合、クラスの SQL 更新文に同時処理の確認を実装するには、コードを次のように設定することになります。

 SELECT InstanceVersion,Name,SpecialRelevantField,%ID
     FROM my_table
     WHERE %ID = :myid

     // Application performs operations on the selected row

 UPDATE my_table
     SET SpecialRelevantField = :newMoreSpecialValue
     WHERE %ID = :myid AND InstanceVersion = :myversion

ここで、myversion は、元のデータで選択したバージョンのプロパティ値を指します。

FeedbackOpens in a new tab