分布式數據庫應用開發正解 [系列之一]
Delphi做為一個快速應用開發工具,深受程序員的喜愛。其強大的組件功能,讓程序員能夠輕松、高效地完成常見的界面開發、
數據庫應用等功能。然而,幫助的相對缺乏,使得許多組件的功能並不為人們正確地使用,究其原因,仍然是認識上的問題。
對於MIDAS開發中的核心部件,TClientDataSet和TDataSetProvider,由於資料的缺乏,人們在網上大多談論的是李維的書籍內容。
我有幸在BDN上見到了Cary Jensen的Professional Developer系列文章,詳細闡述了DELPHI的數據庫開發技術。現節選出其中的
ClientDataSet部分,與大家共同分享。
ClientDataSet是一個功能強大的類,通過在內存中模擬表格,實現了其它數據集組件所不具備的強大功能。以往只在Delphi和C++ Builder
企業版中才提供這個組件,如今,Borland的全部產品(包括最新的Kylix)都集成了TClientDataSet組件。
TClientDataSet從類的繼承關系上來看,是TDataSet這個抽象類的子類,所以我們可以在TDataSet這個抽象層次上對其進行我們熟悉的操作,
比如導航、排序、過濾、編輯。要注意的是,TClientDataSet使用了一種全新的技術,它將所有的數據均放在內存中,所以TClientDataSet是個
只存在內存中的“虛擬表”,因此對數據庫的操作是非常快的。在PIII 850,512MB的機器上對十萬條記錄進行建索引的操作,花費的時間少於半分鐘。
與一般的數據集組件不同,TClientDataSet使用的技術比較特別,本著高速度、低存儲需求的原則,TClientDataSet的內部使用了兩個數據存儲源。
第一個是其Data屬性,這是當前內存數據的視圖,反映了所有的數據改變。如果用戶從數據中刪除一條記錄,則此記錄將從Data中消失,相應地,
加入一條新記錄後,此記錄便存在Data屬性中了。
另一個數據源是Delta屬性,故名思義,即增量的意思,這個屬性反映了對數據的改變。無論是向Data屬性新增還是刪除記錄,都會在Delta中記錄下來,
如果是修改了Data中的記錄,則會在Delta保存兩條相應的記錄,一條是原始記錄,另一條僅包含修改的字段值。正因為Delta的存在和TClientDataSet
在內存中記錄數據的特點,所有的改變都沒有立即更新加對應的物理存儲中,可以根據這些信息在適當的時候恢復,所以TClientDataSet天生具有緩沖更新功能。
為了使數據更新回數據存儲源,我們要調用TClientDataSet中對應的方法。如果ClientDataSet與DataSetProvider關聯,那麼僅需調用TClientDataSet的ApplyUpdates
方法即可保存數據的更新,但如果TClientDataSet沒有對應的TDataSetProvider存在,而是直接同文件關聯,那麼,這種方式是非常有趣的,我們在BriefCase模型中會
再次講解這個問題。此時,如果使用TClientDataSet的SaveToFile和LoadFromFile,都會保留著Delta。調用MergeChangeLog和ClearChanges後,Delta的內容才會被
清空。只是前者是將Delta的數據同Data結合起來,將改變存儲到物理介質上,而ClearChanges則是一股腦兒全部清空,將數據回復到原始狀態。
大部分的應用都是將TClientDataSet與TDataSetProvider結合使用的。兩者聯合使用的行為反映了Borland的設計宗旨,就是要提供一個面向分布式環境的思路。我們下面來
慢慢解釋。
當我們將TClientDataSet對象的Active屬性設為True或者調用其Open方法後,ClientDataSet會向DataSetProvider發送一個取數據包請求。於是DataSetProvider便會打開對應的
數據集,將記錄指針指向第一條記錄,然後從頭到尾依次掃描。對於掃描到的每一條記錄,都會將其編碼成一個variant數組,我們通常將它稱之為數據包。完成掃描後,DataSetProvider
會關閉指向的數據集,並將所有的這些數據包傳遞給ClientDataSet。在我提供的演示程序中,你可以清楚地看到這種行為(畢竟眼見為實嗎!)。程序主界面右邊的DBGrid連接到一個指向數據庫表
的數據源,DataSetProvider即指向此表。當選擇了ClientDataSet | Load菜單項時,你可以看到表格的數據被依次掃描,一旦到達最後一條記錄,表格便會被關閉,右邊的DBGrid被清空,而左邊反映
ClientDataSet數據的DBGrid便出顯示出內存中的數據來。由於這個過程會在DBGrid上反映出來,所以不到1000條記錄的取出時間中,大部分都浪費在屏幕的更新顯示上了,你可以選擇ClientDataSet | View Table Loading來禁止
顯示,而達到加速的目的。
在上面的描述中,我們沒有提到一個重要的環節,即數據包是如何還原成表格的。那是因為DataSetProvider會將數據包中的元數據解碼出來,根據元數據(我們可以理解為數據表的結構)便可以構造出與物理數據表一模一樣的
內存虛擬表。但要注意的是,盡管DataSetProvider指向的數據表可能有多個索引,但這些信息是不會放在數據包中的,換句話說,ClientDataSet當中的數據默認情況下是無索引的。但因為ClientDataSet具有與TDataSet一致的行為,
所以我們可以在此基礎上根據需要重建索引。
在ClientDataSet中的數據被修改後,可以提交給物理數據表持久化這此改變。這個工作便是由DataSetProvider完成的。內部工作原理是:DataSetProvider創建一個TSQLResolver的實例,這個實例會生成要在底層數據上執行更改的SQL語句。
詳細地說,就是對修改日志中的每一條被刪除、插入、更改記錄生成對應的SQL語句。這個語句的生成也可以由用戶控制,DataSetProvider的UpdateMode屬性和ClientDataSet中的ProviderFlags屬性都對SQL語句的生成有影響。
當然,你也可以換一種方式,即采取同單機或C/S結構一樣的數據直接操作機制,繞過SQL語句和緩沖更新機制來修改數據庫。只需將ResolveToDataSet屬性設為True,那麼DataSetProvider在持久化更新時便不會使用TSQLResolve,而是直接修改物理數據源。
即定位到要刪除的記錄,調用刪除語句,定位到修改記錄,調用修改語句。我們可以對演示程序稍加修改,觀察此種行為。請將演示程序中的DataSetProvider的ResolveToDataSet屬性由False改為True,運行。在界面中修改數據並且保存,你將會看到右邊的導航按鈕
會在瞬間變得可用。
更絕妙的是,Borland考慮到了應用的多樣性,為我們提供了BeforeUpdateRecord事件,這樣,當DataSetProvider對每個修改日志的記錄進行操作時,都會觸發此事件,我們可以在此事件中加入自己的處理,如“加密操作”、“商業敏感數據處理”等應用,從而極大地方便了程序員,讓程序員對於數據具有完全的控制能力。
分布式環境的復雜性對數據的存取提出了更高的要求,所以使用事務來保證數據的完整性和一致性是非常必要的,Borland考慮到了這一點,當調用ClientDataSet的ApplyUpdates時,你可以傳遞一個整數值來指明可以容忍的錯誤數量。如果你的數據非常嚴格,則可以傳遞0值,這樣,DataSetProvider在應用修改時便會打開一個事務,
如果遇到錯誤,便會回退此事務,修改日志將保持原樣,並且將出錯的記錄標記出來,最後會觸發OnReconcileError事件。
如果傳遞了一個大於0的數,則當出現的錯誤數量小於此指定值時,事務會被提交,發生錯誤而導致提交失敗的記錄會保留在Delta中,而提交成功的記錄會從修改日志中刪除。
若錯誤數量達到指定值,則事務會回退,結果同整數值為0的情況。
如果值為負數,則會交所以可提交的數據都提交,不可提交的數據仍然保存在修改日志中,並將出錯記錄標記出來。
雖然,Borland是為了滿足分布式編程的需要而設計了TClientDataSet,但在其它類型的編程環境中使用ClientDataSet也具有積極的意義。首先,我們可以看到,由於數據均在內存中進行操作,而且僅在打開數據庫取數據時和將修改持久到回數據庫時,才有數據庫開銷,其它時間數據庫為零,這樣就極大地增加了數據庫的負荷,讓數據庫服務器
能滿足更多用戶的連接請求。其次,ClientDataSet具有其它數據集所不具備的許多高級功能,這為程序員進行復雜的編程提供了便利,可以不考慮數據庫本身是否支持這此功能,而讓ClientDataSet去處理這些復雜而繁瑣的細節。
最後,ClientDataSet在數據存儲和應用程序間起到一個抽象層的作用。假如你的程序使用了TClientDataSet,那麼如果你以後要更改數據庫存儲機制。比如說由BDE移植到dbExpress,或者從ADO移植到Interbase Express,你的用戶界面和數據控制部分幾乎就不用改變,只需要將DataSetProvider指向新的數據存取組件即可。順便說一句,由於緩沖更新
的存在,用戶可能非常厭惡調用ApplyUpdates操作,那麼你可以將此調用放入AfterPost和AfterDelte中,讓用戶的操作更方便。
演示程序下載地址為:211.154.143.75/pub/loadbehaviordemo.zip
待續....