この記事では、Cachéオブジェクトのバージョンストレージとリカバリを実装する方法を示します。
この機能は、%OnBeforeSave()メソッドとUpdateイベントのSQLトリガーを使用して永続クラスに追加できます。 永続クラスが使用されるほとんどの場合、プロパティの値(配列やリスト)を含むレコード全体は、^ ClassData(id)の形式のストレージグローバルノードに格納されるため、オブジェクトを上書きする前に、レコードの前のバージョンをある人里離れた場所に保存することができますWindowsのごみ箱のように。
この記事には、抽象PersHistクラス(必要なメソッドが追加される%Persistent継承クラス)およびTestPersHistデモクラスで構成される上記の機能の使用例が付属しています。TestPersHistデモクラスでは、PersHistの後継であって永続的。 オブジェクトとSQLアクセスの両方でレコードを作成および変更する方法とまったく同じですが、そこに上書きされたデータはトレースなしでは消えず、復元できます。
PersHistクラスのソース
///%Persistentクラスのこの祖先
///更新履歴の記録を有効にするトリガーが含まれています。
クラスPersHist Extends%Persistent [ Abstract 、 ClassType = ""、 ProcedureBlock ]
{
///このコールバックメソッドは、<METHOD>%Save </ METHOD>メソッドによって呼び出され、
///オブジェクトが保存されていることを通知します。 前に呼ばれます
///データはすべてディスクに書き込まれます。
///
/// <P> <VAR> insert </ VAR>は、このオブジェクトが初めて保存される場合、1に設定されます。
///
/// <P>このメソッドがエラーを返した場合、<METHOD>%Save </ METHOD>の呼び出しは失敗します。
メソッド %OnBeforeSave( insert As%Boolean ) As%Status [ Final 、 Private ]
{
q : $$$ OKを 挿入
q : '.. %ObjectModified () $$$ OK
q .. SaveLastRevision (.. %Id ())
}
///このコールバックメソッドは、<METHOD>%Delete </ METHOD>メソッドによって呼び出され、
/// <VAR> oid </ VAR>で指定されたオブジェクトが削除されていることを通知します。
///
/// <P>このメソッドがエラーを返した場合、オブジェクトは削除されません。
ClassMethod %OnDelete( oid As%ObjectIdentity ) As%ステータス [ 最終 、 プライベート ]
{
s id = $ lg ( oid ) q : ' id 0
d .. SaveLastRevision ( id )
q $$$ OK
}
ClassMethod GetDataLocation() As%String
{
■ ix = ##クラス ( %ClassDefinition )。 %OpenId (.. %ClassName ())
q : ix 。 ストレージ 。 カウント () '= 1 ""; 洗練されたストレージはサポートされていません
q ix 。 ストレージ 。 GetAt (1)。 データの場所
}
///保存されたリビジョンの次または前のID($ o()と同様)を返します
メソッド OrderIdSave( ids As%String = "" 、 Direction As%String = 1 ) As%String
{
s dl = .. GetDataLocation () q : dl = "" ""
s id = .. %Id () q : ' id ""
q $ o (@ dl @( "History" 、 id 、 ids )、 Direction )
}
///保存されたリビジョンのタイムスタンプを返します
メソッド GetTimeStampByIdSave( ids As%String ) As%String
{
s dl = .. GetDataLocation () q : dl = "" ""
s id = .. %Id () q : ' id ""
q : $ d (@ dl @( "History" 、 id 、 ids ))<10 ""
q $ o (@ dl @( "History" 、 id 、 ids 、 "" ))
}
///指定した時刻以降にリビジョンを実際に保存し直します
///そしてそれをロードします。 保存されていない変更はすべて失われます。
メソッド MakeActual( タイムスタンプ As%StringTimeStamp ) As%ステータス
{
s dl = .. GetDataLocation () q : dl = "" 0 ; サポートされていないストレージ
s id = .. %Id () q : ' id 0 ; 決して保存しなかった
s zts = タイムスタンプ
; 保存されたリビジョンをzts> = timestampで検索
i $ d (@ dl @( "History" 、 "ZI" 、 id 、 zts )) s ids = $ o (@ dl @( "History" 、 "ZI" 、 id 、 zts 、 "" )、-1)
e s zts = $ o (@ dl @( "History" 、 "ZI" 、 id 、 "" )) q : zts = "" 0 s ids = $ o (@ dl @( "History" 、 "ZI" 、 id 、 zts 、 "" ))
q $ s ( ids :.. MakeActualByIdSave ( ids )、1:0)
}
///保存されたリビジョンを再度実際に作成し、idsaveでロードします。
///未保存の変更はすべて失われます。
メソッド MakeActualByIdSave( ids As%String ) As%Status
{
s dl = .. GetDataLocation () q : dl = "" 0 ; サポートされていないストレージ
s id = .. %Id () q : ' id 0 ; 決して保存しなかった
q : $ d (@ dl @( "History" 、 id 、 ids ))<10 0
s zts = $ o (@ dl @( "History" 、 id 、 ids 、 "" )) q : zts = "" 0
d .. SaveLastRevision ( id )
k @ dl @( id ) m @ dl @( id )= @ dl @( "History" 、 id 、 ids 、 zts )
d .. %Reload () d .. %SetModified (1)
q .. %Save () ; インデックスを正しくするためにもう一度保存します
}
///削除されたレコードの次または前のID($ o()と同様)を返します
ClassMethod OrderDeletedId( id As%String = "" 、 Direction As%String = 1 ) As%String [ 最終 ]
{
s dl = .. GetDataLocation () q : dl = "" 0
f s id = $ o (@ dl @( "History" 、 id )、 Direction ) q : id = "" q : ' $ d (@ dl @( id ))
q id
}
ClassMethod SaveLastRevision( id ) As%ステータス [ 最終 ]
{
s id = $ g ( id ) q : ' id $$$ OK ; 保存する前のリビジョンはありません
s dl = .. GetDataLocation () q : dl = "" $$$ OK
s ids = $ i (@ dl @( "History" 、 id ))、 zts = $ zu (188) ; ローカルタイムゾーンの$ ZTIMESTAMP
m @ dl @( "History" 、 id 、 ids 、 zts )= @ dl @( id )
s @ dl @( "History" 、 "ZI" 、 id 、 zts 、 ids )= ""
q $$$ OK
}
ClassMethod UnDeleteId( id As%String ) As%ステータス [ 最終 ]
{
s dl = .. GetDataLocation () q : dl = "" 0
q : ' $ g ( id )0
q : $ d (@ dl @( id ))0 ; 削除されていません
q : $ d (@ dl @( "History" 、 id )) '> 1 0 ; 保存されたリビジョンはありません
; 最新リビジョンの検索
s ids = $ o (@ dl @( "History" 、 id 、 "" )、-1) q : ' ids 0
s zts = $ o (@ dl @( "History" 、 id 、 ids 、 "" )、-1) q : zts = "" 0
m @ dl @( id )= @ dl @( "History" 、 id 、 ids 、 zts )
s ix = .. %OpenId ( id ) d ix 。 %SetModified (1) s res = ix 。 %保存 () k ix
q res
}
OnBeforeDeleteForSQLの トリガー [ イベント = DELETE]
{
n id 、 ix s id = {ID} q : ' id
d .. SaveLastRevision ( id )
q
}
OnBeforeSaveForSQLの トリガー [ イベント =更新]
{
n id 、 ix s id = {ID} q : ' id
d .. SaveLastRevision ( id )
q
}
}
///更新履歴の記録を有効にするトリガーが含まれています。
クラスPersHist Extends%Persistent [ Abstract 、 ClassType = ""、 ProcedureBlock ]
{
///このコールバックメソッドは、<METHOD>%Save </ METHOD>メソッドによって呼び出され、
///オブジェクトが保存されていることを通知します。 前に呼ばれます
///データはすべてディスクに書き込まれます。
///
/// <P> <VAR> insert </ VAR>は、このオブジェクトが初めて保存される場合、1に設定されます。
///
/// <P>このメソッドがエラーを返した場合、<METHOD>%Save </ METHOD>の呼び出しは失敗します。
メソッド %OnBeforeSave( insert As%Boolean ) As%Status [ Final 、 Private ]
{
q : $$$ OKを 挿入
q : '.. %ObjectModified () $$$ OK
q .. SaveLastRevision (.. %Id ())
}
///このコールバックメソッドは、<METHOD>%Delete </ METHOD>メソッドによって呼び出され、
/// <VAR> oid </ VAR>で指定されたオブジェクトが削除されていることを通知します。
///
/// <P>このメソッドがエラーを返した場合、オブジェクトは削除されません。
ClassMethod %OnDelete( oid As%ObjectIdentity ) As%ステータス [ 最終 、 プライベート ]
{
s id = $ lg ( oid ) q : ' id 0
d .. SaveLastRevision ( id )
q $$$ OK
}
ClassMethod GetDataLocation() As%String
{
■ ix = ##クラス ( %ClassDefinition )。 %OpenId (.. %ClassName ())
q : ix 。 ストレージ 。 カウント () '= 1 ""; 洗練されたストレージはサポートされていません
q ix 。 ストレージ 。 GetAt (1)。 データの場所
}
///保存されたリビジョンの次または前のID($ o()と同様)を返します
メソッド OrderIdSave( ids As%String = "" 、 Direction As%String = 1 ) As%String
{
s dl = .. GetDataLocation () q : dl = "" ""
s id = .. %Id () q : ' id ""
q $ o (@ dl @( "History" 、 id 、 ids )、 Direction )
}
///保存されたリビジョンのタイムスタンプを返します
メソッド GetTimeStampByIdSave( ids As%String ) As%String
{
s dl = .. GetDataLocation () q : dl = "" ""
s id = .. %Id () q : ' id ""
q : $ d (@ dl @( "History" 、 id 、 ids ))<10 ""
q $ o (@ dl @( "History" 、 id 、 ids 、 "" ))
}
///指定した時刻以降にリビジョンを実際に保存し直します
///そしてそれをロードします。 保存されていない変更はすべて失われます。
メソッド MakeActual( タイムスタンプ As%StringTimeStamp ) As%ステータス
{
s dl = .. GetDataLocation () q : dl = "" 0 ; サポートされていないストレージ
s id = .. %Id () q : ' id 0 ; 決して保存しなかった
s zts = タイムスタンプ
; 保存されたリビジョンをzts> = timestampで検索
i $ d (@ dl @( "History" 、 "ZI" 、 id 、 zts )) s ids = $ o (@ dl @( "History" 、 "ZI" 、 id 、 zts 、 "" )、-1)
e s zts = $ o (@ dl @( "History" 、 "ZI" 、 id 、 "" )) q : zts = "" 0 s ids = $ o (@ dl @( "History" 、 "ZI" 、 id 、 zts 、 "" ))
q $ s ( ids :.. MakeActualByIdSave ( ids )、1:0)
}
///保存されたリビジョンを再度実際に作成し、idsaveでロードします。
///未保存の変更はすべて失われます。
メソッド MakeActualByIdSave( ids As%String ) As%Status
{
s dl = .. GetDataLocation () q : dl = "" 0 ; サポートされていないストレージ
s id = .. %Id () q : ' id 0 ; 決して保存しなかった
q : $ d (@ dl @( "History" 、 id 、 ids ))<10 0
s zts = $ o (@ dl @( "History" 、 id 、 ids 、 "" )) q : zts = "" 0
d .. SaveLastRevision ( id )
k @ dl @( id ) m @ dl @( id )= @ dl @( "History" 、 id 、 ids 、 zts )
d .. %Reload () d .. %SetModified (1)
q .. %Save () ; インデックスを正しくするためにもう一度保存します
}
///削除されたレコードの次または前のID($ o()と同様)を返します
ClassMethod OrderDeletedId( id As%String = "" 、 Direction As%String = 1 ) As%String [ 最終 ]
{
s dl = .. GetDataLocation () q : dl = "" 0
f s id = $ o (@ dl @( "History" 、 id )、 Direction ) q : id = "" q : ' $ d (@ dl @( id ))
q id
}
ClassMethod SaveLastRevision( id ) As%ステータス [ 最終 ]
{
s id = $ g ( id ) q : ' id $$$ OK ; 保存する前のリビジョンはありません
s dl = .. GetDataLocation () q : dl = "" $$$ OK
s ids = $ i (@ dl @( "History" 、 id ))、 zts = $ zu (188) ; ローカルタイムゾーンの$ ZTIMESTAMP
m @ dl @( "History" 、 id 、 ids 、 zts )= @ dl @( id )
s @ dl @( "History" 、 "ZI" 、 id 、 zts 、 ids )= ""
q $$$ OK
}
ClassMethod UnDeleteId( id As%String ) As%ステータス [ 最終 ]
{
s dl = .. GetDataLocation () q : dl = "" 0
q : ' $ g ( id )0
q : $ d (@ dl @( id ))0 ; 削除されていません
q : $ d (@ dl @( "History" 、 id )) '> 1 0 ; 保存されたリビジョンはありません
; 最新リビジョンの検索
s ids = $ o (@ dl @( "History" 、 id 、 "" )、-1) q : ' ids 0
s zts = $ o (@ dl @( "History" 、 id 、 ids 、 "" )、-1) q : zts = "" 0
m @ dl @( id )= @ dl @( "History" 、 id 、 ids 、 zts )
s ix = .. %OpenId ( id ) d ix 。 %SetModified (1) s res = ix 。 %保存 () k ix
q res
}
OnBeforeDeleteForSQLの トリガー [ イベント = DELETE]
{
n id 、 ix s id = {ID} q : ' id
d .. SaveLastRevision ( id )
q
}
OnBeforeSaveForSQLの トリガー [ イベント =更新]
{
n id 、 ix s id = {ID} q : ' id
d .. SaveLastRevision ( id )
q
}
}
クラスソースTestPersHist
///テスト方法
///
/// //インスタンスを作成して初期化します:
///
/// s ix = ## class(TestPersHist)。%New()
/// s ix.TestVal = $ H
/// w ix.TestArray.SetAt( "Abra"、1)
/// w ix.TestArray.SetAt( "Shvabra"、2)
/// w ix.TestList.InsertAt( "Cadabra"、1)
/// s id = ix。%Id()
/// w ix。%Save()k ix
///
/// //更新してみてください
///
/// s ix = ## class(TestPersHist)。%OpenId(id)
/// s ix.TestVal = $ J
/// w ix.TestArray.SetAt( "Swim"、3)
///
/// w ix。%Save()k ix
///
/// //グローバル^ User.TestPersHistDを見てください。 すべての変更が記録されました!
クラスTestPersHistはPersHistを拡張します [ ClassType = persistent、 ProcedureBlock ]
{
プロパティ TestArray As%Stringの配列 ( MAXLEN = 255 ); // [コレクション=配列];
プロパティ TestList As %% Stringのリスト 。 // [コレクション=リスト];
プロパティ TestVal As%String ( MAXLEN = 255 )[ 必須 ];
}
///
/// //インスタンスを作成して初期化します:
///
/// s ix = ## class(TestPersHist)。%New()
/// s ix.TestVal = $ H
/// w ix.TestArray.SetAt( "Abra"、1)
/// w ix.TestArray.SetAt( "Shvabra"、2)
/// w ix.TestList.InsertAt( "Cadabra"、1)
/// s id = ix。%Id()
/// w ix。%Save()k ix
///
/// //更新してみてください
///
/// s ix = ## class(TestPersHist)。%OpenId(id)
/// s ix.TestVal = $ J
/// w ix.TestArray.SetAt( "Swim"、3)
///
/// w ix。%Save()k ix
///
/// //グローバル^ User.TestPersHistDを見てください。 すべての変更が記録されました!
クラスTestPersHistはPersHistを拡張します [ ClassType = persistent、 ProcedureBlock ]
{
プロパティ TestArray As%Stringの配列 ( MAXLEN = 255 ); // [コレクション=配列];
プロパティ TestList As %% Stringのリスト 。 // [コレクション=リスト];
プロパティ TestVal As%String ( MAXLEN = 255 )[ 必須 ];
}
PersHistクラスに追加されたメソッドについて簡単にコメントします。
クラスメソッドGetDataLocation()-下位クラスの説明を読み取り、ストレージグローバルの名前を返します。
クラスメソッドSaveLastRevision(id)は、クラスの中心的な図です。 良い方法では、プライベートにする必要がありますが、この場合、SQLトリガーから呼び出すことはできません。 このメソッドは、グローバルノード^ ClassData(id)から^ ClassData( "History"、id、idのsaved_version、$ ZTIMESTAMP)にレコードをコピーします。 当然、^ ClassDataの代わりに、GetDataLocation()によって返されるストレージグローバルの実際の名前が使用されます。
プライベートメソッド%OnBeforeSave()-%Save()メソッドがデータベースに既に保存されているレコードの実際の変更を実行する場合にのみSaveLastRevision()を呼び出します。
OnBeforeSaveForSQL()SQLトリガーも同じことを行います。
最後に、MakeActual(タイムスタンプ)メソッドを使用して、$ ZTIMESTAMP形式(より正確には、$ ZU(188)-ローカライズされた$ ZTIMESTAMP)で、特定の時点での現在の状態にデータを復元します。 このメソッドは、指定された時刻またはそれ以降にレコードが正確に更新されたかどうかを確認し、上書きされた場合、レコードの現在のバージョンを保存し、以前に保存されたバージョンを復元します。 例えば
orefを行い ます。 MakeActual (( $ h -7)_ "、" _(12.5 * 3600))
1週間前の12:30の状態にレコードを戻します。もちろん、控訴の時点でこのレコードの状態のコピーを保存します。
このオブジェクトの保存の完全なリストが必要な場合は、次の一連の呼び出しが提案されます。
s ids = "" f s ids = oref 。 OrderIdSave ( ids ) q : ids = "" w ids!
保存されたバージョンのすべての識別子を提供します。 メソッド
orefを 書き ます 。 GetTimeStampByIdSave ( ids )
興味のあるバージョンを保存する正確な時間を返します。 ただし、識別子によって直接復元できます。
orefを行い ます。 MakeActualByIdSave ( ids )
しかし、レコードが変更されたときにすべての変更を保存し、同時にこのレコードが削除された場合に永久に失わせることは非論理的です。 UndeleteId(id)クラスメソッドを使用して削除されたレコードを復元できるprivate%OnDelete()メソッドとOnBeforeDeleteForSQL SQLトリガーは、この迷惑に対して保証されています。
復元するレコードの識別子を計算する方法は簡単な質問であり、最終アプリケーションの開発者の裁量ですでに決定されているようです。 ただし、いずれにしても、削除されたレコードをナビゲートできる必要があります。 また、この機会はOrderDeletedId(id、dir)メソッドによって提供されます。このメソッドは、よく知られている$ ORDER()関数と同様に、削除されたが履歴に保存されたレコードの最も近い次または前(dir = -1)の識別子を返します。
PersHistから継承したデモクラスTestPersHistには、通常のフィールド、リスト、および配列の形式のプロパティのみが含まれています。 このクラスのレコードを任意に作成および変更することをお勧めします(たとえば、クラスのコメントを参照)。その後、ストレージグローバルを直接表示します。 .TestPersHistD(「履歴」)。
CachéにインポートできるフォームのPersHistおよびTestPersHistクラスのコードは、 このリンクからダウンロードできます。Cachéバージョン5.0以前の場合はCDL形式、最新の場合はXML形式です。