SPRING數(shù)據(jù)訪問對象(DAO)框架入門
(轉(zhuǎn)貼 地址不祥)J2EE應用程序中的業(yè)務組件通常使用JDBC API訪問和更改關(guān)系數(shù)據(jù)庫中的持久數(shù)據(jù)。這經(jīng)常導致持久性代碼與業(yè)務邏輯發(fā)生混合,這是一種不好的習慣。數(shù)據(jù)訪問對象(DAO)設計模式通過把持久性邏輯分成若干數(shù)據(jù)訪問類來解決這一問題。
本文是一篇關(guān)于DAO設計模式的入門文章,突出講述了它的優(yōu)點和不足之處。另外,本文還介紹了Spring 2.0 JDBC/DAO框架并示范了它如何妥善地解決傳統(tǒng)DAO設計中的缺陷。
傳統(tǒng)的DAO設計
數(shù)據(jù)訪問對象(DAO)是一個集成層設計模式,如Core J2EE Design Pattern 圖書所歸納。它將持久性存儲訪問和操作代碼封裝到一個單獨的層中。本文的上下文中所提到的持久存儲器是一個RDBMS.
這一模式在業(yè)務邏輯層和持久存儲層之間引入了一個抽象層,如圖1所示。業(yè)務對象通過數(shù)據(jù)訪問對象來訪問RDBMS(數(shù)據(jù)源)。抽象層改善了應用程序代碼并引入了靈活性。理論上,當數(shù)據(jù)源改變時,比如更換數(shù)據(jù)庫供應商或是數(shù)據(jù)庫的類型時,僅需改變數(shù)據(jù)訪問對象,從而把對業(yè)務對象的影響降到最低。
圖1. 應用程序結(jié)構(gòu),包括DAO之前和之后的部分
講解了DAO設計模式的基礎知識,下面將編寫一些代碼。下面的例子來自于一個公司域模型。簡而言之,這家公司有幾位員工工作在不同的部門,如銷售部、市場部以及人力資源部。為了簡單起見,我們將集中討論一個稱作“雇員”的實體。
針對接口編程
DAO設計模式帶來的靈活性首先要歸功于一個對象設計的最佳實踐:針對接口編程(P2I)。這一原則規(guī)定實體必須實現(xiàn)一個供調(diào)用程序而不是實體自身使用的接口。因此,可以輕松替換成不同的實現(xiàn)而對客戶端代碼只產(chǎn)生很小的影響。
我們將據(jù)此使用findBySalaryRange()行為定義Employee DAO接口,IEmployeeDAO.業(yè)務組件將通過這個接口與DAO交互:
?import java.util.Map; ? //Returns the list of employees who fall into the given salary ? public List findBySalaryRange(Map salaryMap); |
提供DAO實現(xiàn)類
接口已經(jīng)定義,現(xiàn)在必須提供Employee DAO的具體實現(xiàn),EmployeeDAOImpl:
?import java.sql.Connection; public class EmployeeDAOImpl implements IEmployeeDAO{ ? public List findBySalaryRange(Map salaryMap) |
上面的清單說明了DAO方法的一些要點:
它們封裝了所有與JDBC API的交互。如果使用像Kodo或者Hibernate的O/R映射方案,則DAO類可以將這些產(chǎn)品的私有API打包。
它們將檢索到的數(shù)據(jù)打包到一個與JDBC API無關(guān)的傳輸對象中,然后將其返回給業(yè)務層作進一步處理。
它們實質(zhì)上是無狀態(tài)的。唯一的目的是訪問并更改業(yè)務對象的持久數(shù)據(jù)。
在這個過程中,它們像SQLException一樣捕獲任何底層JDBC API或數(shù)據(jù)庫報告的錯誤(例如,數(shù)據(jù)庫不可用、錯誤的SQL句法)。DAO對象再次使用一個與JDBC無關(guān)的自定義運行時異常類DBException,通知業(yè)務對象這些錯誤。
它們像Connection和PreparedStatement對象那樣,將數(shù)據(jù)庫資源釋放回池中,并在使用完ResultSet游標之后,將其所占用的內(nèi)存釋放。
因此,DAO層將底層的數(shù)據(jù)訪問API抽象化,為業(yè)務層提供了一致的數(shù)據(jù)訪問API.
構(gòu)建DAO工廠
DAO工廠是典型的工廠設計模式實現(xiàn),用于為業(yè)務對象創(chuàng)建和提供具體的DAO實現(xiàn)。業(yè)務對象使用DAO接口,而不用了解實現(xiàn)類的具體情況。DAO工廠帶來的依賴反轉(zhuǎn)(dependency inversion)提供了極大的靈活性。只要DAO接口建立的約定未改變,那么很容易改變DAO實現(xiàn)(例如,從straight JDBC實現(xiàn)到基于Kodo的O/R映射),同時又不影響客戶的業(yè)務對象:
?public class DAOFactory { ? private static DAOFactory daoFac; ? static{ ??? daoFac = new DAOFactory(); ? } ? private DAOFactory(){} ? public DAOFactory getInstance(){ ??? return daoFac; ? } ? public IEmployeeDAO getEmployeeDAO(){ ??? return new EmployeeDAOImpl(); ? } } |
與業(yè)務組件的協(xié)作
現(xiàn)在該了解DAO怎樣適應更復雜的情形。如前幾節(jié)所述,DAO與業(yè)務層組件協(xié)作獲取和更改持久業(yè)務數(shù)據(jù)。下面的清單展示了業(yè)務服務組件及其與DAO層的交互:
?public class EmployeeBusinessServiceImpl implements ? public List getEmployeesWithinSalaryRange(Map salaryMap){ ??? IEmployeeDAO empDAO = DAOFactory.getInstance() |
交互過程十分簡潔,完全不依賴于任何持久性接口(包括JDBC)。
問題
DAO設計模式也有缺點:
代碼重復:從EmployeeDAOImpl清單可以清楚地看到,對于基于JDBC的傳統(tǒng)數(shù)據(jù)庫訪問,代碼重復(如上面的粗體字所示)是一個主要的問題。一遍又一遍地寫著同樣的代碼,明顯違背了基本的面向?qū)ο笤O計的代碼重用原則。它將對項目成本、時間安排和工作產(chǎn)生明顯的副面影響。
耦合:DAO代碼與JDBC接口和核心collection耦合得非常緊密。從每個DAO類的導入聲明的數(shù)量可以明顯地看出這種耦合。
資源耗損:依據(jù)EmployeeDAOImpl類的設計,所有DAO方法必須釋放對所獲得的連接、聲明、結(jié)果集等數(shù)據(jù)庫資源的控制。這是危險的主張,因為一個編程新手可能很容易漏掉那些約束。結(jié)果造成資源耗盡,導致系統(tǒng)停機。
錯誤處理:JDBC驅(qū)動程序通過拋出SQLException來報告所有的錯誤情況。SQLException是檢查到的異常,所以開發(fā)人員被迫去處理它,即使不可能從這類導致代碼混亂的大多數(shù)異常中恢復過來。而且,從SQLException對象獲得的錯誤代碼和消息特定于數(shù)據(jù)庫廠商,所以不可能寫出可移植的DAO錯誤發(fā)送代碼。
脆弱的代碼:在基于JDBC的DAO中,兩個常用的任務是設置聲明對象的綁定變量和使用結(jié)果集檢索數(shù)據(jù)。如果SQL where子句中的列數(shù)目或者位置更改了,就不得不對代碼執(zhí)行更改、測試、重新部署這個嚴格的循環(huán)過程。
讓我們看看如何能夠減少這些問題并保留DAO的大多數(shù)優(yōu)點。
進入Spring DAO
先識別代碼中發(fā)生變化的部分,然后將這一部分代碼分離出來或者封裝起來,就能解決以上所列出的問題。Spring的設計者們已經(jīng)完全做到了這一點,他們發(fā)布了一個超級簡潔、健壯的、高度可伸縮的JDBC框架。固定部分(像檢索連接、準備聲明對象、執(zhí)行查詢和釋放數(shù)據(jù)庫資源)已經(jīng)被一次性地寫好,所以該框架的一部分內(nèi)容有助于消除在傳統(tǒng)的基于JDBC的DAO中出現(xiàn)的缺點。
圖2顯示的是Spring JDBC框架的主要組成部分。業(yè)務服務對象通過適當?shù)慕涌诶^續(xù)使用DAO實現(xiàn)類。JdbcDaoSupport是JDBC數(shù)據(jù)訪問對象的超類。它與特定的數(shù)據(jù)源相關(guān)聯(lián)。Spring Inversion of Control (IOC)容器或BeanFactory負責獲得相應數(shù)據(jù)源的配置詳細信息,并將其與JdbcDaoSupport相關(guān)聯(lián)。這個類最重要的功能就是使子類可以使用JdbcTemplate對象。
圖2. Spring JDBC框架的主要組件
JdbcTemplate是Spring JDBC框架中最重要的類。引用文獻中的話:“它簡化了JDBC的使用,有助于避免常見的錯誤。它執(zhí)行核心JDBC工作流,保留應用代碼以提供SQL和提取結(jié)果?!边@個類通過執(zhí)行下面的樣板任務來幫助分離JDBC DAO代碼的靜態(tài)部分:
從數(shù)據(jù)源檢索連接。
準備合適的聲明對象。
執(zhí)行SQL CRUD操作。
遍歷結(jié)果集,然后將結(jié)果填入標準的collection對象。
處理SQLException異常并將其轉(zhuǎn)換成更加特定于錯誤的異常層次結(jié)構(gòu)。
利用Spring DAO重新編寫
既然已基本理解了Spring JDBC框架,現(xiàn)在要重新編寫已有的代碼。下面將逐步講述如何解決前幾節(jié)中提到的問題。
第一步:修改DAO實現(xiàn)類- 現(xiàn)在從JdbcDaoSupport擴展出EmployeeDAOImpl以獲得JdbcTemplate.
?import org.springframework.jdbc.core.support.JdbcDaoSupport; public class EmployeeDAOImpl extends JdbcDaoSupport ? public List findBySalaryRange(Map salaryMap){ ??? Double dblParams [] = {Double.valueOf((String) |
在上面的清單中,傳入?yún)?shù)映射中的值存儲在雙字節(jié)數(shù)組中,順序與SQL字符串中的位置參數(shù)相同。queryForList()方法以包含Map(用列名作為鍵,一項對應一列)的List(一項對應一行)的方式返回查詢結(jié)果。稍后我會說明如何返回傳輸對象列表。
從簡化的代碼可以明顯看出,JdbcTemplate鼓勵重用,這大大削減了DAO實現(xiàn)中的代碼。JDBC和collection包之間的緊密耦合已經(jīng)消除。由于JdbcTemplate方法可確保在使用數(shù)據(jù)庫資源后將其按正確的次序釋放,所以JDBC的資源耗損不再是一個問題。
另外,使用Spring DAO時,不必處理異常。JdbcTemplate類會處理SQLException,并根據(jù)SQL錯誤代碼或錯誤狀態(tài)將其轉(zhuǎn)換成特定于Spring異常的層次結(jié)構(gòu)。例如,試圖向主鍵列插入重復值時,將引發(fā)DataIntegrityViolationException.然而,如果無法從這一錯誤中恢復,就無需處理該異常。因為Spring DAO的根異常類DataAccessException是運行時異常類,所以可以這樣做。值得注意的是Spring DAO異常獨立于數(shù)據(jù)訪問實現(xiàn)。如果實現(xiàn)是由O/R映射解決方案提供,就會拋出同樣的異常。
第二步:修改業(yè)務服務- 現(xiàn)在業(yè)務服務實現(xiàn)了一個新方法setDao(),Spring容器使用該方法傳遞DAO實現(xiàn)類的引用。該過程稱為“設置方法注入(setter injection)”,通過第三步中的配置文件告知Spring容器該過程。注意,不再需要使用DAOFactory,因為Spring BeanFactory提供了這項功能:
?public class EmployeeBusinessServiceImpl ???????????????????????? implements IEmployeeBusinessService { ? IEmployeeDAO empDAO; ? public List getEmployeesWithinSalaryRange(Map salaryMap){ ??? List empList = empDAO.findBySalaryRange(salaryMap); ??? return empList; ? } ? public void setDao(IEmployeeDAO empDAO){ ??? this.empDAO = empDAO; ? } } |
請注意P2I的靈活性;即使極大地改動DAO實現(xiàn),業(yè)務服務實現(xiàn)也只需少量更改。這是由于業(yè)務服務現(xiàn)在由Spring容器進行管理。
第三步:配置Bean Factory- Spring bean factory需要一個配置文件進行初始化并啟動Spring框架。這個配置文件包含所有業(yè)務服務和帶Spring bean容器的DAO實現(xiàn)類。除此之外,它還包含用于初始化數(shù)據(jù)源和JdbcDaoSupport的信息:
?<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" <beans> ? <!-- Configure DAO --> ? <!-- Configure Business Service --> |
這個Spring bean容器通過調(diào)用JdbcDaoSupport提供的setDataSource()方法,設置包含DAO實現(xiàn)的數(shù)據(jù)源對象。
第四步:測試- 最后是編寫JUnit測試類。依照Spring的方式,需要在容器外部進行測試。然而,從第三步中的配置文件可以清楚地看到,我們一直在使用WebLogic Server連接池。
?package com.bea.dev2dev.business; import java.util.*; public class EmployeeBusinessServiceImplTest extends TestCase { ??? protected void setUp() throws Exception { ??? /** |
使用綁定變量
到目前為止,我們搜索了工資介于最低值和最高值之間的雇員。假設在某種情形下,業(yè)務用戶想要顛倒這一范圍。DAO代碼很脆弱,將不得不通過更改來滿足要求的變化。這個問題在于使用了靜態(tài)的位置綁定變量(用“?”表示)。Spring DAO通過支持命名的綁定變量來挽救這個情況。修改的IEmployeeDAO清單引入了命名的綁定變量(用“:<some name>”表示)。注意查詢中的變化,如下所示:
?import java.util.Map; ? //SQL String that will be executed ? //Returns the list of employees falling into the given salary range ? public List findBySalaryRange(Map salaryMap); |
多數(shù)JDBC驅(qū)動程序僅支持位置綁定變量。所以,Spring DAO在運行時將這個查詢轉(zhuǎn)換成位置綁定、基于變量的查詢,并且設置正確的綁定變量。現(xiàn)在,為了完成這些任務,需要使用NamedParameterJdbcDaoSupport類和NamedParameterJdbcTemplate類,以代替JdbcDaoSupport和JdbcTemplate.下面就是修改后的DAO實現(xiàn)類:
?import org.springframework.jdbc.core.namedparam.NamedParameterJdbcDaoSupport; public class EmployeeDAOImpl extends NamedParameterJdbcDaoSupport ? public List findBySalaryRange(Map salaryMap){ ??? NamedParameterJdbcTemplate tmplt = |
NamedParameterJdbcDaoSupport的getNamedParameterJdbcTemplate()方法返回一個NamedParameterJdbcTemplate實例,該實例由數(shù)據(jù)源句柄進行了預初始化。Spring Beanfactory執(zhí)行初始化任務,從配置文件獲得所有的詳細信息。在執(zhí)行時,一旦將命名的參數(shù)替換成位置占位符,NamedParameterJdbcTemplate就將操作委托給JdbcTemplate.可見,使用命名的參數(shù)使得DAO方法不受底層SQL聲明任何更改的影響。
最后,如果數(shù)據(jù)庫不支持自動類型轉(zhuǎn)換,需要如下所示,對JUnit測試類中的initSalaryMap()方法稍做修改。
?private void initSalaryMap() { ??????? salaryMap = new HashMap(); ??????? salaryMap.put("MIN_SALARY",new Double(1)); ??????? salaryMap.put("MAX_SALARY",new Double(50)); ??? } |
Spring DAO回調(diào)函數(shù)
至此,已經(jīng)說明為了解決傳統(tǒng)DAO設計中存在的問題,如何封裝和概括JdbcTemplate類中JDBC代碼的靜態(tài)部分?,F(xiàn)在了解一下有關(guān)變量的問題,如設置綁定變量、結(jié)果集遍歷等。雖然Spring DAO已經(jīng)擁有這些問題的一般化解決方案,但在某些基于SQL的情況下,可能仍需要設置綁定變量。
在嘗試向Spring DAO轉(zhuǎn)換的過程中,介紹了由于業(yè)務服務及其客戶機之間的約定遭到破壞而導致的隱蔽運行時錯誤。這個錯誤的來源可以追溯到原始的DAO.dbcTemplate.queryForList()方法不再返回EmployeeTO實例列表。而是返回一個map表(每個map是結(jié)果集的一行)。
如您目前所知,JdbcTemplate基于模板方法設計模式,該模式利用JDBC API定義SQL執(zhí)行工作流。必須改變這個工作流以修復被破壞的約定。第一個選擇是在子類中更改或擴展工作流。您可以遍歷JdbcTemplate.queryForList()返回的列表,用EmployeeTO實例替換map對象。然而,這會導致我們一直竭力避免的靜態(tài)代碼與動態(tài)代碼的混合。第二個選擇是將代碼插入JdbcTemplate提供的各種工作流修改鉤子(hook)。明智的做法是在一個不同的類中封裝傳輸對象填充代碼,然后通過鉤子鏈接它。填充邏輯的任何修改將不會改變DAO.
編寫一個類,使其實現(xiàn)在Spring框架特定的接口中定義的方法,就可以實現(xiàn)第二個選擇。這些方法稱為回調(diào)函數(shù),通過JdbcTemplate向框架注冊。當發(fā)生相應的事件(例如,遍歷結(jié)果集并填充獨立于框架的傳輸對象)時,框架將調(diào)用這些方法。
第一步:傳輸對象
下面是您可能感興趣的傳輸對象。注意,以下所示的傳輸對象是固定的:
?package com.bea.dev2dev.to; public final class EmployeeTO implements Serializable{ ????? private int empNo; ????? /** Creates a new instance of EmployeeTO */ |
第二步:實現(xiàn)回調(diào)接口
實現(xiàn)RowMapper接口,填充來自結(jié)果集的傳輸對象。下面是一個例子:
?package com.bea.dev2dev.dao.mapper; import com.bea.dev2dev.to.EmployeeTO; public class EmployeeTOMapper implements RowMapper{ ? public Object mapRow(ResultSet rs, int rowNum) |
注意實現(xiàn)類不應該對提供的ResultSet對象調(diào)用next()方法。這由框架負責,該類只要從結(jié)果集的當前行提取值就行?;卣{(diào)實現(xiàn)拋出的任何SQLException也由Spring框架處理。
第三步:插入回調(diào)接口
執(zhí)行SQL查詢時,JdbcTemplate利用默認的RowMapper實現(xiàn)產(chǎn)生map列表?,F(xiàn)在需要注冊自定義回調(diào)實現(xiàn)來修改JdbcTemplate的這一行為。注意現(xiàn)在用的是NamedParameterJdbcTemplate的query()方法,而不是queryForList()方法:
?public class EmployeeDAOImpl extends NamedParameterJdbcDaoSupport ? public List findBySalaryRange(Map salaryMap){ ??? NamedParameterJdbcTemplate daoTmplt = |
Spring DAO框架對執(zhí)行查詢后返回的結(jié)果進行遍歷。它在遍歷的每一步調(diào)用EmployeeTOMapper類實現(xiàn)的mapRow()方法,使用EmployeeTO傳輸對象填充最終結(jié)果的每一行。
第四步:修改后的JUnit類
現(xiàn)在要根據(jù)返回的傳輸對象測試這些結(jié)果。為此要對測試方法進行修改。
?public class EmployeeBusinessServiceImplTest extends TestCase { ? private IEmployeeBusinessService empBusiness; ????? // all methods not shown in the listing remain the ????? /** ????? public void assertEquals(List expResult, List result){ |
優(yōu)勢
Spring JDBC框架的優(yōu)點很清楚。我們獲益很多,并將DAO方法簡化到只有幾行代碼。代碼不再脆弱,這要感謝該框架對命名的參數(shù)綁定變量的“開箱即用”支持,以及在映射程序中將傳輸對象填充邏輯分離。
Spring JDBC的優(yōu)點應該促使您向這一框架移植現(xiàn)有的代碼。希望本文在這一方面能有所幫助。它會幫助您獲得一些重構(gòu)工具和知識。例如,如果您沒有采用P2I Extract Interface,那么可以使用重構(gòu),從現(xiàn)有的DAO實現(xiàn)類創(chuàng)建接口。除此之外,查看本文的參考資料可以得到更多指導。
posted on 2007-03-27 10:48 都市淘沙者 閱讀(548) 評論(0) 編輯 收藏 所屬分類: Spring+Struts+Hibernate