Scott W. Ambler
Ronin International 的總裁
2000 年 7 月
| 內容: |
|
|
| 如何映射對象 |
| 將屬性對應成列 |
| 實現繼承 |
| 將類映射成表 |
| 映射策略 |
| 映射關係 |
| 差異 |
| 實現關係 |
| 實現關聯 |
| 結束語 |
| 參考資料 |
| 關於作者 |
|
為什麼對象-關聯式資料庫的映射對於現代開發人員是一件大事呢?一方面,對象技術(例如 Java 技術)是應用於新軟體系統開發的最常見的環境。另外,關聯式資料庫仍然是許多人都青睞的持久資訊儲存方法,並且在較長時間內這種情況不太會改變。請繼續讀下去,瞭解如何使用這種技術。
為什麼要寫有關對象-關聯式資料庫之間的映射的文章呢?因為在對象範例和關係範例之間“阻抗不匹配”。對象範例基於軟體工程的一些原理,例如耦合、彙總和封裝,而關係範例則基於數學原理,特別是集合論的原理。兩種不同的理論基礎導致各自有不同的優缺點。而且,對象範例側重於從包含資料和行為的對象中構建應用程式,而關係範例則主要針對資料的儲存。當為訪問而尋找一種合適的方法時,“阻抗不匹配”就成了主要矛盾:使用對象範例,您是通過它們的關係來訪問對象,而使用關係範例,則通過複製資料來聯結表中的行。這種基本的差異導致兩種範例的結合并不理想,不過話說回來,本來就預料到會有一些問題。使對象-關聯式資料庫之間的映射成功的一個秘訣就是理解這兩種範例和它們的差異,然後基於這些認識來進行明智的取捨。
本文應該能夠消除現今開發週期中一些普遍共有的誤解,對對象-關聯式資料庫之間映射所涉及到的一些問題提供了切合實際的看法。這些策略基於我的開發經驗,專案範圍從小到大,涉及金融、銷售、軍事、遠程通訊和外購等行業。我已對使用 C++、 Smalltalk、Visual Basic 和 Java 語言編寫的應用程式應用了這些原則。
如何將對象映射成關聯式資料庫
在這一節中,我會描述一些將對象成功映射成關聯式資料庫所需的基本技術。
- 將屬性對應成列
- 在關聯式資料庫中實現繼承
- 將類映射成表
- 映射關聯、彙總和組合
- 實現關係
將屬性對應成列
類屬性將映射成關聯式資料庫中的零或幾列。要記住,並不是所有屬性都是持久的。例如, Invoice 類會有 grandTotal 屬性,這個屬性由其執行個體在計算時使用,但它不儲存到資料庫中。而且,某些對象屬性本身就是對象,例如 Course 對象有一個作為屬性的 TextBook 執行個體,它映射為資料庫中的幾列(實際上,很有可能 TextBook 類本身就將映射成一個或多個表)。重要的是,這是一個遞迴定義:有時屬性將映射成零或者多列。也有可能將幾個屬性對應成表中的單一列。例如,代表美國郵遞區號代碼的類可以有三個數字屬性,每個都表示完整郵政編號代碼中的每一部分,而郵政編號代碼可以在地址表中作為單一的列儲存。
在關聯式資料庫中實現繼承
在將對象儲存到關聯式資料庫中時,繼承的概念中發生幾個有趣的問題。(請參閱參考資料中的 "Building Object Applications That Work"。)問題從根本上歸結為解釋如何在您的持久模型中組織繼承的屬性。解決這個難題所用的方法會對系統設計有很大影響。將繼承映射到關聯式資料庫中有三種基本解決辦法,為更好地理解它們,我將討論在圖 1 中顯示的映射類圖表的優缺點。為簡化問題,我沒有為類的所有屬性都建模;也沒有為其完整簽名或任何類方法建模。
圖 1. 簡單類階層的 UML 類
將類映射成表
類到表的映射通常不是直接的。除了非常簡單的資料庫以外,您不會有類到表的一對一映射。在以下章節中,我將討論為關聯式資料庫實現繼承結構的三種策略:
- 整個類階層使用一個資料實體
- 每個具體類使用一個資料實體
- 每個類使用一個資料實體
整個類階層使用一個資料實體
使用這種方法,您可以將一個完整類階層映射成一個資料實體,而階層中所有類的所有屬性都儲存在這個實體中。圖 2 描述了採取這個方法時圖 1 的類階層的持久模型。請注意,為表的主鍵引入了一個 personOID 列 - 我在所有解決方案中都使用 OID (沒有商業含義的標識,又稱替代鍵),只是為了保持一致和使用我所知道的向資料實體分配鍵的最好辦法。
圖 2. 將類階層映射成單一資料實體
這種方法的優點是簡單,因為所需的所有人員資料都可以在一張表中找到,所以在人們更改角色時支援多態性,並且使用這種方法,專門報告(為一小組使用者特定目的所執行的報告,這些使用者通常自己寫報告)也非常簡單。缺點是每次在類階層的任何地方添加一個新屬性時都必須將一個新屬性添加到表中。這增加了類階層中的耦合 - 如果在添加一個屬性時有任何錯誤,除獲得新屬性的類的子類外,還可能影響到階層中的所有類。它還可能浪費資料庫中的許多空間。我還必須添加 objectType 列來表明行代表的是學生、教授還是其它類型的人員。在人們具有單一角色時這種方法很有效,但如果他們有多個角色(例如,一個人既是學生又是教授),很快就會失效。
每個具體類使用一個資料實體
使用這種方法,每個資料實體就既包含屬性又包含它所表示的類繼承的屬性。圖 3 描述了採取這個方法時圖 1 的類階層的持久模型。有與 Student 類對應的和與 Professor 類對應的資料實體,因為它們是具體類,但沒有與 Person 類對應的資料實體,因為它是抽象類別(它的名稱以斜體字表示)。為每個資料實體都分別分配了自己的主鍵, studentOID 和 professorOID。
圖 3. 將每個具體類映射成單個資料實體
這種方法最大的好處是,它仍然能相當容易地執行專門報告,只要您所需的有關單一類的所有資料都只儲存在一張表中。但也有幾個缺點。一個是當修改類時,必須修改它的表和它所有子類的表。例如,如果要向 Person 類添加高度和重量,就需要同時更新兩個表,它會涉及很多工作。第二,無論何時,只要對象更改了它的角色 - 可能您聘用了您一個剛畢業的學生作為教授 - 則需要將資料複製到相應的表中,並為它指定一個新的 OID。這又涉及到很多工作。第三,很難在支援多個角色的同時仍維護資料完整性。(這種情況是可能的;只是比原先困難一點。)例如,您會在哪裡儲存既是學生又是教授的人的姓名呢?
每個類使用一個資料實體
使用這種方法,為每個類建立一張表,它的屬性是 OID 和特定於該類的屬性。圖 4 描述了採取這個方法時圖 1 的類階層的持久模型。 請注意,將 personOID 用作了所有三個資料實體的主鍵。圖 4 的一個有趣的特性是,為 Professor 和 Student 中的 personOID 列都分配了兩個構造型,而這在標準建模語言 (UML) 中是不允許的。我的意見是,這是一個必須由 UML 持久性建模概要解決的問題,甚至可能在這個建模規則中也需要更改。(有關持久性模型的詳細資料,請參閱參考資料中的 "Towards a UML Profile for a Relational Persistence Model"。)
圖 4. 將每個類映射成它自己的資料實體
這種方法的最大好處就是它能夠最好地適應物件導向的概念。它能夠很好地支援多態性,對於對象可能有的每個角色,只需要在相應的表中儲存記錄。修改超類和添加新的子類也非常容易,因為您只需要修改或添加一張表。這種方法也有幾個缺點。第一,資料庫中有大量的表 -- 實際上每類都有一個(加上維護關係的表)。第二,使用這種技術讀取和寫入資料的時間比較長,因為您必須訪問多個表。如果通過將類階層中的每個表放入不同物理磁碟機碟片(假設每個磁碟機磁頭都單獨操作)上來智能地組織資料庫的話,就可以緩解這個問題。第三,有關資料庫的專門報告很困難,除非添加一些視圖來類比所需的表。
比較映射策略
現在,請注意,每個映射策略怎樣產生不同的模型。要理解三種策略之間的設計優缺點,請考慮圖 5 中顯示的對我們的類階層做些簡單的更改:添加了 TenuredProfessor,這是從 Professor 中繼承的。
圖 5. 擴充初始類階層
圖 6 顯示了一個更新過的持久性模型,用於將整個類階層映射成一個資料實體。儘管很明顯,資料庫中的空間浪費增加了,但請注意,按照這種策略操作,只需花非常小的代價就可以更新模型。
圖 6. 將擴充的階層映射成單一資料實體
圖 7 顯示了將每個具體類映射成資料實體時的持久性模型。使用這個策略,雖然因為我們從教授提升到終身教授,這樣對象和我們的關係就有了改變(學生變成教授),所以如何處理對象的這個問題更複雜了,但我只需要添加一個新表。
圖 7. 將擴充的階層的具體類映射成資料實體
圖 8 顯示了第三種映射策略的解決方案 -- 將單個類映射成單個資料實體。這需要我添加一個只包括 TenuredProfessor 類的新屬性的新表。這種方法的缺點是,要使用新類的執行個體,它需要好幾個資料庫訪問。
圖 8. 將擴充的階層的所有類映射成資料實體
要摒棄這樣一種觀點,即這些辦法都不夠好;每種辦法都有其優缺點。在下面的表 1 中對它們進行比較。
表 1. 比較映射繼承的各種辦法
| 考慮因素 |
單表 |
每個具體類一張表 |
每個類一張表 |
| 專門報告 |
容易 |
中等 |
中等/困難 |
| 實現的難易程度 |
容易 |
中等 |
困難 |
| 資料訪問的難易程度 |
容易 |
容易 |
中等/容易 |
| 耦合 |
非常高 |
高 |
低 |
| 資料訪問速度 |
快 |
快 |
中等/快 |
| 對多態性的支援 |
中等 |
低 |
高 |
映射關聯、彙總和組成
不僅必須將對象映射到資料庫中,還必須將對象之間的關係進行映射,這樣才能在以後進行恢複。對象之間有四種類型的關係:繼承、關聯、彙總和組成。要有效地映射這些關係,必須理解它們之間的差異、如何?一般的關係,以及如何?特定的多對多關係。
關聯、彙總和組合之間的差異
從資料庫的角度看,關聯和彙總/組合關係之間的唯一不同是對象相互之間的綁定程度。對於彙總和組合,在資料庫中對整體所做的操作通常需要同時對部分進行操作,而關聯就不是這樣。
在圖 9 中有三個類,其中兩個在它們之間有簡單的關聯關係,有兩個共用彙總關係(實際上,組合可能是這種模型中更確切的說法)。(有關關係的詳細資料,請參閱參考資料中的 "Building Object Applications That Work"。)從資料庫的觀點看,彙總/組合和關聯是不同的,在彙總情況下,在整體中讀取時,您通常希望在部分中讀取,而在關聯情況下,需要執行什麼操作並不總是那麼明顯。在將對象儲存到資料庫中或從資料庫中刪除對象也存在相同的情況。當然,上述討論通常特定於商業領域,但這種經驗之談往往在很多情況下出現。
圖 9. 關聯和彙總/組合之間的差異
在關聯式資料庫中實現關係
關聯式資料庫中的關係是通過使用外鍵來維護的。外鍵是在一張表中出現的一個或多個資料屬性;它可以是另一張表的鍵的一部分,或者乾脆碰巧就是另一張表的鍵。外鍵可以讓您將一張表中的一行與另一張表中的一行相關起來。要實現一對一和一對多的關係,您只需要將一張表的鍵包括在另一張表中。
在圖 10 中有三張表,它們的鍵 (OID) 和外鍵用於在它們之間實現關係。首先,在 Position 和 Employee 資料實體間有一個一對一的關聯。一對一關聯就是它的每個複合度的最大值都是 1 的這麼一種關係。要實現這個關係,我在 Employee 資料實體中使用屬性 positionOID,Position 資料實體的鍵。因為關聯是單向的 -- employee 那些行知道它們的位置行,但反過來就不行 -- 所以我必須這麼做。如果這是個雙向的關聯,我還會在 Position 中添加一個名為 employeeOID 的外鍵。然後,使用相同的方法在 Employee 和 Task 之間實現了多對一關聯(又稱為一對多關聯),唯一的不同是將外鍵放在了 Task 中,因為它在關係的“多”方。
圖 10. 簡單人力資源資料庫的持久性模型。
實現多對多關聯
要實現多對多關係,需要關聯表的概念,它是一種資料實體,唯一目標是在關聯式資料庫中維護兩個或多個表之間的關聯。圖 10 中,在Employee 和 Benefit 之間有一個多對多關係。圖 11 中,可以看到如何使用關聯表來實現多對多關係。在關聯式資料庫中,關聯表中包含的屬性傳統上是關係中涉及到的表中的鍵組合。關聯表的名稱通常是它所關聯的表的名稱組合,或者是它實現的關聯的名稱。在這種情況下,我選擇 EmployeeBenefit 而不是 BenefitEmployee 和 has,因為我覺得它可以更好地反映關聯的性質。
圖 11. 在關聯式資料庫中實現多對多關係
看一 11 中應用程式的複合度。規則是,一旦引入了關聯表,複合度就“交叉”, 12 所示。值為 '1' 的複合度總在外邊緣引入, 11 和 12 中所示,以保留原始關聯的整體複合度。原始的關聯表明僱員有一種或多種福利,並且任何給定的福利都給予一個或多個僱員。在圖 11 中您可以看到,即使在有關聯表維護關聯的情況下仍然是這種情況。
圖 12. 關聯表簡介
有必要註明我選擇應用構造型“<<關聯表>>”而不是關聯類別的說明 -- 將關聯類別與它所描述的關聯串連的虛線行 -- 出於兩個原因。首先,關聯表的目的是實現關聯,而關聯類別的目的是描述關聯。其次,圖 11 中採取的方法反映了為使用關係技術所需的實際實現策略。
結束語
在本文中,探索了對象-關聯式資料庫之間的映射的基礎。如果按照本文中描述的步驟操作,就可能方便地將對象成功儲存在關聯式資料庫中。如果有任何問題,請發電子郵件給我,地址是 scott.ambler@ronin-intl.com,如果有興趣對持久性模型的 UML 概要提供建議,請放在關於持久性模型概要開發的工作頁面上就可以了。
參考資料
- Building Object Applications That Work: Your Step-By-Step Handbook for Developing Robust Systems with Object Technology by Scott W. Ambler (New York:SIGS Books/Cambridge University Press)
- Process Patterns: Building Large-Scale Systems Using Object Technology by Scott W. Ambler (New York:SIGS Books/Cambridge University Press)
- The Object Primer 2nd Edition -- The Application Developer's Guide to Object-Orientation by Scott W. Ambler (New York:Cambridge University Press)
- Scott W. Ambler 的過程模式資源頁面
- Scott W. Ambler 的“增強標準流程”
- Scott W. Ambler 的 Towards a UML Profile for a Relational Persistence Model: Working Page。
關於作者
Scott W. Ambler 是 Ronin International 的總裁,這家公司是專門研究物件導向的軟體過程教學、體繫結構建模和 Enterprise JavaBeans (EJB) 開發的諮詢企業。他創作或與他人合著了幾本有關物件導向開發的書籍,包括最近發行的 The Object Primer 2nd Edition,該書詳細介紹了本文所概述的主題。可以通過 scott.ambler@ronin-intl.com 與他聯絡。