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

























