本篇主要講如何使用一句較複雜的sql來加載整個聚合對象,以達到最小化資料庫連接配接次數。主要是解釋其中的原理。
lazyload及其缺點
相信越來越多的人已經開始使用富領域對象進行領域/業務層的實作了。而目前主流的資料庫依然還是關系型的。這中間的轉換,我們叫它orm。orm的設計中,有一個常用的模式叫作“延遲加載(lazyload)”。基設計思想大緻上是說,不要把所有的資料都加載進記憶體,而是等到真正要使用資料的時候,再把它加載進記憶體。
例如以下這個聚合對象:
(為了和後面的代碼保持一緻,這裡面使用的是gix4項目中真實的類,可能會帶有一些領域特性,望讀者見諒。後面可能會繼續使用此例,現大緻對其進行解釋:其中,pbstype表示一套pbs模闆/類型,一套模闆由許多pbs組成。pbs是project breakdown structure的簡稱,用于對某一個項目進行分解,這裡面一個pbs對象的執行個體其實隻是結構中的一項,應該在後面加上item,不過公司的人都習慣了,是以就延用這個命名。每個pbs有許多屬性(pbsproperty),每個屬性又有許多可選值(pbspropertyoptionalvalue)。)
這個對象,在使用了lazyload對pbstype進行設計之後,客戶程式使用代碼如下:
這裡一共産生了兩次資料通路:擷取pbstype對象、擷取所有在該pbs模闆下的pbs對象清單。此例說明了對集合對象使用lazyload,還有一種比較常用的lazyload:對引用對象的lazyload。如下例:
文章對象引用一個使用者對象來表示其作者。這個外鍵引用的關系,常常也被設計為lazyload。
這一模式已經被廣泛地應用在各種orm架構中,linq to sql、ef等。這些orm架構極大的友善了開發者,不需要再寫煩人的sql,加快了開發效率。但是如果不謹慎使用這一模式,很可能會造成過多的資料庫連接配接次數,導緻性能低下。如果是分布式程式,則會是更耗時的遠端連接配接。如:
這段代碼中一共産生了 11 次資料通路/遠端連接配接,相當的恐怖吧!
如何能保證又能降低連接配接次數,又不使用傳統的table方案呢?這就是今天要說的,一個用于重構的模式:聚合對象sql。
什麼是“聚合sql”
要支援oo的領域對象,同時保證性能,我們的orm就需要做到:擷取對象時,一次性擷取它指定的關系對象(集合/引用);同時,仍然保留lazyload。
例如,當我們加載上述的article及user時,可以調用類似articlerepository.get_with_user的方法,使得一次性加載article及其對應的user。那麼,資料層通路資料庫時,對應的sql應該是把所有的資料都查詢出來,大緻是:
select a.*, u.*
from articles a inner join users u on a.userid = u.id
然後在把整個table映射為article對象清單的過程中,在每一行中讀取并映射出user對象,然後對該行的article對象的owner屬性指派。
對應的,集合對象的一次性加載,要完成對資料的一次性加載,生成類似以下的sql:
select * from pbstype t
left outer join pbs on t.id = pbs.pbstypeid
在應用中,當然不會那麼簡單,不過都是由以上兩種方式組合而成。如,在gix4的項目pbs子產品中使用到這樣的一個sql,其中關于sql的生成及格式定義,接下來我将會做更詳細的解釋:
<a href="http://11011.net/software/vspaste"></a>
今天先把理論寫一下。下一節主要講在目前的gix4系統中,我們是如何引入聚合sql來改善性能的。