Entity beans提供了一個清楚的模型以描述應用程序中持久穩固的事務對象以及它們的設計。在面向對象的模型中,簡單的Java對象通常被直接地描述,但是並不包括事務處理的功能,這通常需要使用事務對象來完成。Entity beans不僅僅考慮到模型中的簡單類型,它也考慮到面向對象模型中的事務模型,它將所有的復雜的工作都交給了bean和容器服務。這使得應用程序可以象使用通常的Java對象那樣來應用它。在保持存儲數據的公開性和靈活性的同時,容器會對entity beans進行優化,而我們只需要考慮如何部署它。 基於Enterprise JavaBeans的開發方案,導致了面向對象的方法的廣泛使用和對entity beans的大量使用。Sun的工程師對entity beans的實際使用作了大量的實踐和研究。這篇文章是對開發中的一些經驗的總結: 探索各種優化方案 對達到最佳的性能和適應性提供規則和建議 討論如何避免一些已知的問題 一、盡可能地使用容器持久化管理 容器持久化管理(CMP)不僅僅可以大量減少編程的工作,而且在容器和容器生成的數據庫訪問代碼中存在許多優化的可能。容器可以訪問內存中的bean的緩沖,這使得它可以監控緩沖中的任何改變。如果緩沖沒有被改變,這可以在提交事務前避免將緩沖存儲到數據庫中。這減少了不必要的調用數據庫的開銷。 另外一個優化的實例是對find方法的調用。根據對一個entity的參考搜尋一個entity,而接下去最可能的情況是使用這個entity。這通常包括兩個數據庫訪問: 在數據庫中尋找記錄並得到主鍵 將記錄的數據讀入到內存中 CMP會進行這樣的優化,只要它認為可以這樣做,它會使用一個數據庫訪問來代替這兩個數據庫訪問,它會在一個數據庫訪問中同時得到主鍵和記錄的數據。 二、編寫同時支持Bean持久化管理和容器持久化管理的代碼 在許多情況下,EJB的作者無法控制EJB將如何被部署,也無法預知部署使用的容器是否支持CMP。同樣的,部署者在目標容器可能會使用bean持久化管理(BMP)。你必須尋找一個方法以執行這個bean以允許BMP部署而不用破壞對更多的CMP機制優化的破壞。一個簡單的方法是將商業邏輯和持久化機制分離開來。在你的CMP類中執行商業邏輯,如果選擇了CMP,它可以被單獨部署。實現持久化的代碼被寫入到BMP類中,它繼承自CMP類。在CMP的超類中保持了所有的商業邏輯,並在BMP子類中增加了數據庫訪問的代碼。這樣的結構如圖1所示。 圖1: 將CMP和BMP分離
這個模型聽起來很容易實現,但是事實上存在一些問題: 對執行類的繼承是不可能的。 這樣做意味著子類必須同時繼承CMP超類和BMP超類。此外,BMP子類還必須繼承它的直接的CMP執行。這導致了多重的類的繼承,就象在圖2中所示的那樣,而這樣的繼承是不被Java程序所允許的。 圖2:Java所不支持的多重繼承
沒有簡單的方法支持改變的持久化執行 這可能是很有用的,例如,可能要執行不同的數據庫提供商或是不同類型的數據庫(例如關系型、對象型或其它類型)所特定的數據庫定義代碼。 要解決這些問題,你可以對目前的模型作一些改變。對所有BMP類抽象出一個輔助類以執行基本的持久化代碼。這樣的輔助類被稱之為數據訪問對象(DAO)。你可以通過對DAO接口的實現產生多個DAO子類,然後在實際應用時選擇合適的DAO子類進行實例化。這就如圖3所求。有許多方法可以選擇合適的DAO子類進行實例化,例如,通過讀取環境變量或是判斷DB類型。 圖3:代表並允許改變的DAO執行
使用這個模型,CMP執行和包含這個執行的DAO可以很方便地被繼承;除了BMP類外,所有的東西都可以被執行。因為這些BMP類包含了一些具有代表性的代碼的框架,對於第一個entity bean它看上去都是差不多的,你可以很容易地將一個bean拷貝到另一個而只需作很小的改動。而最終,你甚至可以使用工具來自動完成這項工作。 盡管你可能需要擴充一個entity bean以重新使用另一個entity bean所提供的邏輯,但是在EJB 1.1的說明書中不允許對一個繼承自home接口的entity類型進行擴充。對一個調用的定位及建立在理論上總是被認為是一種遠程調用。雖然在很多時候你可能需要使用這種子類型(例如,將它們作為一個factory方法),但是不幸地是,這樣的子類型並沒有被提供。這阻礙了對許多遵守EJB 1.1說明書的執行EJB的有用的設計平台的使用。 三、在ejbStores中盡量減少對數據庫的訪問 在使用CMP時,bean完全不受ejbStore的控制,所有的優化工件都是由容器來完成的。容器所提供的CMP優化成了區別容器性能的主要指標之一。 然而在使用BMP來部署bean時,為緩沖區維持一個dirty標志是一種很有效的做法。對緩沖區的所有改變都會對dirty標志進行設置,ejbStore將會對這個標志進行檢查。如果dirty標志未被設置,這意味著緩沖區沒有被改變,ejbStore將不執行所有的開銷巨大的對數據庫的訪問。這個決竅對於那些經常對數據庫進行查詢而不是作真正的改變的bean尤為有效。而這通常組成了一個應用程序中的絕大部分(例如,查找一個表)。 對於這種技術,有一點需要注意。因為dirty是為了數據庫的訪問而設立的,因此它對於BMP的代碼會更適合,也就是說,它並不太適合在CMP代碼中使用。當使用我們前面介紹的執行平台時,它既可以在BMP代碼中進行設置也可以在DAO中進行設置。 因為DAO不會調用任何的商業邏輯,因此它不是設置dirty標志的最合適的地方,最好你還是在BMP代碼中對這個標志進行設置。這可能會使得BMP代碼變得更加復雜,因為你不能在商業邏輯中考慮對超類的授權。 完全從一種科學的觀點來看,EJB的作者並不需要處理系統級的問題,包括對緩沖區的處理。但是不幸的是,使用BMP的EJB 1.1並不提供這種系統優化的選擇,因此bean的作者還是不得不手工對dirty標志進行設置。 四、對於調用的查找和定位的總是使用Reference Cache 無論是對於entity beans還是session beans,Reference Cache都是一項有用的技術。JNDI對例如數據資源、參考bean或者是環境實體這樣的EJB資源的搜尋是相當耗費資源的,而要防止這種多余的搜尋,方法並不太復雜。要解決這個問題,通常的做法是: 將這些references定義為一個實例變量。 在setEntityContext中搜尋它們(對於session bean,相應地使用setSessionContext方法)。 setEntityContext方法僅僅在一個bean被實例化時被調用,因此在這時候搜尋所有需要的references並不太耗費資源。要避免在其它任何方法中搜尋references,特別是對於數據庫訪問方法、ejbLoad和ejbStore。這樣的方法會被頻繁地調用,這會導致大量的時間被無謂地耗費在搜尋的調用上。 定位另一個entity bean的調用也是相當耗費資源的。雖然這樣的調用不一定適合bean初始化時的象setEntityContext這樣的回叫,但是我們還是需要在合適的地方將搜尋的references結果保存在cache中。如果這個reference僅對當前的實體有效,你需要在實例被重新激活以描述另一個實體前清除references。這需要在ejbActivate方法中完成。 五、總是准備好你的SQL語句 在所有代碼中涉及到對關系型數據庫的訪問總是使用SQL是一種有效的優化手段。因為對於絕大多數的當前EJB執行都使用關系型數據庫,對於bean的作者需要編寫數據庫訪問代碼的EJB部署,這條規則是非常有效的。 因為每一個SQL語句都必須由數據庫進行處理,數據庫需要花費時間在執行SQL語句前對其進行編譯。然而,對於一個性能良好的關系型數據庫,會將語句和編譯結果保存在cache中,對於新的SQL語句,它會將其與cache進行比照,以獲得它的編譯結果。然而,為了得用這種優化手段,新的語句必須完成與老的語句匹配。 未准備好的語句 對於未准備好的語句,數據和語句被傳遞到同一個字符串中,盡管語句可能與後來的調用完成一樣,但是數據不匹配,這使得這種優化手段失去作用。 已准備好的語句 對於已准備好的語句,只將沒有數據的語句傳遞到數據庫,這使用從cache中獲得編譯結果成為可能。 在使用語句時,數據在其後被傳遞,而語句得以執行。通常地,這個語句必須經過編譯,但是對於後來的語句,它可能與cache匹配,而不需要再被重新編譯。這項技術具有較高的語句cache命中率(接近100%),可以最大限度地減少語句的編譯。對於小型的數據庫訪問,這可以減少將近90%的執行時間。 六、完全地關閉所有的語句 當在BMP執行中處理數據庫訪問代碼時,在完成數據庫訪問調用後要記得關閉語句。每一個未關閉的語句對應著數據庫一個開啟的光標。(盡管垃圾搜集器最後會對開啟的語句進行檢查,並在垃圾搜集(GC)時間將其關閉,但是你不能控制這樣的一個時間。)保持語句開啟會導致數據庫過多的開啟光標,這會浪費數據庫的資源,而這些資源本來是可以用來改善數據庫性能的。 同樣,關閉語句時必須確保捕獲所有的另外。在關閉一個語句時產生的另外必須不會導致另一條語句的被忽略或是保持開啟狀態。 七、避免死鎖 應用程序不通過代碼直接控制調用ejbStore(或其它CMP的等價物)的時間。何時執行這個調用是由容器決定的,這通常是在一個事務的最後進行。 如果在一個事務中涉及到一個復合的entity beans或者是復合的實體,調用ejbStore的次序並沒有被定義。這也就意味著用戶沒有控制與那些實體相關的數據庫記錄的訪問/鎖定的次序。當涉及到多個數據表或數據行時,這很可能會導致死鎖。 樂觀地看,理想的情況是在容器會控制EJB中的訪問和鎖定,只使得容器會解決死鎖問題,而開發者和部署者將不必擔心這一類的問題。但是,不幸的是,目前可用的商業應用服務器很少有能很好地處理數據庫的鎖定問題的,對於復雜的entity bean部署,部署者將不得不自行解決死鎖問題。 可適用的規則是(至少在我們編寫這篇文檔時是這樣):我們可以假定容器會按照事務中最初調用事務方法的同樣的次序來調用數據庫訪問。舉例來說:如果在一個entity bean EB1中有一個名為m1的事務方法,而在另一個entity bean EB2中有一個名為m2的事務方法。如果在同一個事務中,EB1.m1在EB2.m2之前被訪問,你也可以假定EB1.ejbLoad會在EB2.ejbLoad被調用,同樣,EB1.ejbStore會在EB2.ejbStore之前被調用。這意味著,與EB1相應的實體或數據庫記錄會在EB2之前被鎖定。為了避免死鎖,必須確保在整個應用程序中,對於任何事務EB1總是在EB2之前被調用。 我們可以肯定,應用程序服務器將會變得越來越聰明,他將會知道如何組織對數據庫的訪問,而開發者和部署者將不再去小心翼翼地處理訪問次序。然而,就象在下面這個例子中那樣嚴格處理調用順序的代碼至少可以保證在目前的服務器上更好地工作。 Vehicle和Car bean的源代碼可以從這裡
下載,它使用了這兒的絕大多數的規則,你也可以將其作為將來entity bean開發的基礎和模板。(要下載jar文件,右擊這個連接,並選擇“目標另存為”。) 展望 當entity bean的開發者和部署者有效地利用這些規則後,可以大大地提高bean的性能和適應性,使得它們可以適應不同的持久存儲類型。這給了部署者以更多的選擇。也就是說 -- 一次編寫,處處使用,而且是有效率地處處使用。