簡介
使用關係數據庫的面向對象系統開發人員通常要花費大量的時間來將對象持久化,這是因爲在兩種技術間存在一個基本的阻抗不匹配。對象由數據和行爲組成,通常可以繼承,而關係數據庫包括表、關係和基本的謂詞計算函數,這個函數用以返回想要的值。
爲避免對象和關係之間的阻抗不匹配,一種方法是使用一個面向對象的數據。然而,系統通常需要將對象存入一個關係型數據庫,有的因爲一個系統需要關係型理論或關係型數據庫的成熟性,有的因爲公司策略就是使用關係型數據庫而非面向對象數據庫。無論是什麼原因,一個將對象存入關係型數據庫的系統需要提供一個減少這個阻抗不匹配的設計。
本文只描述了將對象映射到關係上的部分模式語言,但是它描述了我們認爲在其他地方沒有描述充分的模式。全部模式的概要可以參見[Keller 98-2],其中闡述得較好的模式是關於關係型數據庫設計和優化的[Brown 96][Keller 97-1,97-2,98-1]。Serializer模式[Riehle et al. 1998]描述瞭如何串行化對象,讓它們可以向不同後端存儲和獲取,例如文本文件、關係數據庫和RPC緩衝。
我們曾使用或研究過若干持久對象系統(GemStone[GemStone 96], TopLink[TopLink 97-1,07-2]和ObjectLens[OS 95])。另外,我們用VisaulAge for Smalltalk爲Illinois Department of Public Health(IDPH)實現了一個簡單得持久化框架,這裏介紹的模式存在於所有這些系統中。商業系統在這些模式上的使用通常比我們的框架更加徹底,我們曾更想購買一個持久化框架,但是我們的預算無法負擔它們。我們這些需要使用持久框架的應用系統很簡單,涉及到幾十個數據表,每個應用管理一個病人的病歷信息,所有應用共享病人統計信息,例如病人姓名、地址和醫院、醫生的信息,而每個應用各自負責某個領域,例如病人的免疫、血液檢查等。雖然一個應用能夠管理一個病人的大量信息,在某個時刻,它將只檢查一個病人。本文的示例將向您展示在爲IDPH開發的應用中,如何協同使用這些模式,解決持久化Name和Address對象的問題。
這些模式串在一起,手拉手地工作,來解決上面提到的阻抗不匹配問題。一個持久層將開發人員和實現持久化的細節隔離開,並保護開發人員不爲變更所困。持久層是構建一個層的特例,保護您遠離應用程序和數據庫的變更。實現持久層的方式之一是通過一個PersistentObject,另一個方法是通過一箇中間人(Broker)[BMRSS 96]。
向數據庫讀取和寫入需要基本的創建、讀取、更新和刪除操作,雖然每個對象能夠有它們自己的訪問數據庫接口,但是如果您的系統向持久層提供一組共通操作,那麼所有的對象都可以使用,這樣的系統就會更易使用和維護。不管您採取哪種實現持久層的方式,都需要支持CRUD(創建、讀取、更新和刪除)操作。
CRUD操作最終會使用SQL代碼訪問數據庫,有某種SQL代碼描述來構建實際的數據庫SQL調用是很重要的。
當從數據庫取出值或者將值存回數據庫,系統必須進行屬性映射來映射數據庫字段值和存儲在對象屬性中的值。阻抗不匹配的一部分就是關係型和對象系統有着不同類型的數據,映射對象和數據庫的值需要轉換兩種技術中值的類型。
當對象屬性發生改變後,將它們存入數據庫是很重要的,因此,任何持久對象系統都應該使用某種變更管理器來跟蹤哪些對象發生了變化,使得系統能夠跟蹤到哪些對象已經被改變,以確保根據需要保存。變更管理器同時也有助於減少數據庫訪問,因爲它只爲改變過的對象創建事務並保存。
因爲在面向對象系統中每個對象都是唯一的,通過一個OID管理器爲每個新對象創建一個唯一標識就很重要。同時,支持事務也是非常重要的,它確保改變一個對象是一個原子操作,可以通過一個事務管理器回滾該操作。任何訪問一個RDBMS的系統將通過某種聯接管理器提供對目標數據庫的聯接。通過一個表管理器處理數據庫表名、字段名也是非常有益的。
表1中的模式目錄概括了本文中討論的模式,它列出了每個模式的名稱,以及它所解決問題的簡要描述。
模式名稱
| 描述
|
持久層
| 提供一個層,將您的對象映射到關係數據庫或其他數據庫上
|
CRUD
| 所有持久對象至少需要的創建、讀取、更新和刪除操作。
|
SQL代碼描述
| 定義實際的SQL代碼,從關係數據庫和其他數據庫中取得值,被對象所用,反之亦然。它被用來生成執行CRUD操作的SQL代碼。
|
屬性映射方法
| 映射數據庫值和對象屬性值,這個模式也處理複雜對象的映射,根據數據庫表的一條數據行產生對象。
|
類型轉換
| 和屬性映射方法一起使用,在數據庫類型和對象類型之間轉換,確保數據完整性。
|
變更管理器
| 爲維護數據完整性,跟蹤對象值的變化情況,由它決定是否需要寫入到數據庫中。
|
OID管理器
| 在插入時爲對象產生唯一的對象ID。
|
事務管理器
| 當保存對象時提供事務處理機制。
|
聯接管理器
| 得到並維護數據庫聯接。
|
表管理器
| 管理一個對象和數據庫表、字段的映射
|
表1 - 模式目錄
持久層
別名:
關係數據庫訪問層
動機:
如果您構建一個大型的面向對象業務系統,而將對象保存到關係型數據庫中,您可能要花很多時間去處理如何使對象持久化。如果您不夠仔細,開發系統的每個程序員都不得不瞭解SQL代碼以及訪問庫的代碼,從而被數據庫所約束。將您的系統從Microsoft Access轉到DB2上會有大量的工作,乃至爲一個對象添加很多變量。所以您需要將您的領域知識從對象是如何存儲的知識中分離開,保護開發人員不會爲這些變化所困。
問題:
如何將對象保存到一個非面向對象的存儲機制中?例如關係型數據庫,而開發人員不必知道實際的實現方式。
特定約束:
? 對熟悉數據庫的開發人員來說,寫SQL語句很容易;
? 設計一個優秀的持久化機制需要花費時間,但是它不直接給用戶提供什麼功能;
? 數據庫訪問代價不菲,通常需要優化;
? 開發人員應該可以不必擔心如何在數據庫中存取而專注於解決應用系統的業務域問題;
? 有一個使用模板方法的共通接口,使代碼更易重用;
? 使用一個單一的接口將強制所有的類擁有最低程度的共同性;
? 在應用系統的生命週期中,持久存儲類型有可能會改變;
? 在應用系統的生命週期中,業務模型有可能經常會變;
解決方案:
提供一個持久層,可以從一個數據存儲源中生成對象,並可以把數據保存到數據存儲源中去。這一層向開發人員隱藏了對象存儲的細節,這實際上是構建一個層(Layer)[BMRSS96]的特殊情況,使您自己免於變化之苦。所有持久對象都使用持久層的標準接口,如果數據存儲機制改變了,只有持久層需要改變。例如,公司主管在項目開始使用Oracle,到項目中期又轉到DB2上。
系統需要知道如何存儲和裝載每個對象,有時候一個對象存儲在多個媒介上的多個數據庫中。一個對象作爲另一個更復雜對象的部分,需要跟蹤它是哪個對象的一部分,這叫做所有者對象。這個所有者對象的概念使編寫複雜查詢變的很容易,因此,持久層爲每個對象和它的父對象提供唯一標識是很重要的,這個唯一標識符和父標識符在實現Proxy模式時非常有用,可以作爲部件對象的佔位符。
總結一下模型名稱的使用,持久層提供必要的方法,通過構造SQL代碼提供CRUD操作,提供屬性映射方法,爲對象數據值進行類型轉化,訪問表管理器,提供對事務管理器的訪問,通過聯接管理器聯接到數據庫。同時持久層也有助於提供適當的變更管理,並和OID管理器協作提供唯一的對象標識。
有很多方法可以實現一個持久層,這兒列舉一二。
1. 使用一個對象層[Keller 98]。每個域對象從一個抽象的PersisentObject類繼承,知道如何執行必要的CRUD操作。本文示例使用的就是這種方式,它的主要好處是易於實現。雖然它在每個域類中都要寫一些數據庫相關的代碼,不過這些代碼是分開的,易於查找和修改,它可以在必要時進行優化,儘管一個過於優化的系統難以理解。
2. 使用一箇中間人,它可以從數據庫讀取域對象或將對象寫入數據庫,中間人必須知道每個域對象的格式,生成SQL語句去讀寫。這種方式將數據庫代碼和域對象類分離開來,是一種最具伸縮性的解決方案,不過需要很多基礎部件。
3. 用一組數據對象組成一個域對象,這些數據對象和數據庫表具有一對一關係。這樣,一旦一個域對象改變了,改變相應的數據對象,並且在域對象保存時,他們也將被保存。例如,一個Patient的值可以映射到Name和Address數據表,Patient對象將擁有映射到Name和Address的數據對象。VisualWorks中,ObjectShare的ObjectLens就是採用這種方式構造數據庫對象。持久層通過這些數據對象管理起來,它很容易實現並易於理解,儘管有些慢,並且開發人員必須要維護數據庫表的一一映射關係。
注:主要的決策依據應該看對靈活性、伸縮性和可維護性的要求。
實現舉例:
現在很多關於正確描述構建中間人[BMRSS96]的細節工作已經完成,而且我們擁有更多實現層對象的經驗,因此,我們的例子將主要關注於這個模式的實現。其他在實現中間人中描述的模式在我們討論中也將簡短提到。我們所有的示例代碼都將描述基於PersistentObject的實現。
圖1是一個UML類圖,表示一個持久層將域對象映射到關係數據庫的實現。請注意在這個例子中,需要被持久化的域對象是PersistentObject的子類,PersistentObject爲持久層提供接口。PersistentObject和表管理器交互,可以爲SQL代碼提供物理表名,在SQL代碼生成時,PersistentObject和聯接管理器交互以提供必要的數據庫聯接。如果需要,當需要一個新的唯一標識時,向OID管理器請求。這樣,PersistentObject作爲一箇中間的集線器,爲域對象提供需要而它本身沒有的任何信息。PersistentObject爲持久層提供標準的接口,通過和其他模式一起合作,一旦SQL代碼準備好了,SQL語句將被數據庫部件觸發,在IBM VisualAge for Smalltalk中,這些就是AbtDBM*應用系統。
PersistentObject的屬性如下:
? objectIdentifier - 對象唯一的標識符,可以是數據庫鍵值。
? isChanged - 標誌對象是否被修改過,告訴持久層這個對象是否要寫入數據庫中。
? isPersisted - 標誌對象是否曾寫入到數據庫中。
? owningObject - 標識父對象,在數據庫中作爲一個外鍵使用。注意這個外鍵在本對象而不是父對象中。
PersistentObject的公共方法如下:
? save - 將對象數據寫入到數據庫中,它將更新或插入行;
? delete - 從數據庫中刪除一個對象的數據;
? load - 從數據庫中返回一個類的單一實例及它的數據;
? loadAll - 從數據庫返回一個類的實例集合,包含所有數據,這對爲選擇列表返回數據非常有用;
? loadAllLike - 從數據庫返回一個類的實例集合,包含部分數據;
圖1 - 持久類類圖
數據庫記錄可以以三種方式讀取:
? 讀取一行(PersistentObject>>load:);
? 讀取所有記錄(PersistentObject>>loadAll);
? 讀取所有符合條件的記錄(PersistentObject>>loadAllLike)
指定一個特定條件,創建一個對象的新實例併爲它裝載對應的屬性集,這個功能可以通過PersistentObject>>load:和PersistentObject>>loadAllLike:方法實現。而當您想產生一個選擇列表或是下拉列表時,PersistentObject>>loadAll方法是非常有用的。
下面的示例代碼描述了上面所說的PersistentObject,它們是公共的接口方法,支持事務管理(後文詳述)。read:和saveAsTransaction方法將在CRUD模式中詳述。
Protocol for Public Interface PersistentObject (instance)
load
“得到匹配它自己的PersistentObject子類的單一實例”
| oc |
oc := self loadAllLike.
^oc isEmpty ifTrue: [nil] ifFalse: [oc first]
loadAllLike
“得到匹配它自己的PersistentObject子類的實例集合,selectionClause方法爲PersistentObject的read方法準備Where子句”
^self calss read: ( self selectionClause )
save
“保存他自己到數據庫中,包含在一個事務當中。”
self class beginTrasacction.
self saveAsTransaction.
self class endTransaction.
delete
“從數據庫中刪除他自己,包含在一個事務當中。”
self class beginTrasaction.
self deleteAsTransaction.
self class endTransaction.
Protocol for Public Interface PersistentObject (class)
loadAll
“從數據庫返回我的所有實例”
^self read: nil.
下面是Name類的示例代碼,Name有Address,因此它將有一個部件,需要被存儲,這個方法被任何需要被存儲而擁有持久化部件的域對象重載。
Protocol for Private Interface Name (instance)
saveComponentIfDirty
“驗證address對象的存在,並驗證proxy模式沒有佔據這個位置,address所有者對象被設爲當前對象,並且address對象的保存也是當前事務中的一部分。”
(self address isNil or: [self address isKindOf:
PPLAbstractProxy])
ifTrue: [^nil].
self address owningObject: self objectIdentitier.
self address saveAsTransaction
結論:
? 把應用開發人員從對象存儲細節中隔離開來的另一個好處是易於實現域對象,因此,域模型變更的工作就變少了。總之通過封裝對象持久機制的功能,可以有效地對開發人員隱藏對象存儲的細節。
? 可以改變數據庫技術,而不影響您的應用程序代碼。
? 使改變對象存儲到數據庫的方式變的很容易,因爲我們已將需要改變的地方隔離起來了。
? 用戶只需要調用同樣的接口去持久化對象,開發人員無需檢測一條記錄是否已經存在於數據庫中。
? 用SQL代碼實現起來很簡單的事情,用持久層可能使它變的複雜而且有時候難以操作。
? 持久層的優化很困難。程序員應實現對不同方式做評測來決定哪一個更適合他的實現。
相關或交互的模式:
? 關係數據庫訪問層是一個很相似的模式,它描述了一個需要持久化的對象交互的層。
? 分層架構[Shaw 96]描述了必要的模式,將體系架構分層,以便在開發中隔離變更。
? 信息系統的分層架構討論了開發分層系統的實現細節。
? 層[BMRSS 96]描述了分層系統架構和設計時,需要考慮的細節。
已知應用:
? GemStone OODBMS使用持久層隱藏一個值是一個持久對象的事實。在實際需要時使用代理爲持久層獲取值,在這個例子中,存儲系統不是關係型數據庫。
? Caterpillar/NCSA金融模型框架[Yoder 97]使用持久層,所有的值都通過一個Query對象存儲。這個例子中,應用程序在數據庫中不存儲任意域對象,只是獲取事務。然而,持久層仍隱藏了數據庫和關係型數據庫技術的細節。
? ObjectShare VisualWorks Smalltalk ObjectLens[OS 95]使用持久層映射數據對象和數據庫表。
? VisualAge for Smalltalk也使用持久層和它們的AbtDbm*應用。VisualAge提供圖形化聯接的GUI構建器,形成一個持久機制。
? Illinois Department of Public Health’s TOTS和NewBorn Screening項目使用一個和本節例子非常相似的實現方法。
? TopLink、MicroDoc、Sparky和Object Extencer[MicroDoc 98, Sparky 98, OE 98]都提供一個持久層來將對象映射到關係型數據庫中。
? PLoP登記系統實現了一個持久層,將Java對象存到PostGress數據庫中[JOE PUT THE REF HERE]。
CRUD
別名:
創建、讀取、更新和刪除
基本持久操作