在“
面向?qū)ο蠼Ec數(shù)據(jù)庫(kù)建模兩種分析設(shè)計(jì)方法的比較”一文中我們比較了在對(duì)需求分析時(shí)兩種方法的不同,所謂數(shù)據(jù)庫(kù)建模分析,就是項(xiàng)目一開(kāi)始就根據(jù)需求建立數(shù)據(jù)庫(kù)模型,如數(shù)據(jù)表結(jié)構(gòu)和字段等,這種錯(cuò)誤現(xiàn)象大量普遍存在我們國(guó)內(nèi)項(xiàng)目實(shí)踐中,從每年大量招聘啟示中就可見(jiàn)一斑:招聘數(shù)據(jù)庫(kù)建模人員,招聘Java面向?qū)ο蟪绦騿T。這些說(shuō)明軟件業(yè)一邊在大量使用Java/.NET/Ruby on Rails這樣OO語(yǔ)言同時(shí),還在同時(shí)使用與OO體系抵觸的圍繞關(guān)系數(shù)據(jù)庫(kù)的分析設(shè)計(jì)方法。
為進(jìn)一步說(shuō)明OO和關(guān)系數(shù)據(jù)庫(kù)是屬于兩個(gè)不同世界觀,存在天然矛盾,就象有神論和無(wú)神論,就象水與火那樣屬于截然相反的兩種編程知識(shí)。正好最近TheServerSide刊登一篇大談對(duì)象數(shù)據(jù)庫(kù)ODBMS的文章:When to use an Embedded ODBMS(以下簡(jiǎn)稱ODBMS一文)
http://www.theserverside.com/tt/articles/article.tss?l=EmbeddedODBMS這篇文章不但談到了OO和關(guān)系數(shù)據(jù)庫(kù)天然阻抗;談到了ODBMS和RDBMS區(qū)別,也談到了ODBMS和O/R Mapping如Hibernate的區(qū)別,甚至談到了ODBMS在非C/S如嵌入式方面的優(yōu)點(diǎn)。這篇文章我看最重要價(jià)值就是它用大量篇幅闡述了對(duì)象和關(guān)系數(shù)據(jù)庫(kù)存在的天然矛盾,我這里就將這部分篇幅大意轉(zhuǎn)載這里,可以說(shuō)是經(jīng)過(guò)我咀嚼后反饋的結(jié)果,版權(quán)思想還是屬于這篇文章,我這里只是用中國(guó)人更易于接受方式把這個(gè)天然矛盾轉(zhuǎn)述出來(lái):
對(duì)象和關(guān)系數(shù)據(jù)庫(kù)累贅轉(zhuǎn)換
在一個(gè)面向?qū)ο笙到y(tǒng)中,數(shù)據(jù)存在于對(duì)象中,在關(guān)系數(shù)據(jù)庫(kù)中,數(shù)據(jù)存在于數(shù)據(jù)表的記錄中。
在關(guān)系數(shù)據(jù)庫(kù)世界中,我們必須掌握表結(jié)構(gòu)、表記錄等概念,這里面沒(méi)有任何對(duì)象概念,當(dāng)我們?cè)贠O語(yǔ)言中使用關(guān)系數(shù)據(jù)庫(kù)時(shí),因?yàn)樵贠O世界中是大量對(duì)象,所以必須將數(shù)據(jù)在這兩個(gè)不同世界之間轉(zhuǎn)換傳輸。
這就導(dǎo)致了大量垃圾臨時(shí)對(duì)象,增加了應(yīng)用系統(tǒng)的復(fù)雜性。這也是我們很多人使用了Java等這樣的OO系統(tǒng)后,反而覺(jué)得開(kāi)發(fā)效率并沒(méi)有OO宣傳那么高的原因, 說(shuō)白了,OO思想沒(méi)有完全占領(lǐng)應(yīng)用系統(tǒng),因?yàn)橛嘘P(guān)系數(shù)據(jù)庫(kù)盤(pán)踞著數(shù)據(jù)庫(kù)這個(gè)最后堡壘負(fù)隅頑抗。
下面看看我們程序如何為了讓OO語(yǔ)言和關(guān)系數(shù)據(jù)庫(kù)捆綁在一起工作,如何彌補(bǔ)兩個(gè)世界的裂縫,如何在他們之間和稀泥。 為了將對(duì)象保存到關(guān)系數(shù)據(jù)庫(kù)中,我們必須將對(duì)象中的數(shù)據(jù)解散開(kāi)來(lái),再將它們組裝到SQL中,然后執(zhí)行SQL。
一個(gè)類如下:
public class Course {
public string name;
public int courseID;
int deptID;
public int creditHours;
}
將上述對(duì)象保存到關(guān)系數(shù)據(jù)庫(kù)的SQL語(yǔ)句如下:
PreparedStatement insertStatement = connection.prepareStatement(
"INSERT INTO Courses (name, courseID, deptID, creditHourse) " +
"VALUES (?, ?, ?, ?)");
insertStatement.setString(1, course.name);
insertStatement.setInt(2, course.courseID);
insertStatement.setInt(3, course.deprtID);
insertStatement.setInt(4, course.creditHours);
insertStatement.executeUpdate();
上述在兩個(gè)世界之間做的翻譯工作與復(fù)雜性取決于對(duì)象Course這樣的復(fù)雜性。如果Course字段有十幾個(gè),那么這個(gè)保存SQL將更復(fù)雜。
更重要的是,萬(wàn)一有一個(gè)字段發(fā)生變化,更改量就很大。我們不但要修改對(duì)象,而且還要修改數(shù)據(jù)表結(jié)構(gòu),有過(guò)數(shù)據(jù)庫(kù)編程經(jīng)驗(yàn)的人知道,如果表中有數(shù)據(jù),表結(jié)構(gòu)變動(dòng)帶來(lái)的問(wèn)題可能就象噩夢(mèng)一樣,所以,我們程序員經(jīng)常以這個(gè)需要更改表結(jié)構(gòu)拒絕一些軟件的維護(hù)和拓展,這其實(shí)更是錯(cuò)上加錯(cuò);在ODBMS一文中提出了對(duì)象數(shù)據(jù)庫(kù)解決方案:只要更改類結(jié)構(gòu),其他都有ODBMS搞定;使用ORM如Hibernate也只多一個(gè)步驟:更改映射配置。
繼承關(guān)系的尷尬實(shí)現(xiàn)
對(duì)象和關(guān)系數(shù)據(jù)庫(kù)的不匹配還表現(xiàn)在關(guān)系數(shù)據(jù)庫(kù)難以實(shí)現(xiàn)對(duì)象世界中的繼承關(guān)系,如下圖:
這是一個(gè)典型的對(duì)象世界表達(dá)客觀世界的類圖,學(xué)生和教授都是人,都具備人一些共性,當(dāng)然他們之間也有區(qū)別,所以我們用上述繼承關(guān)系來(lái)表達(dá)這樣一個(gè)情況。
那么如果這樣繼承關(guān)系的類圖如何保存到關(guān)系數(shù)據(jù)庫(kù)中呢?有兩種方式:one-class-per-table(一個(gè)類對(duì)應(yīng)一個(gè)表)和one-object-per-row(一個(gè)對(duì)象對(duì)應(yīng)一個(gè)表行)。
在one-class-per-table方式中,Student對(duì)象放在Student表中,Professor放在Professor表中。但是Student和Professor實(shí)際具有共性Person,這實(shí)際上將Person這個(gè)共性對(duì)象中數(shù)據(jù)分割成兩個(gè)表保存,從語(yǔ)義上所:也就是將一個(gè)對(duì)象分割成兩個(gè)部分了;而且當(dāng)你要獲取這個(gè)對(duì)象時(shí),需要兩次Select。同樣道理增刪改查都要兩次。
如果按照one-object-per-row,將這Student和Professor放到一個(gè)表中,就會(huì)產(chǎn)生空白字段。Student對(duì)象中數(shù)據(jù)保存到Student對(duì)象對(duì)應(yīng)的字段中,那么Professor對(duì)象對(duì)應(yīng)的字段就是空的,反之也然。
這些都反映了對(duì)象和關(guān)系數(shù)據(jù)庫(kù)天然不匹配,兩者根本就無(wú)法搭配在一起工作,只有一方作出妥協(xié),大部分情況是對(duì)象作出妥協(xié),這樣,一個(gè)OO語(yǔ)言Java/.NET系統(tǒng)就做成了一個(gè)數(shù)據(jù)庫(kù)中心系統(tǒng),根本無(wú)法享受OO語(yǔ)言帶來(lái)快捷方便,可維護(hù)性強(qiáng),由于Java出現(xiàn)比較早,更多程序員將項(xiàng)目失敗歸結(jié)于Java語(yǔ)言本身,轉(zhuǎn)而使用數(shù)據(jù)庫(kù)思維去做.NET,去做Ruby On Rails,還是會(huì)碰到上面這些問(wèn)題,沒(méi)有碰到,只能說(shuō)明他們系統(tǒng)中就沒(méi)有對(duì)象概念,解決方式很簡(jiǎn)單很可笑:矛盾雙方,消滅一方不久解決矛盾了。
類的復(fù)雜關(guān)系實(shí)現(xiàn)
下面我們?cè)倏纯碠DBMS一文中列舉的類復(fù)雜關(guān)系如何用關(guān)系數(shù)據(jù)庫(kù)實(shí)現(xiàn)保存:
public class Department {
private Professor[] professors;
... }
這個(gè)Department類很明確告訴我們這樣一個(gè)意思,在一個(gè)Department中,存在很多Professor,大白話就是:一個(gè)部門(mén)里有很多教授,這個(gè)類就自然準(zhǔn)確地表達(dá)這個(gè)意思,這也體現(xiàn)了使用OO表達(dá)需求的自然性和方便性。
那么我們下面看看,如果我們使用關(guān)系數(shù)據(jù)庫(kù)來(lái)保存Department這個(gè)對(duì)象,很顯然,這里需要使用關(guān)系數(shù)據(jù)庫(kù)的表外鍵,通過(guò)表外鍵來(lái)表達(dá)Department表和Professor之間1:N關(guān)系,關(guān)鍵問(wèn)題是:這個(gè)外鍵一般是設(shè)計(jì)在Professor表中,這就造成語(yǔ)義上的誤解。
對(duì)象Department表達(dá)的需求含義是:
1. 我是一個(gè)部門(mén)對(duì)象,在我里面包含很多教授對(duì)象。
當(dāng)這個(gè)對(duì)象在關(guān)系數(shù)據(jù)庫(kù),由于外鍵在Professor表中,那么意思就是:
2. 我是一個(gè)教授對(duì)象,這里有一個(gè)我所屬的部門(mén)對(duì)象。
前后對(duì)比發(fā)現(xiàn),其實(shí)完全是兩者不同表達(dá)方式,這已經(jīng)屬于拷貝走樣了,當(dāng)我們從關(guān)系數(shù)據(jù)庫(kù)中獲得Department 對(duì)象時(shí),得按照關(guān)系數(shù)據(jù)庫(kù)規(guī)則繞一個(gè)彎來(lái)獲得完整的Department 對(duì)象。也就是說(shuō):每次獲得一個(gè)對(duì)象Department,我們得將這個(gè)需求意思翻譯成關(guān)系數(shù)據(jù)庫(kù)的表達(dá)方式,這種內(nèi)耗式的翻譯不但容易出錯(cuò),也帶來(lái)了程序員開(kāi)發(fā)上的負(fù)擔(dān)。萬(wàn)一不留神,翻譯出錯(cuò),問(wèn)題就嚴(yán)重了。
當(dāng)然,如果我們使用ODBMS,就不必做這些費(fèi)力不討好的翻譯轉(zhuǎn)換,也不再繞著彎子編程,只要直接告訴ODBMS或ORM框架如Hibernate,就能夠獲得一個(gè)真正對(duì)象意義上的Department。
以上是TSS的ODBMS一文大量篇幅闡述了對(duì)象和關(guān)系數(shù)據(jù)庫(kù)矛盾的方面,所以,我們才認(rèn)為:如果你使用對(duì)象語(yǔ)言,如Java/.NET/Ruby On Rails等,那么就必須堅(jiān)持OO思想和方法,從程序中杜絕關(guān)系數(shù)據(jù)庫(kù)對(duì)軟件的影響,將關(guān)系數(shù)據(jù)庫(kù)只看成是活動(dòng)對(duì)象的“冬眠”(英文Hibernate)地方,這也就是ORM框架Hibernate的本義所在。