當前,在持久性方面CMP(容器管理持久性)雖然不是很流行的選擇,但它仍然是企業環境中惟一立即可用的持久性框架。
盡管不如hibernate或SQL Maps那麼有吸引力,但領先的應用服務器(如BEA Weblogic)中的CMP2.X實現也提供了非常好的ORM(對象角色建模)問題解決方案。此外,有許多開發人員熟悉EJB,從xdoclet到Eclipse wtp/jst的各種工具也為之提供了完善的支持。
先看看在Weblogic中通過啟用更好的而不是使用默認的緩存策略,能否提高CMP的性能。
大家都知道EJB容器保留了實體Bean的緩存或池,它們通常可在部署描述符中配置。這並不意味著容器從數據庫中加載了特定的bean實例之後就不再訪問數據庫,而是將bean實例保存在池中,令人驚訝的是,許多開發人員沒有意識到這一點。恰恰相反,默認情況下,在每個事務開始的時候,容器調用ejbLoad()同步數據庫中實例的狀態。基本上在每個使用CMP bean的操作中,即使bean在數秒前的前一個事務中已經加載了,容器也會執行SQL選擇語句刷新它。只有在事務中使用CMP實例進行操作時容器才會緩存它們。這就是EJB2.0規范(10.5.9)中描述的所謂“Commit Options A/B/C”。
很明顯對於每次事務都從數據庫重新加載狀態會影響性能。很容易理解為什麼這被設為默認行為——如果多個進程共享數據庫,並且每個進程都能改變數據庫中持久對象的狀態,而該狀態緩存在EJB池中,這種情況下這是最安全的方法。這種情況下可能會丟失更新信息——其他進程的更新信息記錄在數據庫中,而容器使用緩存中可能已經失效的數據將其重寫。但是在事務開始時首先更新數據並不能保證不丟失更新信息,而只能減少丟失的可能性。在數據庫領域中這是一個眾所周知的問題,有兩個方法可以解決該問題。第一種方法就是所謂的“悲觀並發鎖定”,即在事務的持續時間內當在數據庫中記錄時鎖定其他的更新進程(使用“select for update”語句)。但是由於開銷巨大和性能降低,該方法幾乎不使用。第二種方法稱為“樂觀並發鎖定”,在該方法中,數據庫中沒有實際的鎖,但是執行更新的進程會使用某種機制檢測在其讀取和更新之間記錄是否被其他進程更新過。首選的實現方法是使用數據庫表格中的version列(在SQL中更新該列的increment屬性值,包括where子句中的先前的值,然後檢測,如果沒有行被更新的話)。
通過weblogic-cmp-rdbms-jar.xml部署描述符中的verify-columns元素,Weblogic已經在CMP bean中內置樂觀並發支持。對數據庫中的所有(可更新的)表格都使用version列,並通過在weblogic-ejb-jar.xml中的並發策略元素中啟用樂觀並發策略,配置使用CMP,這是一個良好的實踐。
樂觀並發本身並不能帶來任何性能改進,但它是啟用cache-between-transactions功能的必要步驟,該功能沒有啟用樂觀並發時不可用(原因如上所述)。如果EJB緩存中的bean實例已經可用,將cache-between-transactions設置為'true'會導致容器跳過ejbLoad()調用。對於某些類型的應用程序,在短時間內相同的對象會被不同的事務多次訪問,則上述方法將使性能大大提升(在我們的測試中,應用程序性能提升了30%)。
當然,當容器檢測到並發錯誤時,應用程序必須能馬上處理OptimisticConcurrencyException。當拋出OptimisticConcurrencyException(運行時異常的子類型)時,容器從緩存中刪除EJB實例。在集群中,當更新使用樂觀並發的bean時,會廣播通知給其他集群成員以防止樂觀沖突。注意,如果delay-updates -until-end-of-tx設置為'true'(默認值),代碼將在事務提交後才能看到樂觀異常。
一些說明。雖然已經出現了很長一段時間,但Weblogic 7中的cache-between-transactions功能常常不起作用,而8.1以上版本似乎解決了這個問題。我們已經將它應用於生產應用程序中,並對其效果非常滿意。