POJO 式開(kāi)發(fā) POJO
POJO 就是簡(jiǎn)單 java 對象,不實(shí)現任何特殊接口。
POJO 這一名字由 Fower 、 Rebbecca 、 Parsos 、 Josh MacKenzie(Foeler POJO) 發(fā)明,目的地是為了給普通 Java 對象取個(gè)令人興奮的、過(guò)目不忘的名字。
早期 EJB 及其存在的問(wèn)題 及其存在的問(wèn)題
EJB1.0 版本發(fā)布于 1998 年,它提供了兩種企業(yè) bean :會(huì )話(huà) bean 和實(shí)體 bean 。會(huì )話(huà) bean 便是無(wú)狀態(tài)服務(wù)或與客戶(hù)端之間的有狀態(tài)會(huì )話(huà)。實(shí)體 bean 表示數據庫里的數據,最初意在實(shí)現業(yè)務(wù)對象。
EHB2 提煉了 EJB 編程模型。不僅增加了支持由容器管理的關(guān)系增強型實(shí)體 bean ,還新增了消息驅動(dòng) bean( 負責處理 Java Message Service 或 JMS ,消息 ) 。
EJB 存在的問(wèn)題 存在的問(wèn)題
盡管有很多書(shū)幫助開(kāi)發(fā)人員對付 EJB ,并學(xué)會(huì )如何有效的使用 EJB ,但是 EJB 的;兩個(gè)主要問(wèn)題并沒(méi)有直接解決。
第一,
EJB 鼓勵開(kāi)發(fā)人員編寫(xiě)過(guò)程式 過(guò)程式應用程序 應用程序 第二,
使用 EJB 開(kāi)發(fā) 相當麻煩
過(guò)程式設計的缺點(diǎn):
過(guò)程式設計的缺點(diǎn):
對業(yè)務(wù)邏輯的組織方式主要有兩種:過(guò)程式或面向對象。過(guò)程式方式以函數為單元組織代碼,這些函數操作單獨的簡(jiǎn)單數據對象。在過(guò)程式架構中,數據結構遍布各處,并作為參數傳入函數,或返回給調用函數。
數據與操作之間的關(guān)系非常松散 ,并且完全由開(kāi)發(fā)人員自己維護。在面向對象語(yǔ)言出現之前,這種編程方式主導了軟件開(kāi)發(fā)。
與之相比,面向對象方法則以對象為單元組織代碼,這些對象 具有狀態(tài)和行為 ,并與其他對象協(xié)作。
數據結構和操作定義在一個(gè)語(yǔ)言構造單元內,數據和對數據操作并存于其中。數據和操作之間的關(guān)系 ( 和狀態(tài) ) 由語(yǔ)言本省維護 。與過(guò)程式設計相比,面向對象設計更易理解、維護、擴展和測試。
如果業(yè)務(wù)邏輯夠簡(jiǎn)單,過(guò)程式設計方法倒也不成問(wèn)題,但是業(yè)務(wù)邏輯總有變得愈加復雜的趨勢。一旦需求改變,業(yè)務(wù)邏輯就必須實(shí)現新的特性, EJB 的代碼量會(huì )不斷增加。
EJB2 在一定程度上就是鼓勵人們編寫(xiě)過(guò)程式代碼, 實(shí)現新行為時(shí),不必像設計真正的對象模型那樣費心地識別類(lèi)并賦予其職責。相反,你可以編寫(xiě)一個(gè)新的會(huì )話(huà) bean 方法或在現有方法里添加代碼 。
這鐘過(guò)程式的設計方法,有些開(kāi)發(fā)人員仍把持久對象簡(jiǎn)單的視為一種向數據庫存取數據和編寫(xiě)過(guò)程式業(yè)務(wù)邏輯方法,這就是所謂的貧血模型
EJB 開(kāi)發(fā)的麻煩:
開(kāi)發(fā)的麻煩:
n
你必須面對惱人而長(cháng)的編輯 - 編譯 - 調試周期 n
你得面對關(guān)注點(diǎn)缺少分離的顯示 n
你必須編寫(xiě)大量的代碼才能實(shí)現一個(gè) EJB n
你必須編寫(xiě)數據傳輸對象 (DTO)
用 POJO 開(kāi)發(fā) 開(kāi)發(fā)
用 POJO 進(jìn)行開(kāi)發(fā),僅有 POJO 本身還是不夠的。在企業(yè)應用程序里,你還需要諸如事務(wù)管理、安全和持久化等服務(wù),此前這些服務(wù)由 EJB 容器提供?,F在的解決方案是使用所謂“輕量級”框架來(lái)代替 J2EE STACK 里的一些“重量級”部分。主要是 4 種輕量級框架:
Hibernate 、 JDO 、 Ibatis 和 Spring 。
這些技術(shù)的主要特征在于他們都是非侵入式的。它們提供事務(wù)和持久化時(shí)并不要求應用程序類(lèi)實(shí)現任何特殊接口。甚至當應用程序的類(lèi)需要運行在事務(wù)里或者持久化的時(shí)候,它們仍是 POJO 。
典型的 典型的 EJB 和 POJO 方法比較 方法比較
典型的 EJB 方法 POJO 方法 組織 過(guò)程式業(yè)務(wù)邏輯 面向對象設計 面向對象設計 實(shí)現 基于 EJB POJO 數據庫訪(fǎng)問(wèn) JDBC/SQL 或實(shí)體 Bean 持久層框架 持久層框架 返回給表示層的數據 DTO 業(yè)務(wù)對象 業(yè)務(wù)對象 事務(wù)管理 EJB 容器管理的事務(wù) Spring 框架 框架 應用程序組裝 顯示的 JNDI 查詢(xún) 依賴(lài)注入 依賴(lài)注入
l
面向對象設計
整個(gè)設計更容易理解和維護 更易于測試 更易擴展
l
使用 POJO
開(kāi)發(fā)更加容易 更加快捷 可移植性增強
l
持久化 POJO
使用 JDO 和 Hibernate 提供透明持久化,這意味著(zhù)類(lèi)不會(huì )意識到它們是持久的。應用程序只需要調用持久層框架 API 保存、查詢(xún)和刪除持久對象、而且對測試也很方便。
l
消除 DTO
DTO 又稱(chēng)為值對象( value object )。
DTO 只是一個(gè)由成員變量組成的簡(jiǎn)單行為對象,用于從業(yè)務(wù)層向表示層返回數據。這是由于表示層無(wú)法高效地訪(fǎng)問(wèn) EJB2 實(shí)體 bean ,因此 EJB 程序需要 DTO 。
向表示層返回 Hibernate 、 JDO 、 EJB3 對象有兩種方式。一種選擇是表示層返回仍持久地的對象。另一種做法是讓業(yè)務(wù)層返回脫管對象。
l
是 POJO 具有事務(wù)性
用 spring 管理事務(wù)。對測試也很方便。
系統設計時(shí)需要考慮的五大因素:
系統設計時(shí)需要考慮的五大因素:
1 、如何組織業(yè)務(wù)邏輯 2 、如何封裝業(yè)務(wù)邏輯,以及暴露給表示層及其他客戶(hù)程序調用的接口
3 、如何訪(fǎng)問(wèn)數據庫 4 、如何處理短事務(wù)中的并發(fā) 5 、如何處理長(cháng)期運行事務(wù)中的并發(fā) 決策 選項 業(yè)務(wù)邏輯封裝 EJB Session Façade 模式 POJO Façade 模式 Exposed Domain Model 模式 數據庫訪(fǎng)問(wèn) 直接使用 JDBC iBATIS Hibernate JDO 數據庫事務(wù)中的并發(fā) 不理睬該問(wèn)題 悲觀(guān)鎖 樂(lè )觀(guān)鎖 可串行化隔離級別 長(cháng)期運行事務(wù)中的并發(fā) 不理睬該問(wèn)題 Pessimistic Offline Lock 模式 Optimistic Offline Lock 模式
以上 5 個(gè)決策每個(gè)都有多種選項?;?EJB 的設計,它由會(huì )話(huà) bean 實(shí)現的過(guò)程式代碼組成,并使用 JDBC 訪(fǎng)問(wèn)數據庫。相比之下,基于 POJO 的設計由對象模型組成,通過(guò) JDO 、Hibernate 等 O/R 框架映射到數據庫,并用使用 Spring 進(jìn)行事務(wù)管理的 POJO façade 進(jìn)行封裝。每種選項都有優(yōu)缺點(diǎn),這也決定了它只能適用于某種具體情況。每種選項都會(huì )在一個(gè)或多個(gè)方面做出一定的妥協(xié),包括功能性、開(kāi)發(fā)難易度、可維護性和可用性等,需要自己的應用程序作出最佳選擇。
封裝業(yè)務(wù)邏輯 封裝業(yè)務(wù)邏輯
業(yè)務(wù)邏輯的接口由那些可被表示層調用的類(lèi)型(type )和方法(method )
組成。接口設計的要點(diǎn)是應當封裝多少業(yè)務(wù)邏輯的實(shí)現,并對表示層不可見(jiàn)。封裝隱藏了業(yè)務(wù)邏輯的實(shí)現細節,可以防止表示層受業(yè)務(wù)邏輯變化的影響,從而提升可 維護性。同時(shí)還需考慮怎樣處理事務(wù)、安全性和遠程調用等問(wèn)題,因為通常這些都是業(yè)務(wù)邏輯接口代碼的職責。一般來(lái)說(shuō),業(yè)務(wù)層接口應保證對業(yè)務(wù)層的每個(gè)調用都 在事務(wù)中執行,以便保證數據庫的數據一致性。同樣的,業(yè)務(wù)層接口還要驗證調用者是否有足夠的權限來(lái)調用某種業(yè)務(wù)方法。此外,它還要負責處理某些遠程客戶(hù) 端。
若存在表示層遠程訪(fǎng)問(wèn)業(yè)務(wù)層 API 的情況,盡量將業(yè)務(wù)層的 API 設計成粗粒度的,這樣對業(yè)務(wù)層的調用越少,數據庫事務(wù)數量就越少,內存緩存對象的機會(huì )就越多。還能減少網(wǎng)絡(luò )來(lái)回傳輸次數。
單個(gè)數據庫 事務(wù)中的并發(fā) 事務(wù)中的并發(fā) 完全事務(wù)腳本、樂(lè )觀(guān)(optimistic )鎖、悲觀(guān)(pessimistic )鎖 )鎖
完全事務(wù)腳本 是一種解決方案是使用完全和其他事務(wù)隔離的事務(wù),用數據庫的話(huà)來(lái)說(shuō),就是隔離級別為 serializable (串行化)的事務(wù)(悲觀(guān)鎖的另一種方案)。數據庫保證:執行多個(gè) serializable 事務(wù)的結果和一個(gè)個(gè)串行執行它們的結果一樣。serializable 事務(wù)避免了更新丟失、讀取不一致等問(wèn)題。一些數據庫還提供了repeatable read (能夠保持一致的重復讀?。┖?Read committed 的 隔離級別,完全隔離事務(wù)有兩個(gè)主要優(yōu)點(diǎn):一是使用簡(jiǎn)單;二是避免了很多并發(fā)問(wèn)題,包括修改丟失和讀取不一致的問(wèn)題。完全隔離事務(wù)的主要缺點(diǎn)是開(kāi)銷(xiāo)太大,降 低了性能和規模擴展性,不管有沒(méi)有并發(fā)更新,都需要額外開(kāi)銷(xiāo)。而且,由于死鎖和其他并發(fā)相關(guān)問(wèn)題,完全隔離事務(wù)比低隔離級別的事務(wù)的失敗頻率更高。用 serializable 或者 repeatable read 隔離級別的事務(wù),這種方案不需要數據庫模式變化。因為數據庫的 serializable 事務(wù)機制能夠處理并發(fā)更新,所以不需要用語(yǔ)句來(lái)鎖住記錄,也不需要維護版本號。在 spring 中可以通過(guò)配置數據源的屬性“defaultTransactionIsolation ”為“SERIALIZABLE ”來(lái)實(shí)現。
樂(lè )觀(guān)鎖的工作原理 樂(lè )觀(guān)鎖的工作原理 是讓?xiě)贸绦驒z查它即將更新的數據是否已被另一個(gè)事務(wù)修改(自該數據上次讀取以來(lái))。實(shí)現樂(lè )觀(guān)鎖的一種常見(jiàn)做法是在每個(gè)表里添加一個(gè)版本字段,每次應用程序更新數據表記錄時(shí)就增加這個(gè)版本字段。每個(gè) UPDATE 語(yǔ)句中的 WHERE 子句會(huì )根據上次讀取的值來(lái)判斷這個(gè)版本號是否改變。使用諸如JDO 和 Hibernate 的持久層構架時(shí),實(shí)現樂(lè )觀(guān)鎖更為容易,因為它們已將樂(lè )觀(guān)鎖作為配置選項提供。
應用程序或持久層框架有三種方法可以判斷一條記錄自從上次讀取出來(lái)后是否被修改過(guò)。
第一種方法是 用一個(gè) version (版本)字段來(lái)跟蹤記錄修改狀況 ,每次修改,version 都會(huì )遞增。事務(wù)只需要把原來(lái)讀出的 version 和當前 version 進(jìn)行比較,就可以判斷一條記錄是否被修改過(guò)。應用程序檢查和修改 version 字段是比較簡(jiǎn)單的做法,通常也是最好的做法。
第二種方法是 用時(shí)間戳字段,每次應用程序修改數據,時(shí)間戳也會(huì )更新 。
事務(wù)只需要把原來(lái)讀出的時(shí)間戳和當前時(shí)間戳進(jìn)行比較,就可以判斷一條記錄是否被修改過(guò)。這個(gè)表結構也很容易實(shí)現,尤其是這種情況下,數據表經(jīng)常已經(jīng)有一個(gè) 時(shí)間戳字段來(lái)記錄用戶(hù)修改記錄的時(shí)間。然而,時(shí)間戳的問(wèn)題是,如果兩個(gè)修改操作之間的時(shí)間差小于時(shí)鐘最小單位,那么一個(gè)事務(wù)可能覆蓋另一個(gè)事務(wù)的修改。所 以,只有在無(wú)法增加 version 字段的遺留系統中,才應該使用時(shí)間戳,否則,盡量使用version (版本)。
第三種方法是 把上次讀出的字段值和現有字段值進(jìn)行比較 。這種方法最大的好處是,不需要引入 version 或者時(shí)間戳字段,所以可以用在遺留系統中。這個(gè)方法的一個(gè)缺點(diǎn)是使得 SQL UPDATE 更加復雜,因為 WHERE 子句里面包含所有的字段的條件(具體原因我們后面會(huì )詳述)。還必須處理 null 字段,可能比較復雜。比如,有一次我發(fā)現,一個(gè)持久層框架不能正確比較空字符串,因為 Oracle 把空字符串認為是 null ,這和 Java 不一樣。我們在數據表里面增加了一個(gè)版本字段,解決了這個(gè)問(wèn)題。
第三種方法的另一個(gè)缺點(diǎn)是,浮點(diǎn)數字段不能精確比較,浮點(diǎn)數字段的修改可能發(fā)現不了。由于這些問(wèn)題,應用程序只有在別無(wú)選擇,無(wú)法應用版本和時(shí)間戳的情況下,才應該使用這種方法。
悲觀(guān)鎖的工作原理 悲觀(guān)鎖的工作原理 是 當讀取某些記錄時(shí),事務(wù)先鎖住這些記錄,這樣可以防止其他事務(wù)訪(fǎng)問(wèn)這些數據記錄。具體細節要視數據庫而定,不過(guò)糟糕的是,并非所有數據庫都支持悲觀(guān)鎖。如果數據庫支持悲觀(guān)鎖,在直接執行 SQL 語(yǔ)句的應用程序中,實(shí)現悲觀(guān)鎖非常容易。在 JDO 或 Hibernate 應用程序中使用悲觀(guān)鎖更為容易。JDO 以配置選項的方式提供悲觀(guān)鎖,而 Hibernate 則提供一個(gè)簡(jiǎn)單實(shí)用的 API ,來(lái)鎖定對象。
獲取鎖的機制是數據庫相關(guān)的,并非所有數據庫都支持。在 Oracle 數據庫中,應用程序通過(guò) SELECT FOR UPDATE 語(yǔ)句鎖住選出的記錄,從而實(shí)現悲觀(guān)鎖。如果已經(jīng)有事務(wù)鎖住記錄,執行 SELECT FOR UPDATE 語(yǔ)句的事務(wù)就會(huì )被。如果其他事務(wù)更新、刪除或者試圖用 SELECT FOR UPDATE 選取這些記錄,阻塞情況就會(huì )發(fā)生。事務(wù)一直阻塞,直到事務(wù)提交或者回滾。如果事務(wù)不想等待,可以采用SELECT FOR UPDATE NO WAIT ,如果不能立即鎖住記錄,就返回 ORA-00054 錯誤。你還可以用 SELECT FOR UPDATE WAIT 來(lái)指定等待時(shí)間。要注意的是:一些
數據庫對 SELECT FOR UPDATE 的用法有限制。例如,Oracle 里面,SELECT FOR UPDATE 只能用在頂層 SQL ,而不能嵌在子查詢(xún)里面。還有一些 SQL 特性不能和SELECT FOR UPDATE 一起使用。這些特性包括 DISTINCT ,集合統計函數(max 、min 、sum 、count ),GROUP BY 。SELECT FOR UPDATE 也不能用在某些類(lèi)型的view 和嵌套的 SELECT 里面。
處理長(cháng)事務(wù)中的并發(fā) 處理長(cháng)事務(wù)中的并發(fā) 樂(lè )觀(guān)離線(xiàn)鎖(Optimistic Offline Lock) 模式、悲觀(guān)離線(xiàn)鎖(Pessimistic Offline Lock ) 模式 模式
樂(lè )觀(guān)離線(xiàn)鎖模式 是 擴展此前描述的樂(lè )觀(guān)鎖機制,在編輯過(guò)程的最后一個(gè)數據庫事務(wù)里,檢查數據自最初讀取后并未改變。例如,你可以使用共享數據表里的版本號字段實(shí)現這一機制。
在編輯過(guò)程開(kāi)始時(shí),應用程序先將版本號存儲在會(huì )話(huà)狀態(tài)里。然后,當用戶(hù)保存其更改時(shí),應用程序進(jìn)行檢查,保證會(huì )話(huà)狀態(tài)里保存的版本號和數據庫中的版本號一 致. 由于樂(lè )觀(guān)離線(xiàn)鎖模式只在用戶(hù)要保存修改后的數據時(shí)才進(jìn)行檢測,在實(shí)現諸如“修改訂單”用例時(shí),要用戶(hù)放棄好幾分鐘才完成的操作,用戶(hù)肯定會(huì )非常惱火,此時(shí)更好的選擇是使用悲觀(guān)離線(xiàn)鎖 . 悲觀(guān)離線(xiàn)鎖模式 是在編輯過(guò)程開(kāi)始之初,就鎖定共享數據,以防止其他用戶(hù)編輯該共享數據。這種方式與此前描述的悲觀(guān)鎖機制類(lèi)似,只不過(guò)這里鎖由應用程序而不是數據庫實(shí)現。由于每次只有一個(gè)用戶(hù)能編輯共享數據,因此可保證用戶(hù)能保存自己的修改。
通知并發(fā)更新失敗 通知并發(fā)更新失敗 當 DAO 或者數據庫認為當前的兩個(gè)事務(wù)不能并發(fā)運行時(shí),就會(huì )發(fā)生并發(fā)失敗。DAO 可以允許 JDBC SQLException 傳播到 DAO 的調用者。不過(guò),有兩個(gè)原因說(shuō)明這種做法不是一個(gè)好主意。拋出 SQLException 的第一個(gè)問(wèn)題是 JDBC 相關(guān)。應用程序也可以使用 JDO 這樣的持久層,但是拋出其他的非 JDBC 異常。理想情況下,更高層的應用程序組件不應該知道底層訪(fǎng)問(wèn)了數據庫。
SQLException 的另一個(gè)問(wèn)題是,SQLException 是一個(gè) checked exception (非 Runtime Exception ,需要顯式聲明或者捕獲的異常),需要 DAO 調用者或者捕捉 SQLException ,或者在方法簽名上聲明繼續拋出 SQLException ,這種情況下,調用者代碼會(huì )變得雜亂。使用unchecked exceptions (Runtime Exception ,不需要顯式聲明或者處理的異常)來(lái)報告并發(fā)失敗要好得多。