編者按:簡單而有技巧地解決問題總是比蠻力解決要好。這就是最近出版的Better, Faster, Lighter java一書中所遵循的原則。從這本書的第八章中節選出來的這個關于SPRing的兩部分的系列文章,也體現了書的作者BrUCe Tate和Justin Gehtland所信奉的這個原則。本周Bruce和Justin將繼續第一部分,向Pet Store示例添加持久性,并探討Spring框架中的持久性邏輯方面。
添加持久性通常,我們都寧愿處理對象而不愿處理關系。Spring也有一個用于透明持久性的模型。jPetStore使用Spring的OR映射層,該層提供了許多預包裝的選項?,F在Spring支持針對基本JDBC DAO、Hibernate和JDO的映射層。這個例子使用一個稱為iBATIS SQL Maps的DAO框架來實現Spring DAO層。
模型例8-3. Product.java
public class Product implements Serializable { private String productId; private String categoryId; private String name; private String description; public String getProductId( ) { return productId; } public void setProductId(String productId) { this.productId = productId.trim( ); } public String getCategoryId( ) { return categoryId; } public void setCategoryId(String categoryId) { this.categoryId = categoryId; } public String getName( ) { return name; } public void setName(String name) { this.name = name; } public String getDescription( ) { return description; } public void setDescription(String description) { this.description = description; } public String toString( ) { return getName( ); }}
這里沒什么特別的。它完全由屬性組成,通過getter、setter以及一個實用方法toString進行訪問。如果您看一下jPetStore應用程序,就會發現域中其他每個持久性對象都有類似的類:Account、Order、Category、Item和LineItem。
映射例8-4. Product.xml
[1][2] [3] [4] select PRODUCTID, NAME, DESCN, CATEGORY from PRODUCT where PRODUCTID = #value# [5]select PRODUCTID, NAME, DESCN, CATEGORY from PRODUCT where CATEGORY = #value# [6]select PRODUCTID, NAME, DESCN, CATEGORY from PRODUCT lower(name) like #keyWordList[]# OR lower(category) like #keywordList[]# OR lower(descn) like #keywordList[]#
下面是注釋的含義:
[1]每個映射文件對應于一個域對象。本例中的域對象關系到為該DAO所指定的查詢的返回類型。
[2]關于DAO層的其他信息,比如緩存策略,也屬于映射文件。這里,iBatis將緩存維持24小時,然后刷新。
[3]當然了,每個查詢都返回一個產品。該映射將結果集中的每一列與產品中的一個字段聯系起來。
[4]該SQL語句找出給定productID的產品。
[5]該SQL語句找出一個類別中的所有產品。它返回一個產品列表。
[6]該SQL語句是動態的。IBatis迭代關鍵字列表,形成一個動態查詢。
到目前為止,您已經看到了Product及其映射的域模型,包括查詢。您已經上路了。
DAO接口例8-5. ProductDAO.java
public interface ProductDao { List getProductListByCategory(String categoryId) throws DataaccessException; List searchProductList(String keywords) throws DataAccessException; Product getProduct(String productId) throws DataAccessException;}
這非常簡單??梢钥吹?,定義在映射中的每個查詢都有一個接口。具體來說,getProduct接口通過ID查找產品,getProductListByCategory接口返回一個類別中的所有產品,還有一個基于關鍵字的動態查詢接口?,F在,DAO拋出Spring異常,任何使用DAO的邏輯都有一致的異常,即使您之后決定更改實現。
DAO實現例8-6. SqlMapProductDao.java
public class SqlMapProductDao extends SqlMapDaoSupport implements ProductDao {[1] public List getProductListByCategory(String categoryId) throws DataAccessException { return getSqlMapTemplate( ).executeQueryForList("getProductListByCategory", }[1] public Product getProduct(String productId) throws DataAccessException { return (Product) getSqlMapTemplate( ).executeQueryForObject("getProduct", productId); }[1] public List searchProductList(String keywords) throws DataAccessException { Object parameterObject = new ProductSearch(keywords); return getSqlMapTemplate( ).executeQueryForList("searchProductList", parameterObject); } /* Inner Classes */[2] public static class ProductSearch { private List keywordList = new ArrayList( ); public ProductSearch(String keywords) { StringTokenizer splitter = new StringTokenizer(keywords, " ", false); while (splitter.hasMoreTokens( )) { this.keywordList.add("%" + splitter.nextToken( ) + "%"); } } public List getKeywordList( ) { return keywordList; } } }
下面是注釋的含義:
[1]這些方法提供了接口的SQL Map實現。其他的實現可能使用Hibernate、JDO或straight JDBC。在本例中,getTemplate調用指示Spring獲取支持iBATIS SQL Map的模板,并使用該框架執行適當的查詢。
[2]我并非特別喜歡內部類,但是這是通常實現關鍵字查詢的方法。在本例中,內部類通過實現getKeywordList來支持searchProductList方法。使用余下的DAO實現,內部類有助于組織代碼基址,將所有的支持都保存在一個地方。
現在我們看到了映射、模型和DAO。有了一個完全持久性的模型。接下來,我們要使用代碼訪問DAO層。jPetStore通過一個外觀層來接受所有的DAO訪問。
通過外觀使用模型
在本例中,外觀是一個非常薄的層,它環繞所有的DAO。通過配置和方法攔截器,Spring使外觀具有聲明式事務支持。在本例中,外觀存在于兩個部分中:接口和實現。接口允許不影響其他代碼地更改外觀的實現。例8-7顯示了接口。
例8-7. PetStoreFacade.java
public interface PetStoreFacade { Account getAccount(String username); Account getAccount(String username, String password); void insertAccount(Account account); void updateAccount(Account account); List getUsernameList( ); List getCategoryList( ); Category getCategory(String categoryId); List getProductListByCategory(String categoryId); List searchProductList(String keywords); Product getProduct(String productId); List getItemListByProduct(String productId); Item getItem(String itemId); boolean isItemInStock(String itemId); void insertOrder(Order order); Order getOrder(int orderId); List getOrdersByUsername(String username);}
可以將該接口視為所有創建、讀、更新或刪除任何Pet Store對象的方法的統一列表。注意,您不能看到來自所有DAO的方法,而只能看到我們希望公開給外界的方法。還要注意接口中的命名一致性。這很重要,因為,在我們的配置文件中,您可以看到用來傳播以get、search、update或insert開頭的方法的事務支持。
實現調用底層的DAO來執行適當的操作。必須在接口中實現所有的方法。例8-8是與ProductDAO相關的方法的實現。
例8-8. Excerpt fromPetStoreImpl.java
[1] private ProductDao productDao; ... public void setProductDao(ProductDao productDao) { this.productDao = productDao; } ...[2] public List getProductListByCategory(String categoryId) { return this.productDao.getProductListByCategory(categoryId); } public List searchProductList(String keywords) { return this.productDao.searchProductList(keywords); } ...
下面是注釋的含義:
[1]顯示DAO訪問(包括粗體文本)。Spring框架使用反射將DAO插入到外觀中。這意味著外觀必須支持一個set方法和一個私有成員變量。
[2]提供數據訪問的方法使用底層的DAO來做實際工作(包括對粗體文本的處理)。
當然了,我沒有給出接口的所有方法的實現。只顯示了與產品相關的方法。它們分為兩部分。
首先,應用程序上下文將每個DAO連接到外觀。Spring使用反射和bean工廠來創建產品DAO,并使用setProductDAO API來對其進行設置。為此,外觀需要一個變量來保存DAO以及一個set方法來通過反射訪問它。
其次,實現非常簡單。外觀只將請求傳遞給下面的模型層。但是,最終的實現要強大得多。外觀用作一個支持聲明式事務的EJB會話bean。通過配置,POJO變成了一個聲明式事務協調程序!它也是整個數據庫層的中心控制點。余下的工作就是配置DAO層了。
配置DAO層例8-9. dataAccessContext-local.xml
[1] [2] /WEB-INF/jdbc.properties [3] [4] [5] classpath:/sql-map-config.xml
下面是注釋的含義:
[1]這個bean處理JDBC配置。JDBC配置屬性在一個標準的JDBC配置文件中,這使得它們的維護和讀取更為容易。Spring提供了一個配置類,使得用戶可以輕松地讀取屬性文件,而無需將其轉換為XML。
[2]這里是數據源。它是一個標準的J2EE數據源。許多J2EE應用程序或框架都將應用程序或框架硬連接到一個給定的數據源上。而對其進行配置,就可以輕松地選擇自己的數據源(以及池化策略)了。
[3]applicationContext.xml配置設置事務策略。該配置指定實現。該應用程序使用數據源事務管理器,它通過JDBC將事務管理委托給數據庫(使用提交和回滾)。
[4]必須對構建DAO的iBATIS SQL Map實用工具進行配置。這里配置了。
[5]最后,我們看到了實際的DAO配置。不知道您是否記得,applicationContext.xml文件通過名稱引用每個bean。
配置不止從模型或視圖中解除了持久性層的耦合。還從持久性層解除事務管理的耦合,將事務策略與實現分離開來,并隔離數據源。下面我們來看除了配置之外的眾多好處。
優點數據源配置
由Spring框架處理。您不必管理一整套單元素集合(針對會話管理)、數據源等等。您還可以將重要決定(如:數據源類型)延遲到部署時。
連接處理
Spring框架管理所有的連接處理。一個最常見的JDBC錯誤就是連接泄漏。如果不對連接的關閉非常小心,特別是在異常條件中,應用程序就很容易失去穩定而崩潰。
特化的異常
許多框架將SQL異常傳至頂層。它們通常都將可能特化到您自己的RDBMS中的代碼內置,這使得難以編碼可移植的應用程序。Spring有它自己的異常層次結構,從而使您遠離這些問題。此外,如果換為使用Hibernate或JDO方法,不需要對異常處理做任何更改。
到現在為止,我們有了一個整潔、透明的域模型和一個獨立于數據庫的、不怎么需要維護的服務層。每個層都巧妙地封裝起來了??赐炅撕笈_邏輯,現在我們來為應用程序添加一個用戶界面。
呈現如果曾經使用過Struts,您就應該很熟悉MVC Web的基本范例。圖8-4顯示了它的工作機制??刂破骰旧咸幚砹怂袕妮斎胍晥D傳入的請求。如果輸入請求是一個提交的表單,控制器就調用一個(由程序員創建和配置的)業務驗證例程,并根據結果向用戶發送相關的錯誤視圖或者成功視圖。
圖8-4. MVC Web框架的工作機制類似于Struts
考慮搜索一個類別中的產品的Html頁面,以及根據關鍵字搜索產品的HTML頁面。配置文件需要兩個應用程序上下文文件的控制器。每個記錄項指定一個控制器以及模型對象,如例8-10所示。
例8-10. web.xml節選
還記得吧,所有對數據層的訪問都是通過外觀進行的。是的,這些bean ID記錄項指定了外觀,稱為petstore。應用程序中的每個表單都以同樣的方式工作。我們進一步來看看searchProducts的控制器。
例8-11. SearchProductsController.java
public class SearchProductsController implements Controller {[1] private PetStoreFacade petStore; public void setPetStore(PetStoreFacade petStore) { this.petStore = petStore; }[2] public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {[3] if (request.getParameter("search") != null) { String keyword = request.getParameter("keyword"); if (keyword == null keyword.length( ) == 0) { return new ModelAndView("Error", "message", "Please enter a keyword to search for, then press the search button."); } else {[4] PagedListHolder productList = new PagedListHolder( this.petStore.searchProductList(keyword.toLowerCase( ))); productList.setPageSize(4); request.getsession( ).setAttribute( "SearchProductsController_productList", productList);[5] return new ModelAndView("SearchProducts", "productList", productList); } } else {[6] String page = request.getParameter("page"); PagedListHolder productList = (PagedListHolder) request.getSession( ). getAttribute("SearchProductsController_productList"); if ("next".equals(page)) { productList.nextPage( ); } else if ("previous".equals(page)) { productList.previousPage( ); } return new ModelAndView("SearchProducts", "productList", productList); } } }
下面是注釋的含義:
[1]每個控制器都有對適當的域模型的控制權。在本例中,視圖自然是通過外觀來訪問模型的。
[2]控制器有一個類似于servlet但是實際上不是servlet的接口。因此用戶請求通過一個調度servlet傳入,該servlet將請求發送給適當的控制器,填充request成員??刂破髦豁憫m當的請求,調用業務數據,并將控制權發送給適當的頁面。
[3]在本例中,請求是“search”??刂破鞅仨毞治龀鲞m當的關鍵字。
[4]控制器使用用戶所提供的關鍵字調用業務邏輯。
[5]控制器將適當的視圖(以及適當的模型)發送給用戶。
[6]在本例中,請求是“page”。我們的用戶界面支持適用于同一個頁面的多種產品。
表單例8-12. AccountForm.java
public class AccountForm { private Account account; private boolean newAccount; private String repeatedPassword; public AccountForm(Account account) { this.account = account; this.newAccount = false; } public AccountForm( ) { this.account = new Account( ); this.newAccount = true; } public Account getAccount( ) { return account; } public boolean isNewAccount( ) { return newAccount; } public void setRepeatedPassword(String repeatedPassword) { this.repeatedPassword = repeatedPassword; } public String getRepeatedPassword( ) { return repeatedPassword; }}
這些bean的每個字段都直接對應于一個HTML輸入字段或控制權。Spring框架將一個提交請求轉換為表單,然后可以將它當作POJO來訪問以進行驗證、映射輸入數據或用于其他用途。與Struts不同的是,Spring表單對象可以是任何Java bean。沒有必要擴展ActionForm。這非常重要,因為你不必從一個ActionForm復制屬性到域對象或值對象。
例8-13. AccountValidator.java
public class AccountValidator implements Validator { public boolean supports(Class clazz) { return Account.class.isAssignableFrom(clazz); } public void validate(Object obj, Errors errors) { ValidationUtils.rejectIfEmpty(errors, "firstName", "FIRST_NAME_REQUIRED", ValidationUtils.rejectIfEmpty(errors, "lastName", "LAST_NAME_REQUIRED", ValidationUtils.rejectIfEmpty(errors, "email", "EMAIL_REQUIRED", ValidationUtils.rejectIfEmpty(errors, "phone", "PHONE_REQUIRED", ValidationUtils.rejectIfEmpty(errors, "address1", "ADDRESS_REQUIRED", ValidationUtils.rejectIfEmpty(errors, "city", "CITY_REQUIRED", ValidationUtils.rejectIfEmpty(errors, "state", "STATE_REQUIRED", ValidationUtils.rejectIfEmpty(errors, "zip", "ZIP_REQUIRED", "ZIP is required."); ValidationUtils.rejectIfEmpty(errors, "country", "COUNTRY_REQUIRED", }}
在本例中,Spring框架從幾個方面使開發人員的生活變得更為輕松。開發人員不需要手動編寫驗證方法。有許多預包裝的方法。框架負責驗證和發送??蚣茇撠煾鶕晒蚴〉慕Y果發送控制權。
僅僅一章還不足以判斷Spring框架,但是您已經看到了它的整體要點。該框架——更重要的是,這種編碼方式——的優點應該顯而易見了。特別要注意整潔的分層架構所提供的清晰性和簡單性。您可以預料到,將業務邏輯與一個類似于Spring的透明框架相結合是多么輕松的事情。
小結我并非一直是Spring的信徒。實際上,在Rod Johnson與我在波士頓的一次會議上被介紹認識之前,我根本不知道他是誰。后來我逐漸欣賞起這個簡單、優雅而重要的框架。如果您對Spring還很陌生,那么您只能看到一個應用程序。我希望通過這個應用程序,您能夠領會它如何體現本書中的這些原則:
盡可能地簡單
Spring的易用性和可讀性。在僅僅一章之中,我們介紹了一個具有事務、持久性、一個完整的web前端以及一個完全的模塊化配置引擎的應用程序。
只做一件事,但是要做好
Spring的框架有許多不同的方面和子框架。但是,它將每個概念很好地分離開來。Spring最基本的意義在于它的bean工廠和配置服務,它們使得用戶可以管理依賴性而無需耦合代碼。Spring的每個附加層都是完全去耦合并且獨立的。
爭取透明
Spring應用程序完全不需要依賴于基礎容器。實際上,這些應用程序可以輕松自如地存在于容器外。用戶只需手動創建和配置它們。這種能力使得Spring應用程序成為開發人員樂于測試的應用程序。
內容決定形式
Spring利用它所包含的各種框架為用戶提供了靈活的選擇。針對數據源和登錄的Apache項目為它奠定了良好的基礎。Spring提供了多種可配置的選項,使用戶可以針對給定的解決方案選擇最佳的框架。
支持擴展
Spring可能是現存的最為開放和可擴展的容器。它允許使用常見的配置服務和整潔的抽象快速有效地進行擴展。
此處對Spring的介紹并不全面。我的目標只是向您說明,利用本書前六章所介紹的概念是可以構建現實世界的應用程序的。如果想了解得更多,一定要研究一下Spring的高級特性:
《Better, Faster, Lighter Java》一書的兩位作者將在其他章節中繼續探討使用了本書所介紹的基本原則的實際例子。例如,一個稱為Simple Spider的服務的實現,您還將看到該服務被集成到Spring中。之后您將會看到類似框架的更多優點。
原文出處 Persistence in Spring: http://www.onjava.com/pub/a/onjava/excerpt/BFLJava_chap8/index1.html
(出處:http://www.49028c.com)
新聞熱點
疑難解答