園子里有些人,他們真以為自己明白了面向?qū)ο?,然后裝著滿腹經(jīng)綸,侃侃而談,一篇接一篇,不厭其煩地喊著ORM如何如何。你以為他真的明白“面向?qū)ο?#8221;么?其實(shí),他對(duì)面向?qū)ο蟮睦斫鈨H限于教科書中的封裝、繼承和多態(tài),或者再知道一點(diǎn)面向?qū)ο蟮娜舾稍瓌t但其實(shí)并不真正理解。
筆者愚鈍,入行多年尚不懂面向?qū)ο?,只懂得用其形而不懂用其?shí)。五年后的某一天終于開(kāi)竅,明白了面向?qū)ο笾畬?shí),也僅僅是一個(gè)開(kāi)始而已。當(dāng)又經(jīng)歷了另一個(gè)五年的倦怠,發(fā)現(xiàn)并理解了設(shè)計(jì)模式、面向方面等技術(shù)作為面向?qū)ο蟮谋匾a(bǔ)充后,才算是徹悟!所以當(dāng)我見(jiàn)過(guò)一個(gè)同學(xué),尚未出校門已然徹悟,真是羞愧!
有一天面試的時(shí)候,我問(wèn)一位同學(xué),F(xiàn)ramework和Library的區(qū)別是什么?他答不上來(lái)。而另一個(gè)同學(xué)略一思考就告訴我,你的程序會(huì)調(diào)用Library,而Framework會(huì)調(diào)用你的程序。雖然精辟,但我還是要補(bǔ)充:Framework通常也會(huì)提供一個(gè)Library,所以,Library是水平的,而Framework是垂直的,此處的“水平”和“垂直”是相對(duì)應(yīng)用系統(tǒng)的層次設(shè)計(jì)而言的。如果沒(méi)有層次,其實(shí)Framework其實(shí)就是Library。Microsoft的Enterprise Library當(dāng)然就是一個(gè)Library,無(wú)法代替Framework。
如果讓那位已經(jīng)徹悟的同學(xué)舍棄ORM來(lái)實(shí)現(xiàn)復(fù)雜的業(yè)務(wù)功能,他當(dāng)然無(wú)法接受。相反,如果讓一位抱著《Thinking in Java》似懂非懂的同學(xué)用ORM來(lái)實(shí)現(xiàn)同樣的功能,他也一樣無(wú)法接受。其中的一些同學(xué)非常擅于“雞蛋里挑骨頭”,于是園子里有了這樣一堆垃圾文章或者垃圾跟貼。另外一些同學(xué)不精于這樣的能力,所以仍在徬徨之中。
此乃ORM惟一之硬傷也!如果你不理解面向?qū)ο笏枷耄拖仍囍ダ斫?,然后再?lái)討論ORM這個(gè)話題,并發(fā)表你的高見(jiàn)。
再說(shuō)性能
ORM提供了所有SQL語(yǔ)句的生成,代碼人員遠(yuǎn)離了數(shù)據(jù)庫(kù)概念。從一個(gè)概念需求(例如一個(gè)HQL)映射為一個(gè)SQL語(yǔ)句,并不需要什么代價(jià),連1%的性能損失都沒(méi)有。真正的性能損失在映射過(guò)程中,更具體地講,是在對(duì)象實(shí)例化的過(guò)程中。我曾經(jīng)做過(guò)一個(gè)試驗(yàn),以“計(jì)算第N個(gè)素?cái)?shù)”這樣的命題。我采用Delphi寫Native Win32 Console程序,又采用C#寫CLR Console程序。兩者相比,令我大失所望。
N
|
結(jié)果
|
耗時(shí)
|
Delphi
|
C#
|
1000
|
7927
|
0ms
|
2ms
|
10000
|
104743
|
16ms
|
17ms
|
100000
|
1299721
|
438ms
|
324ms
|
1000000
|
15485867
|
11437ms
|
7823ms
|
該命題采用的算法是找出第N個(gè)素?cái)?shù)以前的所有素?cái)?shù),開(kāi)辟一個(gè)內(nèi)存區(qū)存貯這些素?cái)?shù)。在Delphi中我用鏈表,在C#中我用List<int>。實(shí)際的結(jié)論是:當(dāng)列表足夠大時(shí),鏈表的性能遠(yuǎn)不及List<int>。當(dāng)然,如果每個(gè)鏈表節(jié)點(diǎn)只裝一個(gè)元素,這種差異會(huì)更明顯。事實(shí)上,我測(cè)試過(guò)每個(gè)鏈表節(jié)點(diǎn)所裝的元素個(gè)數(shù)做了一個(gè)階梯試驗(yàn),從30個(gè)、254個(gè)、510個(gè)、1022個(gè)到2046個(gè),每個(gè)節(jié)點(diǎn)所裝載的元素?cái)?shù)越多,耗時(shí)越短,最終越來(lái)越接近C#的List<int>。
不知道各位是否已經(jīng)明白了性能在哪兒損失了:內(nèi)存分配。Native的內(nèi)存分配與釋放都是非常耗時(shí)的操作系統(tǒng)行為。但在托管環(huán)境下,內(nèi)存的釋放是GC干的事情,甚至不需要統(tǒng)計(jì)到耗時(shí)中,而內(nèi)存的分配也是一件非??旖莸氖虑椤.?dāng)然,即使是快捷也還是需要耗時(shí)的。這讓我聯(lián)想到DataSet的性能。DataSet也是一種數(shù)據(jù)容器,但是卻沒(méi)有多少人抱怨DataSet的性能。如果你明白DataSet的機(jī)制,就會(huì)發(fā)現(xiàn),DataStorage巧妙地規(guī)避了內(nèi)存分配和耗時(shí)的問(wèn)題。而我們的ORM無(wú)法解決每個(gè)對(duì)象實(shí)例在構(gòu)造時(shí)分配內(nèi)存所耗時(shí)間。我做了一個(gè)不精確的評(píng)估,相比DataSet,對(duì)象集合的性能損失大約占20%左右。
如果假定ORM并沒(méi)有比傳統(tǒng)的數(shù)據(jù)訪問(wèn)方式耗費(fèi)額外的IO的話,除此之外,ORM再?zèng)]有任何性能損失!
再回到前提條件:ORM并沒(méi)有比傳統(tǒng)的數(shù)據(jù)訪問(wèn)方式耗費(fèi)額外的IO。這個(gè)條件成立么?
“
由于ORM的實(shí)體對(duì)象定義已經(jīng)固定,所以即使我不需要某些字段,也一樣需要加載這些字段。”
OK,有的同學(xué)已經(jīng)看出來(lái)了。額外定義一個(gè)視圖的實(shí)體對(duì)象即可。定義這些視圖的實(shí)體對(duì)象的確很麻煩,但是肯定比構(gòu)造那些SQL并不斷地維護(hù)它簡(jiǎn)單得多。
“
當(dāng)一張表中有1000萬(wàn)行數(shù)據(jù)時(shí),實(shí)例化1000萬(wàn)個(gè)對(duì)象是不可能的。”
非常正確。難道你曾經(jīng)成功地嘗試過(guò)將1000萬(wàn)行數(shù)據(jù)加載到某個(gè)DataTable中并且沒(méi)有性能問(wèn)題?從應(yīng)用的角度來(lái)說(shuō),在一個(gè)模型中包含的實(shí)例數(shù)超過(guò)500行就有設(shè)計(jì)不當(dāng)?shù)南右伞N覍?duì)Google的抱怨是:當(dāng)搜索結(jié)果超過(guò)1000個(gè)時(shí)都會(huì)令我抓狂。讓我從1000行數(shù)據(jù)中找出我所需要的某一行,這是開(kāi)發(fā)人員的思維,并不是用戶的思維。如果能夠在已有的結(jié)果中進(jìn)行二次、三次或者多次進(jìn)一步的篩選,可能更適合絕大多數(shù)人。我為什么不愿意在分頁(yè)中花太多的精力,其原因也是如此。我認(rèn)為用戶的眼球只能接受100行以內(nèi)的數(shù)據(jù),超過(guò)這個(gè)行數(shù)就需要采用其它的方式,或者改善領(lǐng)域設(shè)計(jì)。所以,這個(gè)問(wèn)題的答案是:你不可能需要一次載入1000萬(wàn)行。
“
當(dāng)應(yīng)用系統(tǒng)整體性能欠佳時(shí),因?yàn)殡[藏了數(shù)據(jù)訪問(wèn)細(xì)節(jié),從而無(wú)法找到快速優(yōu)化的途徑。”
不能同意。幾乎每一個(gè)ORM框架都提供了非??煽康臄?shù)據(jù)庫(kù)訪問(wèn)日志。通過(guò)這些日志分析性能損失將比直接使用SQL語(yǔ)句更可靠、更方便。
靈活性
ORM不夠靈活?我完全不能理解,我甚至不知道這個(gè)不夠靈活是與什么基準(zhǔn)相比。相反,ORM可以讓你靈活地替換數(shù)據(jù)庫(kù)(當(dāng)然這個(gè)優(yōu)點(diǎn)并沒(méi)有非常重要的意義);在修改數(shù)據(jù)庫(kù)以后不需要修改服務(wù)層或者只需要進(jìn)行簡(jiǎn)單的修改;可以對(duì)某個(gè)服務(wù)進(jìn)行單獨(dú)的測(cè)試;可以對(duì)服務(wù)進(jìn)行不依賴數(shù)據(jù)庫(kù)的、上下文一級(jí)的擴(kuò)展;可以進(jìn)行更好的層次設(shè)計(jì);......
不能實(shí)現(xiàn)所有的查詢條件
如果是想表達(dá)“每一個(gè)Select語(yǔ)句可以通過(guò)面向?qū)ο蟮姆绞竭M(jìn)行查詢”的話,我覺(jué)得目前絕大部分ORM框架都已經(jīng)很好地解決。我解決這一問(wèn)題的基礎(chǔ)是:我不提供超越SQL ANSI92的能力,但覆蓋SQL ANSI92的所有功能。對(duì)于解決實(shí)際應(yīng)用中的不足部分,采用運(yùn)行時(shí)算法補(bǔ)充。Hibernate采用的是HQL這樣的方式,基本上SQL能夠做到的,HQL都無(wú)一例外可以做到。ECO采用的是OCL的方式,其功能可以完全覆蓋SQL。我的框架所實(shí)現(xiàn)的查詢目前我還沒(méi)有發(fā)現(xiàn)無(wú)法解決并必須利用Native SQL來(lái)實(shí)現(xiàn)的(因此我無(wú)法理解Hibernate3為什么要提供這樣的擴(kuò)展)。Hibernate采用的策略是以面向?qū)ο鬄楹诵?,換句話說(shuō),以持久化對(duì)象為終極目標(biāo),而以加載對(duì)象以持久化對(duì)象為前提。設(shè)計(jì)一個(gè)POJO,實(shí)例化,然后保存起來(lái),下次使用的時(shí)候可以依樣載入即可。大規(guī)模的查詢并不是框架的核心目標(biāo)。所以,如果你完全依賴Hibernate去持久化,我非常擔(dān)心你將來(lái)是否有機(jī)會(huì)用你的數(shù)據(jù)積累去做數(shù)據(jù)倉(cāng)庫(kù)。而我的框架目標(biāo)則不同。在持久化與加載兩個(gè)目標(biāo)間我沒(méi)有主次之分。我也沒(méi)有超前到MDA,我的對(duì)象模型仍然基于數(shù)據(jù)庫(kù)的ER設(shè)計(jì),我仍然提供一組非常清晰明了的數(shù)據(jù)庫(kù)視圖。
多表連接查詢
如果需要將多表的連接查詢結(jié)果轉(zhuǎn)換成一個(gè)二維視圖,顯然需要你再定義另一個(gè)視圖實(shí)體對(duì)象,將視圖映射到對(duì)象模型。如果你僅僅是要在一個(gè)對(duì)象實(shí)例的某個(gè)屬性中獲得另外一個(gè)對(duì)象的集合,似乎這不是DAL方式的優(yōu)勢(shì),而反而是ORM的優(yōu)勢(shì)。將多個(gè)對(duì)象所依賴的多個(gè)對(duì)象放到同一個(gè)上下文中,顯然這是最好的一種方式。
統(tǒng)計(jì)查詢
從理論上講,ORM不適合做OLAP,不適合做太多統(tǒng)計(jì)查詢。其實(shí)這一點(diǎn),我的框架已經(jīng)提供了非常好的解決方案,對(duì)Aggregate到面向?qū)ο蟮囊晥D處理得非常好。
開(kāi)發(fā)效率
提高開(kāi)發(fā)效率僅僅是一個(gè)抽象的目標(biāo),具體的手段應(yīng)該是兩個(gè)方面:一是IDE和輔助工具;一是適合將任務(wù)分解成多個(gè)解耦的部分從而可以通過(guò)增加人員來(lái)提升總的開(kāi)發(fā)效率。雖然ORM僅僅是開(kāi)發(fā)環(huán)節(jié)中很小的一部分,但是卻遍布應(yīng)用系統(tǒng)中的每一角落,因而對(duì)開(kāi)發(fā)效率影響較大。除了ORM,難道還有更好的選擇么?
ORM后,原來(lái)精湛的SQL技能變得毫無(wú)用武之地,讓人甚是失落,但這并不是ORM的過(guò)錯(cuò)。