|
本文的目的是以最精煉的語言,理解什么是O/R Mapping,為什么要O/R Mapping,和如何進行O/R Mapping。
什么是O/R Mapping?
廣義上,ORM指的是面向對象的對象模型和關系型數據庫的數據結構之間的相互轉換。
狹義上,ORM可以被認為是,基于關系型數據庫的數據存儲,實現一個虛擬的面向對象的數據訪問接口。理想情況下,基于這樣一個面向對象的接口,持久化一個OO對象應該不需要要了解任何關系型數據庫存儲數據的實現細節。
為什么需要O/R Mapping?
廣義上,因為我們需要面向對象來描述我們的業務,我們也需要關系型數據庫來存儲我們的數據。
有人可能會提到,我們未必要用面向對象來描述業務,或者未必用關系型數據庫來存儲數據。沒錯,但是,至少,還是有相當大部分的程序,同時需要這兩者的合作。存在即合理。因為同時需要彼此,兩者都客觀存在,就值得討論。
既然從廣義上,存在即合理,無需討論為什么需要ORM,很多關于ORM的討論,其實都是針對上面提到的狹義的定義。但是即使到目前為止,真正能夠完美的實現這個狹義定義的ORM的工具,其實還并不存在(很多工具如Hibernate,已經相當接近,但是,離完美還有相當距離)。
既然還不存在,那么,在討論需不需要之前,我們恐怕要先討論一下,是否可能,即從理論上,從數學上,面向對象的對象模型和關系型數據庫的數據結構之間,到底能不能做到完美的映射?要回答這個問題,我們要解決兩個難題。
O/R阻抗失衡
第一個難題,叫“O/R阻抗失衡(O/R Impedance Mismatch)”,指的是OO對象模型和關系型數據庫的數據結構之間的,設計理念上的差異。OO的設計理念,是以最正確的語義來描述真實世界;而關系型數據庫的設計理念,則是從數學的角度,如何更有效的存儲和管理數據。由于兩者設計理念的差異,導致它們盡管從數據結構上,可能很相近,但是關注點往往是不同的。
例如:
- 從OO的角度,凡是語義明確,每一個對象語義上的屬性,就應該定義為一個屬性;從關系型數據庫的角度,有可能考慮到一些屬性從來不會被作為查詢條件,而把多個語義上的屬性,以一定的格式,存在一個數據表的字段中,也有可能因為一組語義上的屬性使用的頻度完全不同與另一組屬性,即使他們語義上屬于一個對象,也有可能將他們拆分成兩個數據表來存儲。
- 從OO的角度,對象只關心自己的固有屬性,不需要被唯一標識;但是,從關系型數據庫的角度,一般一個數據表中的每一行數據都需要要一個唯一標識,且很可能是語義上沒有意義的,如自增長的標識。
- 從OO角度的優化,一般遵循SOLID這樣的原則,以更正確的語義來組織對象;從關系型數據庫角度的優化,往往為了查詢性能,來修改字段的類型、長度,修改索引,甚至分表、分庫。
- ...
一個ORM工具想要通過簡單的配置,完美的解決“O/R阻抗失衡”問題,在我看來幾乎是不可能的。但是,一定程度上,通過靈活的配置支持絕大多數常見的映射策略,再加上提供可供用戶通過自定代碼來擴展的接口的話,應該還是可以相當接近完美的。
文化阻抗失衡
第二個難題,叫“文化阻抗失衡(Cultural Impedance Mismatch)”,指的是關系型數據專家和面向對象專家之間的文化差異。關于這個難題的最經典的爭論是“到底應該以關系型數據庫的數據結構來驅動,還是以OO的對象模型來驅動程序的開發?”。
關于這個爭論,OO專家的主要觀點是:
我的業務才是程序的核心,數據庫只是為我持久化數據的需求服務的,所以設計OO對象模型的時候,我不應該考慮如何存儲到關系型數據庫的問題;
而數據庫專家的主要觀點是:
數據才是公司的核心財富,數據的穩定性遠遠強于OO對象模型的穩定性,由OO對像模型來驅動數據庫架構的設計,根本保證不了性能,數據庫維護的成本根本不可接受。
其實,爭論的真正原因即在于“關系型數據專家和面向對象專家之間的文化差異”。大大牛Scott W. Ambler的文章Why Data Models Shouldn't Drive Object Models(And Vice Versa),很好的解答了這個問題。
歸根結底,既然OO和關系型數據庫都是不可缺少的部分,需要協同工作,希望做到完美的ORM,不僅僅需要好的ORM工具,更需要無論是OO專家還是數據庫專家互相了解對方的技術和設計理念。對一個OO專家,一個ORM工具,可以對其所支持的常見的映射,提供直接的支持,但是,應用這些ORM工具的OO專家,必須了解關系型數據庫,知道如何在不影響OO對象的語義的前提下,如何設計一個更容易映射到關系型數據庫的對象模型,知道如何選擇ORM工具所支持的映射方式,以及如何用自定代碼擴展ORM所不能方便支持的映射方式;同樣的,對一個關系型數據庫的專家,也需要了解OO的語義,和可能發生的重構,從而,使得所設計的數據庫結構,更容易映射到OO對象,更容易響應OO對象模型的重構。如此才是真正的和諧,真正的完美。
小結
綜上所述,無論對一個OO專家還是對一個關系型數據庫專家,都需要了解OO,并了解關系型數據庫,了解ORM的基本原理。離開全面的知識,一個再完美的ORM工具也無法被正確使用,擁有這些知識,則能夠充分利用已有的ORM工具來加速自己的工作,并且合理的或擴展現有的ORM工具,或用自定代碼,實現ORM工具能力之外的ORM映射。這才是理想的ORM實踐。下一章節,我們就來談談一下,ORM的基本原理。
如何進行O/R Mapping?
簡單映射
1. Class <-> Table
一個Class一般可以映射為一個Table,一個Class的實例對應Table的一行數據。但是,一個Table中的每行數據,一般都需要有一個主鍵來唯一標識這行數據,而一個Class的每個實例,則不一定需要一個唯一標識。
2. Property <-> Field
一個Class的Property一般可以直接映射為Table的一個Field。但是,他們的數據類型不一定直接匹配。如果他們代表的數據類型的語義上可轉換,則Field的類型,應大于等于Property的數據類型。如果他們代表的類型語義上不可轉換,則需要在應用程序層面,進行自定義的轉換。
繼承映射
1. 單表映射整個繼承體系
用一張數據庫表存儲整個繼承體系中的所有Class的數據,數據表需要額外的標志字段來區分一行記錄應該映射到繼承體系中的哪一個Class,適合繼承體系層次較少,總記錄數相對較少,子類對父類的屬性擴展也相對不那么頻繁的情形。
單表映射整個繼承體系的優點是讀/寫繼承體系中的每個Class的數據,都只需操作一張表,性能較好,并且,新增繼承類,或擴展Class屬性都只需要增減一張表的字段就可以了,易于維護;主要缺點是,因為繼承體系中所有的Class共享一張表,表中會有比較多的NULL字段值的數據,浪費了一些存儲空間,同時,如果記錄數過多,表就會更龐大,也會影響表的讀寫性能。
2. 一個Class映射一個具體表
所謂一個Class映射一個具體表就是每個Class對應一張數據表,并且,每個數據表冗余包含其父類的所有屬性字段,并且,子類和父類共享相同的主鍵值。一個Class一個具體表方案適合需要較高查詢性能,繼承體系層次不太復雜,并且基類包含較少的屬性而子類擴展較多屬性,并且能夠承受一定的數據庫冗余的情況。
一個Class映射一個具體表方案的優點主要就是查詢性能好,讀操作只需操作一張表,和實體數據的對應結構清晰,數據庫表遷移和維護會比較方便;主要的缺點是數據冗余較大,因為每次插入一條子類數據時,同時要插入一份子類包含的父類字段的數據到所有父類層次表中。
3. 一個Class映射一個擴展表
所謂一個Class映射一個擴展表是指繼承體系中的每個Class對應一張數據表,但是,每個子類不冗余包含父類的所有屬性,而只是包含擴展的屬性和共享的主鍵值。一個Class映射一個擴展表方案適合繼承體系非常復雜,結構易變,并希望最大程度減少數據冗余的情形。
一個Class映射一個擴展表方案的優點是結構靈活,新增子類或插入中間的繼承類都很方便,冗余數據最少;但是缺點是,無論讀還是寫操作都會涉及到子類和所有的父類。讀操作時,必須自然鏈接查詢所有的父類對應的數據表,而插入或更新數據時,也需要寫所有的父類表。
4. 通用的表結構映射所有的Class
這種方案其實不僅支持用一張表存儲一個繼承體系,它甚至可以支持,用一張表存儲任意數量的不同Class。它的原理是元數據驅動。這張表的每一行,包含一個類型的標識字段,一個表示Class的屬性名稱的字段和一個表示Class的屬性值的字段。在運行時,通過唯一標識取出描述一個Class實例的所有Property的值,再根據Property的名稱來映射。
關于各種繼承映射的實例分析和詳細的優劣比較,請參見:Scott W. Ambler的O/R Mapping In Detail。
關聯映射
1. 一對一關聯、一對多關聯(包含一對一和一對多的自關聯)
所謂一對一關聯,實際上還可以分為三種情形,即0..1 - 1,1 – 1,1 – 0..1三種情形;而一對多關聯則分為* - 1和1 - *。
以下三種方案中第1)種為最常用的映射方案,后面幾種是在某些特殊情形下可參考的方案:
1) 最常用的方案為為需要其他對象引用的類對應的表增加一個到被引用對象對應表的外鍵即可,只不過,與表對應的實體類代碼中,對于一對多情形下的“多”這一端,需定義成集合類型;
2) 在Hibernate稱為“組件(Component)映射”,舉例來說,假如Person類包含一個Address成員類型的屬性,而Address由City,Street,ZipCode三個成員屬性組成,假如Address除了與Person關聯不被其他對象使用,則我們可以考慮只用一張數據表Person來持久化Person和Address這兩張表,Person數據表包含Person類中除Address的屬性和Address類中的所有屬性的集合,當然,這時需要在元數據中特別指明映射關系;
3) 還有一種針對上面的方案中的Person,Address兩個類的持久化方案則是將Address類型的所有屬性先序列化,再存入Person表的字段Address中,這樣也可以只用一張表來持久化兩個類,當然,本方案中這種被序列化對象成員數據量應盡量小;
4) 還有一種方案是共享同一主鍵值的一對一關聯。即將原本可以同屬于一個表中相對使用不太頻繁的字段提出來放在另一張表中,這樣,這兩張表的記錄就可以通過一個相同的主鍵進行關聯。
2. 多對多關聯(包含多對多的自關聯)
所謂多對多關聯自然就是* - *這種情形了。一般都需要一張包含關聯雙方主鍵的關聯表,在取數據時,需要鏈接該關聯表和數據表。
英文資料
- http://en.wikipedia.org/wiki/Object-relational_mapping
- http://www.agiledata.org/essays/impedanceMismatch.html
- http://www.agiledata.org/essays/culturalImpedanceMismatch.html
- http://www.agiledata.org/essays/drivingForces.html
- http://www.agiledata.org/essays/mappingObjects.html
it知識庫:理解O/R Mapping,轉載需保留來源!
鄭重聲明:本文版權歸原作者所有,轉載文章僅為傳播更多信息之目的,如作者信息標記有誤,請第一時間聯系我們修改或刪除,多謝。