詳解GraphDatabase在關(guān)系數(shù)據(jù)庫中實(shí)現(xiàn)
1.前言
近幾年,隨著WEB的發(fā)展,大家意識到傳統(tǒng)關(guān)系數(shù)據(jù)庫的不足,于是各種適用于WEB應(yīng)用的非關(guān)系型數(shù)據(jù)就應(yīng)運(yùn)而生了.如 Cassandra, MongoDB等.也就是所謂的NOSQL數(shù)據(jù)庫.
而另一方面程序員期望的面向?qū)ο笮蛿?shù)據(jù)庫,卻還遠(yuǎn)不成熟,遲遲未能出現(xiàn)一件像樣的產(chǎn)品,原因各種各樣,但***的問題可能是難度太大,其實(shí)面向?qū)ο笮蛿?shù)據(jù)庫差不多伴隨面向?qū)ο笳Z言的發(fā)展,在很早就已經(jīng)出現(xiàn)了.但它有太多的晦澀和局限之處,有興趣大家可以去研究一下,但是它在第二輪數(shù)據(jù)庫大戰(zhàn)中輸給了關(guān)系數(shù)據(jù)庫。
傳統(tǒng)的關(guān)系型數(shù)據(jù)庫作為數(shù)據(jù)存儲的***工具地位在短期內(nèi)無法撼動。以這種數(shù)據(jù)庫為基礎(chǔ)發(fā)展起來的工具非常之多,歷史也非常久遠(yuǎn)。而且關(guān)系數(shù)據(jù)庫也嘗試加入一些面向?qū)ο蟮奶匦?如音頻、圖像等新數(shù)據(jù)類型、自定義數(shù)據(jù)類型以及重載運(yùn)算符等等。而Postgresql在面向?qū)ο蠓矫娓M(jìn)一步,加入了如繼承等特性, 一般稱之為ORDBMS。
在RDBMS的使用者也從另一面方嘗試解決RDBMS與應(yīng)用之間的一些模式映射匹配問題,近幾年出現(xiàn)了ORM技術(shù),為RDBMS與面向?qū)ο笳Z言之間減少了一些隔壑,雖然并沒有從根本上解決問題,但作了一些很好的嘗試,也獲得了廣泛的應(yīng)用.但使用ORM技術(shù),通過 Hibernate(或其他 ORM 工具)訪問RDBMS,映射問題也無法徹底解決,它們只會轉(zhuǎn)移到配置文件。而且,有以下問題。
1. 如果您想要創(chuàng)建一個分層良好的繼承模型,將它映射到表或一組表無疑會是失敗之舉。若用違背常規(guī)形式的方式來換取查詢的性能,就會將 DBA 與開發(fā)人員在某種程度上對立起來。面向?qū)ο笾械睦^承也不能過于頻繁的使用,而且不易于使用.例如你不可能讓所有的實(shí)體類繼承于同一個類。
2. 關(guān)系不作為實(shí)體出現(xiàn), 實(shí)體之間的關(guān)系簡化為依賴這一種.無法為實(shí)體之間的關(guān)系建立一個分層良好的繼承模型,而有時關(guān)系很重要,如網(wǎng)絡(luò)故障分析中網(wǎng)絡(luò)對象之間的關(guān)系顯得至關(guān)重要.
與此同時,還出現(xiàn)了一些專用某個領(lǐng)域的數(shù)據(jù)庫如CMDB和GeoDatabase。像CMDB,它將實(shí)體之間的關(guān)系提高到了與實(shí)體同等的地位,并提供了一個圖查詢的方法。可惜它并沒有作為一個通用的數(shù)據(jù)庫出現(xiàn),它甚至不是真的作為一個數(shù)據(jù)庫存在,而只是對CMDB用戶來說像一個數(shù)據(jù)庫而已.
我在網(wǎng)絡(luò)中找到一種與CMDB很類似的數(shù)據(jù)庫,名為GraphDatabase,其中的代表是Neo4j,可惜它不是免費(fèi)的。而只能用于java語言。更為重要的是它們都是自已實(shí)現(xiàn)了一個存儲系統(tǒng),不能與RDBMS共存,可實(shí)際情況中,RDBMS是主流我們根本不可能拋棄它,因此我想基于RDBMS開發(fā)一個GraphDatabase數(shù)據(jù)庫。它作為一個RMDBS的前端運(yùn)行。
2.設(shè)想
我設(shè)想的GraphDatabase數(shù)據(jù)庫并不想做成***的,僅僅處理已有的RDBMS+ORM遇到的一些問題,在此我對這個數(shù)據(jù)庫做了一些設(shè)想
1. 很好的支持繼承,讓面向?qū)ο笳Z言更方便的映射。但它并不是面向?qū)ο髷?shù)據(jù)庫。
2. 將關(guān)系提升至與實(shí)體同等的地位,并提供完整的圖查詢操作。
3. 鑒于RDBMS的壟斷地位,它應(yīng)該能與RDBMS良好的集成,基于它開發(fā),也就是說本數(shù)據(jù)庫的數(shù)據(jù)模型能夠簡單地映射到RDBMS,它們之間有一個簡單的映射機(jī)制。
4. 數(shù)據(jù)庫中的信息主要通過如下3個基本的構(gòu)建塊表示:
a) 條目(Item, 又叫做vertex)——從概念上來說,這類似于對象實(shí)例,擁有唯一的ID,并包含0個或多個以上的屬性組(attributeGroup)。
b) 關(guān)系(relationship,又叫做edge)——它連接了兩個Item,此外還具有方向和類型(RelationshipType),它實(shí)際上是條目(Item)的一個子類。
c) 屬性組(attributeGroup) ——它是一組 key/Value對的集合。Item與Relationship都有零到多個attributeGroup。
鑒于第3個需求,我們必須基于RDBMS 上來開發(fā),做一個數(shù)據(jù)庫的前端,它可以嵌入在其他應(yīng)用程序中,也可以獨(dú)立運(yùn)行。
此外,我們還應(yīng)加上一些額外的可選功能,如
1. 數(shù)據(jù)的版本信息
2. 數(shù)據(jù)的狀態(tài)管理
3. 數(shù)據(jù)的變更記錄
4. 數(shù)據(jù)的基線功能
5. 自動的全文搜索
#p#
3.需求
在做這個數(shù)據(jù)庫前我們還是先確定一下上述的第2種情況主要有那些需求.
3.1數(shù)據(jù)的定義
本數(shù)據(jù)庫是用條目(item)、關(guān)系(relationship)和屬性組(attributeGroup)三個概念來組織數(shù)據(jù)的,用戶可以按自已的需求定義這三種對象,將數(shù)據(jù)庫的數(shù)據(jù)看成一個有屬性的有向圖,如下
條目(item)就是圖中的節(jié)點(diǎn), 關(guān)系(relationship)就是圖中的邊。節(jié)點(diǎn)和邊都具有屬性,它們用屬性組(attributeGroup)來組織。這三個對象的UML類圖如下
所有用戶自定義的條目(item)、關(guān)系(relationship)和屬性組(attributeGroup)都必須從它們繼承。
3.1.1條目(item)
一個條目(item)代表一個對象實(shí)例(如計(jì)算機(jī),應(yīng)用軟件,或其它),
- <!--[if !supportLists]-->1. <!--[endif]-->每一個條目(item)將至少有一個唯一的Id,并充當(dāng)一個Key
- <!--[if !supportLists]-->2. <!--[endif]-->為一個條目(item)指定一個Id.后,它可能用在任何需要Id的場合
- <!--[if !supportLists]-->3. <!--[endif]-->一個條目(item)具有0個或多個屬性組(attributeGroup)。注意它自己不能直接包含屬性。
3.1.2關(guān)系(relationship)
一個關(guān)系(relationship)表示源條目(item)與目標(biāo)條目(item)之間的連接。如一個軟件“運(yùn)行(runs)”在一個操作系統(tǒng)上、一個操作系統(tǒng)“安裝(installed)”在一個計(jì)算機(jī)上、一個故障(incident)記錄“影響(affects)”一個計(jì)算機(jī)、以及一個服務(wù)使用另一個服務(wù)。關(guān)系(relationship)有下列特征
- <!--[if !supportLists]-->1. <!--[endif]-->一個關(guān)系(relationship)嚴(yán)格的連接兩個條目(item),一個是源,一個是目標(biāo),并提供關(guān)于這個關(guān)系(relationship)的信息。
- <!--[if !supportLists]-->2. <!--[endif]-->一個關(guān)系(relationship)是一個條目(item)的子類,并具有一個條目(item)的所有特征。如每個關(guān)系(relationship)都將有一個唯一的ID,并作為key。
- <!--[if !supportLists]-->3. <!--[endif]-->一個關(guān)系是有方向的,但本系統(tǒng)并沒有為這個方向賦予什么特殊的意義。但刪除是依賴于方向的,具體請見刪除條目。
- <!--[if !supportLists]-->4. <!--[endif]-->一個關(guān)系(relationship)具有0個或多個屬性組(attributeGroup)。注意它自己不能直接包含屬性。
3.1.3屬性組(attributeGroup)
屬性組(attributeGroup)表示一個含有描述條目(item)或關(guān)系的屬性(注意這里的屬性是類似于數(shù)據(jù)庫表中的字段,而不是像C#語言中的屬性)的集合。屬性組(attributeGroup)有下列特征:
<!--[if !supportLists]-->1. <!--[endif]-->一個屬性組(attributeGroup)必須關(guān)聯(lián)于一個條目(item)或關(guān)系(relationship)
<!--[if !supportLists]-->2. <!--[endif]-->一個屬性組(attributeGroup)可能含有用于標(biāo)識條目(item)或關(guān)系(relationship)的屬性,或它可能含有描述條目(item)或關(guān)系(relationship)的屬性
<!--[if !supportLists]-->3. <!--[endif]-->不同類型的幾條屬性組(attributeGroup)可能關(guān)聯(lián)相同的條目(item)或關(guān)系(relationship)
<!--[if !supportLists]-->4. <!--[endif]-->一個屬性組(attributeGroup)可能含有多個屬性,也有可以不含屬性,這時它充當(dāng)一個標(biāo)記。
一個屬性組(attributeGroup)類似于SQL視圖中的一行。它是一個屬性的投影。相同的屬性可能出現(xiàn)在同一個條目(item)或關(guān)系的多個屬性組(attributeGroup)中。屬性組(attributeGroup)可能沒有屬性,這種情況下它用于充當(dāng)一個標(biāo)記。
每個屬性組(attributeGroup)可能有下列描述屬性組(attributeGroup)自身的元屬性
<!--[if !supportLists]-->1. <!--[endif]-->有一個Id在它所關(guān)聯(lián)的條目(item)或關(guān)系(relationship)的范圍內(nèi)中唯一的,并充當(dāng)key(如果條目(item)或關(guān)系(relationship)只有一個記錄時是可選地)
<!--[if !supportLists]-->2. <!--[endif]-->記錄的日期/時間是***修改時間(可選的)
為什么提供屬性組(attributeGroup)這樣一個東西,而不是直接讓條目(item)或關(guān)系(relationship)擁有屬性呢?
因?yàn)檎鎸?shí)世界中一個對象從不同角度來看它可能有不同的屬性,如一個計(jì)算機(jī),從網(wǎng)管員的角度看,它有主機(jī)名,IP地址,MAC址址,CPU型號,MEM大小, 硬盤容量等屬性,而對財(cái)務(wù)部門的角度來看,它有訂單號,單價,購買日期,使用年限,維護(hù)人等信息,
如果我們提供屬性組(attributeGroup)這個概念后,用戶在建立模型時可以從不同角度對實(shí)物建模,一個條目(item)可以有一到多個屬性組(attributeGroup),每個屬性組(attributeGroup)針對一個或多個使用者.
3.1.4繼承
以上三個對象都像類一樣可以繼承,用戶可以用它們來對自已的領(lǐng)域建模,它們繼承的行為如下:
屬性組(attributeGroup)的繼承非常類似于普通的類,當(dāng)一個屬性組(attributeGroup)繼承另一個屬性組(attributeGroup)時,就具有它的所有屬性,但它沒有方法。屬性不能重載。
條目(item)的繼承則表示: 當(dāng)一個條目(item)繼承另一個條目(item)時,就具有它的所有屬性組(attributeGroup).此外它還有二個特殊的地方
1. 當(dāng)一個條目(item)繼承另一個條目(item)時,子條目(item)中有一個屬性組(attributeGroup), 該屬性組在子條目(item)重復(fù)申明了, 或該屬性組(attributeGroup)也有一個父屬性組(attributeGroup),它的父屬性組(attributeGroup)已經(jīng)存在于父條目(item)中,那么子條目(item)中的該屬性組(attributeGroup)覆蓋父條目(item)中的父屬性組(attributeGroup),
2. 當(dāng)出現(xiàn)上述情況,同時子條目(item)的約束與父條目(item)不一致時,需要注意, 父條目(item)中的約束必須比子條目(item)中的約束更嚴(yán)格 .
這里說約束是指條目與屬性組的約束.
關(guān)系(relationship)是條目(item)的一個子類,因此它的繼承行為與條目(item)的繼承行為完全一致。
3.1.5數(shù)據(jù)類型
- Integer 通過指定范圍來確定是int8, int16, int32,int64
- Numeric 用戶指定精度
- Money 專用于存儲貨幣類型的數(shù)據(jù)
- String, 用戶指定長度來確是是 char, varchar還是text
- date/timestamp/duration
- boolean
- ipAddress ip地址,包括ipv4和ipv6
- physicalAddress MAC地址
- GUID類型
- XML 數(shù)據(jù)
- 數(shù)組
在這里我就不詳細(xì)說了,將會在概要設(shè)計(jì)中說明.
3.1.6約束
在描述約束之前我們說明一下約束更嚴(yán)格的含義: 約束a比約束b更嚴(yán)格,意味著假如一個實(shí)例滿足約束a,那么該實(shí)例一定滿足約束b,反之不一定成立。
約束分為三種:一種是針對屬性的值約束,一種是條目與屬性組的約束,一種是針對條目與條目的關(guān)系約束。
3.1.6.1屬性的值約束
它主要是針對單個值的限定,它是我從xml的限定學(xué)過來的
限定 |
描述 |
enumeration |
定義可接受值的一個列表 |
fractionDigits |
定義所允許的***的小數(shù)位數(shù)。必須大于等于0。 |
totalDigits |
定義所允許的阿拉伯?dāng)?shù)字的精確位數(shù)。必須大于0。 |
length |
定義所允許的字符或者列表項(xiàng)目的精確數(shù)目。必須大于或等于0。 |
maxExclusive |
定義數(shù)值的上限。所允許的值必須小于此值。 |
maxInclusive |
定義數(shù)值的上限。所允許的值必須小于或等于此值。 |
maxLength |
定義所允許的字符或者列表項(xiàng)目的***數(shù)目。必須大于或等于0。 |
minExclusive |
定義數(shù)值的下限。所允許的值必需大于此值。 |
minInclusive |
定義數(shù)值的下限。所允許的值必需大于或等于此值。 |
minLength |
定義所允許的字符或者列表項(xiàng)目的最小數(shù)目。必須大于或等于0。 |
pattern |
定義可接受的字符的精確序列。 |
這些限定都與類型相關(guān),具體不再敘述了,將會在概要設(shè)計(jì)中說明.
3.1.6.2條目與屬性組之間的約束
它主要是描述一種條目可以包含的那些屬性組可以出現(xiàn)的次數(shù),可以用以下兩個限定符來修飾屬性:
maxOccurs 表示***出現(xiàn)次數(shù),默認(rèn)值為1
minOccurs 表示最小出現(xiàn)次數(shù),當(dāng)為0時,表示可選,默認(rèn)值為0
這兩個修飾屬性的值必須是一個正整數(shù)或"unbounded","unbounded"表示不限制。
注意,關(guān)系是條目的一個子類,因此它一樣也有此約束。
3.1.6.3條目與條目之間的關(guān)系約束
它主要是描述一種關(guān)系中源條目或目標(biāo)條目可以出現(xiàn)的次數(shù)。出現(xiàn)次數(shù)可以用上面的兩個限定符。
maxOccurs 表示***出現(xiàn)次數(shù),默認(rèn)值為unbounded
minOccurs 表示最小出現(xiàn)次數(shù),當(dāng)為0時,表示可選,默認(rèn)值為0.
這兩個修飾屬性的值必須是一個正整數(shù)或"unbounded","unbounded"表示不限制。需要注意的是一個關(guān)系有兩個端點(diǎn),需要分別對這兩個節(jié)點(diǎn)分別用這兩個限定符進(jìn)行修飾。
3.2數(shù)據(jù)操作
3.2.1一般功能
對數(shù)據(jù)庫的操作無非就是插入,更新,刪除和查詢, 其中最重要的就是查詢了.
3.2.1.1查詢
3.2.1.1.1普通查詢
基本上與常見的ORM工具提供的查詢語言(hibernate的HQL或)沒有什么區(qū)別,一般Select 語句能支持的都支持,在這里我就不再說了。具體的設(shè)計(jì)將在概要設(shè)計(jì)中定義.
注意我們不要開發(fā)一個像HQL那樣的查詢語言,而是應(yīng)該用一個SQL語句的抽象類庫來生成數(shù)據(jù)庫的原生SQL語句。
3.2.1.1.2圖查詢
這里就是與其他ORM工具提供的查詢語言(hibernate的HQL)不同的地方了,它提供了完整的圖查詢操作。
在設(shè)計(jì)一個圖查詢之前我們想象一下我們對一個圖進(jìn)行查詢對有什么樣子的需求呢
1.從一點(diǎn)或n點(diǎn)出發(fā),走指定的條件的線路,找出所有可到達(dá)的所有端點(diǎn)和線路
2.從一點(diǎn)或n點(diǎn)出發(fā),走任意線路,找出所有可到達(dá)的所有端點(diǎn)和線路,但這些端點(diǎn)必須符合指定的條件。
3.從一點(diǎn)或n點(diǎn)出發(fā),走指定的條件的線路,找出所有可到達(dá)的所有端點(diǎn)和線路,但這些端點(diǎn)必須符合指定的條件。
4.以上三個反過來,反過來查起始端點(diǎn)
因此我們將圖查詢設(shè)計(jì)為由三部分組成,源條目過濾表達(dá)式,目標(biāo)條目過濾表達(dá)式和關(guān)系過濾表達(dá)式。其中源條目過濾表達(dá)式和目標(biāo)條目過濾表達(dá)式在格式上完全相同,我們稱之為條目過濾表達(dá)式(itemFilter),而關(guān)系過濾表達(dá)式(relationshipFilter)則稍有不同,它是在條目過濾表達(dá)式的基礎(chǔ)上增加了一個遍歷深度參數(shù),你可以認(rèn)為關(guān)系過濾表達(dá)式(relationshipFilter)是條目過濾表達(dá)式(itemFilter)的派生類。
其中源條目過濾表達(dá)式,目標(biāo)條目過濾表達(dá)式是可選的,但不能相同兩個都沒有。
條目過濾表達(dá)式(itemFilter)
一個條目(item)匹配一個itemFilter當(dāng)且僅當(dāng)下列規(guī)定所有都為真時:
1.該條目符合定義在itemFilter中的約束。
2.當(dāng)它作為源條目過濾表達(dá)式時,都有一個匹配 relationshipFilter 并將此條目(item)作為源的關(guān)系。
3.當(dāng)它作為目標(biāo)條目過濾表達(dá)式時,都有一個匹配 relationshipFilter 并將此條目(item)作為目標(biāo)的關(guān)系。
雖然關(guān)系也是一個條目,但條目過濾表達(dá)式(itemFilter)不會返回關(guān)系實(shí)例。
關(guān)系過濾表達(dá)式(relationshipFilter)
一個關(guān)系匹配relationshipFilter當(dāng)且僅當(dāng)下列規(guī)定所有都為真:
- 符合 relationshipFilter中的約束的關(guān)系。如果源條目到目標(biāo)條目之間要經(jīng)過多個端點(diǎn)時,我們可能需要增加一個針對中間端點(diǎn)的itemFilter。
- 關(guān)系的源條目(item)匹配源條目過濾表達(dá)式。
- 關(guān)系的目標(biāo)條目(item)匹配目標(biāo)條目過濾表達(dá)式。
- 圖中源條目和目標(biāo)條目之間的邊的數(shù)量滿足指定的條件。
沒有一個源或目標(biāo)的關(guān)系,不能匹配relationshipFilter。
通過這三個部分的組合,基本上可以達(dá)到上面提到的要求了,便幾點(diǎn)需要注意:
1.一個圖中可能會有一個環(huán),用戶無需關(guān)心,實(shí)現(xiàn)本文的實(shí)現(xiàn)應(yīng)該自己處理
2.因?yàn)閳D查詢其實(shí)是一個遞歸操作,因此需要對遞歸的深度進(jìn)行限制。
3.一個端點(diǎn)可能會有多個到達(dá)另一個端點(diǎn)的路徑,只要這些路徑符合relationshipFilter,那么它們就應(yīng)該出現(xiàn)在結(jié)果中。
3.2.1.2插入
屬性組的插入, 可以將它當(dāng)作表一樣,基本上與常見的Insert支持的都支持,在這里我就不再說了。但需要注意滿足條目與屬性組的約束。
條目的插入,它有點(diǎn)特殊,因?yàn)橛袟l目與屬性組的約束和條目與條目的關(guān)系約束,因此你必須一次性地將條目和條目的必選屬性組以及條目與其它條目的必選關(guān)系一起建起來,否則無法創(chuàng)建成功??赡苓€要一同創(chuàng)建必要的子條目。
關(guān)系的插入,它也有點(diǎn)特殊,因?yàn)橛袟l目與屬性組的約束,因此一次性的將關(guān)系與它的必選屬性組一起建起來,否則無法創(chuàng)建成功。
3.2.1.3刪除
這個就是與一般的數(shù)據(jù)庫相差甚遠(yuǎn),因此重點(diǎn)說明一下。
3.2.1.3.1刪除屬性組
基本上與常見的ORM工具提供的刪除沒有什么區(qū)別,一般Delete 語句能支持的都支持,在這里我就不再說了。當(dāng)然刪除它不能違反條目與屬性組的約束。
3.2.1.3.2刪除條目
刪除一個條目時必須同時刪除該條目的所有屬性組,但在刪除它之前,需要同時刪除與它相連的關(guān)系。但刪除關(guān)系時需要滿足下面的條件:
<!--[if !supportLists]-->l <!--[endif]-->不能違反條目與條目之間的關(guān)系約束
在實(shí)際情況中,僅僅這樣子是不夠,你可能希望在刪除一個條目時,將其相鄰的條目刪除(相鄰是指兩者之間有一個關(guān)系存在),但這需要清楚該條目能否安全的刪除。所以我們需要用戶來指定一下:
在定義關(guān)系時,在定義關(guān)系的源和目標(biāo)時各增加一個onDelete字段,用來表示刪除這個關(guān)系時,是否連帶刪除該條目.
呵呵,這個就是數(shù)據(jù)庫的級聯(lián)刪除了.
3.2.1.3.3刪除關(guān)系
同樣,刪除一個關(guān)系時必須同時刪除該關(guān)系的所有屬性組。注意同樣地刪除時不能違反條目與條目的關(guān)系約束。
3.2.1.4更新
屬性組的更新,可以將它當(dāng)作表一樣,基本上與常見的Update支持的都支持,在這里我就不再說了。
條目的更新,它有點(diǎn)特殊,因?yàn)樗膶傩远細(xì)w類在屬性組中,自身沒有屬性,那么唯一可以更改的就是改變類型了。但改變類型需要注意,兩個類型可以擁有的屬性組是不一樣的,你可能在更改它的類型時,需要刪除和添加一些屬性組。
關(guān)系的更新,因?yàn)樗菞l目的子類,因此它的更新與條目的更新差不多,但它比條目多兩個屬性,即源條目和目標(biāo)條目的引用。源條目是不可更改的,但目標(biāo)條目的引用是可以更改的。用戶可以修改它,但要注意不能違反條目與條目的關(guān)系約束
3.2.2可選功能
3.2.2.1對象的狀態(tài)管理
一個對象在它的生命周期中可能會經(jīng)過幾個階段, 而對不同的用戶來說在對象處于某些階段時他并不想看到它,如一個PC可能會分為購入待處理、試用、正式運(yùn)行、停用、作廢。對財(cái)務(wù)人員來說,從它生命周期開始到結(jié)束之前都能看到。而對網(wǎng)絡(luò)管理的值班人員來說只希望看到處于“試用”和“正式運(yùn)行”階段的PC。
針對這種情況本數(shù)據(jù)庫引入一個環(huán)境的概念,即管理員可以為系統(tǒng)定義幾個環(huán)境,并定義這些環(huán)境可以訪問到處于哪幾個狀態(tài)的對象,并且為用戶設(shè)置一個默認(rèn)環(huán)境。用戶可以在運(yùn)行中更改自己所處的環(huán)境。
3.2.2.2數(shù)據(jù)的版本管理
每一個數(shù)據(jù)我們都給它一個版本,初始值為0,以后對數(shù)據(jù)的每次變化時,都給它一個新的版本號,以便用戶進(jìn)行調(diào)試或查詢。
3.2.2.3數(shù)據(jù)的變更記錄
對數(shù)據(jù)的每次變更都記錄下來,形成一個歷史記錄,以便用戶進(jìn)行調(diào)試或查詢。它可以與基線管理配合使用。
3.2.2.4數(shù)據(jù)的基線管理
將數(shù)據(jù)的某個版本作為一個基線,以后每次的變更都是針對這個基線的增量。
3.2.2.5數(shù)據(jù)的全文搜索
3.3非功能性需求
3.3.1分布式的需求
本數(shù)據(jù)庫暫時對分布式方面的要求不高,或者說暫時不需要,在***個實(shí)現(xiàn)中不與考慮.
3.3.2數(shù)據(jù)庫事務(wù)的要求
雖然本數(shù)據(jù)庫對分布式要求不高,但對事務(wù)時要求卻很高,必須有完整ACID支持,幸運(yùn)的是我們有下層的RDBMS來保證,我們只要提供封裝就可以了。
3.3.3移植的要求
這個分為兩個方面
一、對RDBMS數(shù)據(jù)庫的移植要求,暫時不要求支持多種數(shù)據(jù)庫,僅支持PostgreSQL.
二、對操作系統(tǒng)方面的移植要求,暫時要求支持windows和redhat。
#p#
4.設(shè)計(jì)思路
本數(shù)據(jù)庫的架構(gòu)大致如下
<!--[if !mso]-->
整個數(shù)據(jù)庫分為兩大部分:Model generator負(fù)責(zé)將用戶創(chuàng)建的數(shù)據(jù)類圖生成為多個RDBMS表或視圖的創(chuàng)建或修改語句。而DB front-end 則負(fù)責(zé)將客戶端的請求轉(zhuǎn)化為一個或多個SQL語句。總之盡量讓RDBMS來完成工作.
那么本數(shù)據(jù)庫的對象模型如何映射到RDBMS上呢?在說明這個之前我們先來介紹一下PostgreSQL數(shù)據(jù)庫的兩個功能:
<!--[if !supportLists]-->
<!--[if !supportLists]--> 一、<!--[endif]-->表的繼承
二、遞歸查詢,或者叫公共表表達(dá)式 <!--[endif]-->(Common Table Expression,CTE)
<!--[endif]-->
現(xiàn)在聰明的你差不多應(yīng)該明白我的想法了, 我們按數(shù)據(jù)的定義中的UML類圖中定義的那樣子在RDBMS中定義5個基本的表,其它用戶定義的派生條目(item) 、派生關(guān)系(relationship)和派生屬性組(attributeGroup)則各對應(yīng)一個表,所有的這些表的繼承均與類的繼承關(guān)系完全一致。
4.1模型的映射
我將它簡化一下僅保留item、attributeGroup和relationship, 如下
- <!--[endif]-->
- CREATE TABLE identifierObject(id INTEGER PRIMARY KEY ) ;
- CREATE TABLE itemObject ( ) INHERITS (identifierObject);
- CREATE TABLE item( ) INHERITS (itemObject);
- CREATE TABLE relationship (
- id INTEGER PRIMARY KEY,
- source INTEGER NOT NULL REFERENCES item (id) ON UPDATE CASCADE ON DELETE CASCADE,
- destination INTEGER NOT NULL REFERENCES item (id) ON UPDATE CASCADE ON DELETE CASCADE,
- UNIQUE(source, destination)
- ) INHERITS (itemObject);
- CREATE TABLE attributeGroup (
- id INTEGER PRIMARY KEY,
- ownerid INTEGER NOT NULL REFERENCES item (id) ON UPDATE CASCADE ON DELETE CASCADE
- ) INHERITS (identifierObject);
- CREATE INDEX a_idx ON relationship (source);
- CREATE INDEX b_idx ON relationship (destination);
- CREATE INDEX c_idx ON attributeGroup (ownerid);
- -–確保它不與自己發(fā)生關(guān)系
- ALTER TABLE relationship ADD CHECK (source <> destination) ;
現(xiàn)在模型建立好了,我們可以開始對它進(jìn)行操作了.
4.1.1創(chuàng)建
這個不用說了吧
4.1.2刪除
刪除一個條目
- DELETE FROM item WHERE id = 2;
--因?yàn)橄嚓P(guān)的relationship和attributeGroup有CASCADE選項(xiàng),與之相關(guān)的關(guān)系和屬性組會自動刪除
刪除一個關(guān)系
- DELETE FROM relationship WHERE id = 3;
刪除一個屬性組
- DELETE FROM attributeGroupWHERE id = 6;
4.1.3更新
這個不用說了吧
4.1.4查詢
呵呵,普通查詢我就不說了,我們說說圖查詢吧.
4.1.4.1簡單一級的查詢
查詢id為1的條目相鄰的條目
- <!--[endif]-->
- SELECT *
- FROM item n
- LEFT relationship e ON n.id = e.source JOIN
- WHERE e.id = 1; -- 查詢id為1的條目相鄰的條目
4.1.4.2復(fù)雜一點(diǎn)的遞歸查詢
從id為1的條目出發(fā),詢它沿關(guān)系能到達(dá)的條目,并返回路過的中間節(jié)點(diǎn)的個數(shù)和路徑
- <!--[endif]-->
- WITH RECURSIVE transitive_closure(source, b, distance, path_string) AS
- (
- SELECT source, destination, 1 AS distance,
- source || '.' || destination || '.' AS path_string
- FROM relationship
- WHERE source = 1 -- source
- UNION ALL
- SELECT tc.source, e.destination, tc.distance + 1,
- tc.path_string || e.destination || '.' AS path_string
- FROM relationship AS e
- JOIN transitive_closure AS tc ON e.source = tc.destination
- WHERE tc.path_string NOT LIKE '%' || e.destination || '.%'
- )
- SELECT * FROM transitive_closure
4.2討論
我對postgresql進(jìn)行過測試,在這種繼承結(jié)構(gòu)下,它是很低效的,一般來說你對一個父表進(jìn)行查詢時,它會依次對派生表進(jìn)行查詢的,當(dāng)派生表太多時,它的查詢時候基本上變成了你查詢每一個表的時間總和的三分之一(這好像是依賴于查詢的并發(fā)數(shù))。此外繼承還有一定的局限性。
因此我想既然item表中沒有用戶定義的屬性,那么父條目與子條目的屬性是相同的(有一些系統(tǒng)定義的屬性),那么條目(item)的繼承我們可以不用表繼承的方式實(shí)現(xiàn),而是加入一個表示它的類型的字段。當(dāng)你查詢指定類型的條目時,可以在where中加上一個 type = x的過濾表過式,而這個表示類型的字段的設(shè)計(jì)請參見層次型枚舉。
將對象的類型改成一個字段來表示,還有一個好處就是用戶可以更改對象的類型,而這樣子更符合面向?qū)ο蟮乃枷搿?/p>
5.后記
呵呵,這個數(shù)據(jù)庫是我為公司設(shè)計(jì)一個CMDB原型時開始構(gòu)思的,花了8天完成了本文。其間對數(shù)據(jù)庫了解從僅知道簡單的Select到了解分區(qū)、物化視圖、自定義操作符、公共表表達(dá)式(Common Table Expression,CTE)。感謝Google,讓我可以查到大量的資料。
原文標(biāo)題:GraphDatabase在關(guān)系數(shù)據(jù)庫中的實(shí)現(xiàn)
鏈接:http://www.cnblogs.com/runner-mei/archive/2010/09/15/1826219.html
【編輯推薦】