成都心情
BlogJava
::
首頁
:: ::
聯系
::
聚合
::
管理
::
98 隨筆 :: 2 文章 :: 501 評論 :: 1 Trackbacks
公告
本作品采用
知識共享署名-相同方式共享 2.5 中國大陸許可協議
進行許可。
(15)
給我留言
查看公開留言
查看私人留言
隨筆分類
(91)
Java EE 服務器端(13)
(rss)
Java EE 表現層及容器(12)
(rss)
Java ME(1)
(rss)
Java 基礎(10)
(rss)
MatLab(1)
(rss)
O/R Mapping(13)
(rss)
Versant db4o 中文項目(12)
(rss)
Web前端技術
(rss)
五花八門(8)
(rss)
大數據(3)
(rss)
工作流(10)
(rss)
數據庫(2)
(rss)
模式與策略(6)
(rss)
隨筆檔案
(99)
2016年7月 (2)
2016年6月 (4)
2016年5月 (3)
2016年4月 (2)
2010年7月 (1)
2010年6月 (2)
2010年5月 (3)
2010年3月 (1)
2010年1月 (1)
2009年10月 (1)
2009年9月 (1)
2009年7月 (1)
2009年6月 (1)
2009年3月 (1)
2009年2月 (1)
2008年12月 (2)
2008年9月 (1)
2008年8月 (1)
2008年7月 (1)
2008年6月 (1)
2008年4月 (1)
2008年3月 (1)
2008年1月 (1)
2007年12月 (2)
2007年10月 (1)
2007年9月 (1)
2007年8月 (1)
2007年6月 (2)
2007年5月 (1)
2007年4月 (1)
2007年2月 (1)
2007年1月 (1)
2006年12月 (1)
2006年11月 (1)
2006年10月 (1)
2006年9月 (1)
2006年8月 (1)
2006年7月 (1)
2006年6月 (1)
2006年5月 (1)
2006年4月 (1)
2006年3月 (1)
2006年2月 (1)
2006年1月 (1)
2005年12月 (1)
2005年11月 (1)
2005年10月 (1)
2005年9月 (2)
2005年8月 (37)
文章分類
(2)
我的收藏(2)
(rss)
友情鏈接
david.turing
(rss)
wyingquan的專欄
(rss)
信息安全思想
俺的豬窩~!@
喜馬拉雅的雪杉
(rss)
無聊人士
(rss)
竹十一
(rss)
老劉忙不忙
(rss)
邢紅瑞的blog
(rss)
積分與排名
積分 - 634264
排名 - 74
最新評論
1.?re: 精確截取字符串(轉載)
string=goodStr(string); 這個方法 是干什么用處的?
--JustPassoner
2.?re: 精確截取字符串(轉載)
@國家機器
六六,認識你是我等榮幸,酒瘋知己千杯燒...
--JustPassoner
3.?re: 使用Memory Analyzer tool(MAT)分析內存泄漏(二)[未登錄]
樓主可以去看看一篇jvm的連載 公眾號 ITmenghuo
--dddd
4.?re: 使用Memory Analyzer tool(MAT)分析內存泄漏(一)
小瑕疵。
圖片顯示不了。
--袁良錠
5.?re: Hadoop周刊—第 169 期
喲,又開始更新了
--救救劉書記
閱讀排行榜
1.?使用Memory Analyzer tool(MAT)分析內存泄漏(二)(121240)
2.?使用Memory Analyzer tool(MAT)分析內存泄漏(一)(75947)
3.?Java 中的位運算(31038)
4.?Ajax輪詢以及Comet模式—寫在Servlet 3.0發布之前(15639)
5.?RBAC 模型初探(13698)
評論排行榜
1.?德國申根商務簽證攻略(成都版)(36)
2.?OSWorkflow 探索(29)
3.?北漂找工作經歷(26)
4.?使用Memory Analyzer tool(MAT)分析內存泄漏(一)(23)
5.?開源面向對象數據庫 db4o 之旅: 初識 db4o“db4o 之旅(一)”(21)
Spring 數據訪問對象(Data Access Object,DAO)框架入門(翻譯)
請注意!本文的譯者是 rosenjiang!!!而不是 copy 自? BEA 譯本!!!
摘要
J2EE 應用中的業務組件普遍采用 JDBC API 訪問和修改關系型數據庫的持久化數據。這樣做常常會把持久化代碼和業務邏輯混合在一起,實在太糟糕了。數據訪問對象(Data Access Object,DAO)設計模式針對這一問題把持久化邏輯分離到數據訪問類中,以達到分離目的。
本文是基于 DAO 設計模式的入門文章,把它的優點和缺點都毫無偏頗的展現出來。接著將引入 Spring 2.0 JDBC/DAO 框架,并將示范它是如何優雅的解決傳統 DAO 設計的缺點。
傳統 DAO 設計
Data Access Object (DAO)屬于《
Core J2EE Design Pattern
》一書中所介紹的集成層設計模式范疇。它封裝了持久化存儲訪問并以獨立的層次來處理代碼。本文中所提到的持久化存儲是指關系型數據庫。
DAO 模式在業務邏輯層和持久化存儲層之間引入了新的抽象層,如圖 1 所示。業務對象通過數據訪問對象訪問關系型數據庫(數據源)。抽象層簡化了應用程序代碼增強了靈活性。太完美了,試想一下哪天改變了數據源,比如更換為其他數據庫廠商的產品,只需要修改數據訪問對象,并且對業務對象的影響也是最小的。
????????? 圖1. 使用 DAO 前后的應用程序結構
現在我解釋一下 DAO 設計模式的基本含義,并寫一些代碼說明。下面的例子來自于某公司的
域模型(domain model)
。簡單的來說,該公司有多名工作在不同部門的員工,比如銷售、市場、人力資源。為了更簡略,我將只針對“Employee”實體來說明。
面向接口編程
之所以 DAO 設計模式有如此的靈活性主要歸功于對象設計的最佳實踐:
面向接口編程(Program to an Interface,P2I)
。該原則是這樣陳述的:具體的對象必須實現一個接口。換句話說,利用調用者(caller)編程勝于利用具體對象本身。因此,你可以容易的替代不同的實現,僅僅需要改動很少的客戶端代碼(譯注:這里的客戶端代碼應該是指業務代碼)。
下面要開始定義 Employee DAO 接口了,具有 findBySalaryRange() 行為的 IEmployeeDAO。業務組件將通過這些接口與 DAO 交互:
import
?java.util.Map;
public
?
interface
?IEmployeeDAO?{
??
//
SQL?String?that?will?be?executed
??
public
?String?FIND_BY_SAL_RNG?
=
?
"
SELECT?EMP_NO,?EMP_NAME,?
"
??
+
?
"
SALARY?FROM?EMP?WHERE?SALARY?>=???AND?SALARY?<=??
"
;
??
//
Returns?the?list?of?employees?who?fall?into?the?given?salary?
??
//
range.?The?input?parameter?is?the?immutable?map?object?
??
//
obtained?from?the?HttpServletRequest.?This?is?an?early?
??
//
refactoring?based?on?"Introduce?Parameter?Object"
??
public
?List?findBySalaryRange(Map?salaryMap);
}
提供 DAO 實現類
定義好接口后,現在我必須提供具體的 Employee DAO 實現,EmployeeDAOImpl:
import
?java.sql.Connection;
import
?java.sql.PreparedStatement;
import
?java.sql.ResultSet;
import
?java.util.List;
import
?java.util.ArrayList;
import
?java.util.Map;
import
?com.bea.dev2dev.to.EmployeeTO;
public
?
class
?EmployeeDAOImpl?
implements
?IEmployeeDAO{
??
public
?List?findBySalaryRange(Map?salaryMap)
??{
????Connection?conn?
=
?
null
;
????PreparedStatement?pstmt?
=
?
null
;?
????ResultSet?rs?
=
?
null
;?
????List?empList?
=
?
new
?ArrayList();
????
//
Transfer?Object?for?inter-tier?data?transfer
????EmployeeTO?tempEmpTO?
=
?
null
;
????
try
{
????
//
DBUtil?-?helper?classes?that?retrieve?connection?from?pool
??????conn?
=
?DBUtil.getConnection();?
??????pstmt?
=
?conn.prepareStatement(FIND_BY_SAL_RNG);
??????pstmt.setDouble(
1
,?Double.valueOf(?(String)
??????????salaryMap.get(
"
MIN_SALARY
"
)?);
??????pstmt.setDouble(
2
,?Double.valueOf(?(String)
??????????salaryMap.get(
"
MIN_SALARY
"
)?);?
??????rs?
=
?pstmt.executeQuery();?
??????
int
?tmpEmpNo?
=
?
0
;
??????String?tmpEmpName?
=
?
""
;
??????
double
?tmpSalary?
=
?
0.0D
;
??????
while
?(rs.next()){?
????????tmpEmpNo?
=
?rs.getInt(
"
EMP_NO
"
);
????????tmpEmpName?
=
?rs.getString(
"
EMP_NAME
"
);
????????tmpSalary?
=
?rs.getDouble(
"
SALARY
"
);
????????tempEmpTO?
=
?
new
?EmployeeTO(tmpEmpNo,
??????????????tmpEmpName,
??????????????tmpSalary);
????????empList.add(tempEmpTO);???
??????}
//
end?while?
????}
//
end?try?
????
catch
?(SQLException?sqle){?
??????
throw
?
new
?DBException(sqle);?
????}
//
end?catch?
????
finally
{?
??????
try
{?
????????
if
?(rs?
!=
?
null
){?
??????????rs.close();?
????????}
??????}
??????
catch
?(SQLException?sqle){
????????
throw
?
new
?DBException(sqle);
??????}
??????
try
{
????????
if
?(pstmt?
!=
?
null
){
??????????pstmt.close();?
????????}????????
??????}
??????
catch
?(SQLException?sqle){
????????
throw
?
new
?DBException(sqle);
??????}
??????
try
{?
????????
if
?(conn?
!=
?
null
){
??????????conn.close();
????????}????????
??????}
??????
catch
?(SQLException?sqle){?
????????
throw
?
new
?DBException(sqle);
??????}
????}
//
end?of?finally?block
????
return
?empList;
??}
//
end?method?findBySalaryRange
}
以上代碼闡明了 DAO 模式的幾個關鍵要素:
1. 它們封裝所有的 JDBC API 交互。如果用到了 Kodo 或 Hibernate 這些 O/R 映射工具,它們的 DAO 類將把這些私有 API 都封裝好。
2. 它們利用與 JDBC API 無關的 transfer object(http://java.sun.com/blueprints/corej2eepatterns/Patterns/TransferObject.html) 封裝獲取的數據,并返回到業務層待進一步處理。
3. 它們是無狀態的。每個業務對象的訪問和改變持久化數據它們都單獨對應。
4. 它們捕獲任何錯誤(例如,數據庫不可用,錯誤的 SQL 語法)并利用底層 JDBC API 或數據庫的 SQLException 進行報告。接著 DAO 對象利用 JDBC 無關的提示再次向業務對象通知這些錯誤,比如定制 DBException 運行時異常類。
5. 它們釋放 Connection 以及 PreparedStatement 這些數據庫資源對象返回給連接池并釋放已經使用過的 ResultSet 游標占用的內存。
因此,DAO 層抽象了底層數據訪問 API,為業務層提供了一致的數據訪問 API。
構建 DAO 工廠
DAO 工廠是典型的
工廠設計模式
,為業務對象構建和服務具體的 DAO 實現。業務對象使用 DAO 接口,所以并不知道具體的實現類。依賴倒置原則賦予了 DAO 工廠極大的靈活性。DAO 工廠可以輕松的改變 DAO 實現(例如從 JDBC 實現到基于 Kodo 的 O/R 映射實現)而不會影響到業務對象,不過一旦 DAO 接口約定建立將不能改變:
public
?
class
?DAOFactory?{
??
private
?
static
?DAOFactory?daoFac;
??
static
{
????daoFac?
=
?
new
?DAOFactory();
??}
??
private
?DAOFactory(){}
??
public
?DAOFactory?getInstance(){
????
return
?daoFac;
??}
??
public
?IEmployeeDAO?getEmployeeDAO(){
????
return
?
new
?EmployeeDAOImpl();
??}
}
與業務組件協作
是時候來了解 DAO 是如何適應這樣的環境了。前面說到 DAO 與業務組件協作獲取和改變持久業務數據。下面列出了業務服務組件并展示了如何與 DAO 層交互:
public
?
class
?EmployeeBusinessServiceImpl?
implements
?
???????????????????????????????????????IEmployeeBusinessService?{
??
public
?List?getEmployeesWithinSalaryRange(Map?salaryMap){
????IEmployeeDAO?empDAO?
=
?DAOFactory.getInstance()
????????????????????????????????????.getEmployeeDAO();
????List?empList?
=
?empDAO.findBySalaryRange(salaryMap);
????
return
?empList;
??}
}
完美而清晰,不依靠包括 JDBC 在內的任何持久化接口。
問題
DAO 設計模式并不是沒有缺點的:
代碼重復:
正如 EmployeeDAOImpl 類列出的,代碼重復是基于 JDBC 的傳統數據庫訪問的主要問題。反復書寫代碼明顯違反了基本的 OO 代碼復用原則。對于項目開銷、時間和消耗都有著明顯的不利因素。
耦合:
DAO 代碼非常緊密的和 JDBC 接口以及核心集合類型耦合在一起。這點可以從每個 DAO 類導入語句的數目體現。
資源泄漏:
進入 EmployeeDAOImpl 類的設計,所有 DAO 方法都必須釋放已獲取的數據庫資源的控制權,比如 connection、statements 以及結果集。這樣做是很危險的,因為一名沒有經驗的程序員可以很容易的繞開這些程序塊。結果資源將流失,最終導致系統當機。
錯誤捕獲:
JDBC 驅動通過拋出 SQLException 報告所有的錯誤情況。SQLException 是 checked exception。因此開發者被迫處理它——即使無法從大部分異常中恢復,這樣也就導致了混亂的代碼。此外,從 SQLException 對象獲取錯誤代碼和消息是數據庫供應商特定的,因此不可能編寫靈活的 DAO 錯誤消息代碼。
脆弱的代碼:
在基于 JDBC 的 DAO 中,為 statement 對象綁定變量的設置,以及獲取數據使用的結果集 getter 方法是頻繁用到的兩個任務。如果 SQL 語句中的列數改變了,或者列的位置改變了,代碼將不得不再經過嚴格的反復修改、測試然后部署。
讓我們來看看如何繼承 DAO 的優勢并去除上面提到的缺點。
進入 Spring DAO
在上面列出的問題可以通過改變某些代碼來解決,接著再分離或封裝遺留代碼。Spring 的設計者做得非常嚴密,提供了擁有超輕量、健壯以及高度擴展性的 JDBC 框架。固定的部分(像獲取連接、準備 statement 對象、執行查詢、釋放數據庫資源這些)已經一次性提供好了——所以框架已經幫助我們排除了傳統的基于 JDBC DAO 的缺點。
圖 2 展示了主要的 Spring JDBC 框架組成板塊。通過適當的接口,業務服務對象繼續使用 DAO 實現類。
JdbcDaoSupport
是 JDBC 數據訪問對象的超類。它關聯了實際的數據源。Spring 的
控制反轉(Inversion of Control,IoC)
容器或者叫
BeanFactory
是用來獲取具體的數據源配置并把這些細節關聯給 JdbcDaoSupport。JdbcDaoSupport 類最重要的功能是為其子類構造可用的
JdbcTemplate
對象。
????????? 圖 2. Spring JDBC 框架的主要組件
JdbcTemplate 是 Spring JDBC 框架最重要的類。引用文檔中的一段話:“它簡化了 JDBC 的操作并幫助開發者避免一般性錯誤,它完成了核心 JDBC 工作流程,剝離了應用程序代碼中提供的 SQL 語句和提取結果。”該類幫助分離 JDBC DAO 代碼的靜態部分并賦予了以下任務:
1. 從數據源獲取連接。
2. 準備適當的 statement 對象。
3. 執行 SQL CRUD 操作。
4. 遍歷結果集,并把結果遷移到標準的集合對象中。
5. 捕獲 SQLException 異常并翻譯成更加具體的錯誤體系。
用 Spring DAO 重寫
現在你已經對 Spring JDBC 框架有了基本了解,該是重寫現有代碼的時候了。我分步驟進行,在這一過程中將探討如何克服前面章節提到的問題。
第 1 步:修改 DAO 實現類
—— EmployeeDAOImpl 現在繼承自 JdbcDaoSupport 并獲取 JdbcTemplate。
import
?org.springframework.jdbc.core.support.JdbcDaoSupport;
import
?org.springframework.jdbc.core.JdbcTemplate;
public
?
class
?EmployeeDAOImpl?
extends
?JdbcDaoSupport?
?????????????????????????????????????
implements
?IEmployeeDAO{
??
public
?List?findBySalaryRange(Map?salaryMap){
????Double?dblParams?[]?
=
?{Double.valueOf((String)
????????????salaryMap.get(
"
MIN_SALARY
"
))
??????????????,Double.valueOf((String)
????????????salaryMap.get(
"
MAX_SALARY
"
))??
??????????};
????
//
The?getJdbcTemplate?method?of?JdbcDaoSupport?returns?an
????
//
instance?of?JdbcTemplate?initialized?with?a?datasource?by?the
????
//
Spring?Bean?Factory
????JdbcTemplate?daoTmplt?
=
?
this
.getJdbcTemplate();
????
return
?daoTmplt.queryForList(FIND_BY_SAL_RNG,dblParams);?
??}
}
上面代碼中,傳入參數 salaryMap 的值被存儲在 double 數組中,參數的順序按照 SQL 字符串中順序一樣。查詢結果通過 queryForList() 方法返回包含 Map 對象(每列一條,使用列名作為 key)的 List(每行一條)。稍后我將說明如何返回一列 transfer objects。
通過簡單的代碼,明顯可以發現 JdbcTemplate 是鼓勵復用的,這將減少 DAO 實現中大量的代碼。JDBC 與集合包之間緊密耦合也被去除了。JDBC 資源泄漏不再是問題,JdbcTemplate 確保數據庫資源在使用后以固定的順序被釋放掉。
另外,在使用 Spring DAO 的時候不用把精力集中在捕獲異常上面。JdbcTemplate 類捕獲了 SQLException,接著基于 SQL 錯誤代碼或錯誤狀態翻譯成 Spring 具體的異常。例如,當試圖插入重復的值到主鍵列時拋出 DataIntegrityViolationException。當然,當你無法從錯誤中恢復時無需捕獲這些異常。這是由于根異常類在 Spring DAO 中,DataAccessException 是運行時異常。值得注意的是 Spring DAO 異常與具體的數據訪問實現無關。同樣的異常也會在使用 O/R 映射方案實現的情況下拋出。
第 2 步:修改業務服務
——通過 Spring 容器傳遞一個 DAO 實現類的引用,業務服務現在實現了 setDao() 新方法。這種處理叫做“設值注入”,并通過第三步中 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 實現,也只是稍稍修改業務服務實現而已。這點小小的修改使業務服務被 Spring 容器管理起來了。
第 3 步:配置 Bean 工廠
—— Spring bean 工廠需要一個配置文件初始化和啟動 Spring 框架。這個配置文件把所有的業務服務和 DAO 實現類集成到 Spring bean 容器。除此以外,配置文件也包含了初始化數據源和 JdbcDaoSupport 的信息:
<?
xml?version="1.0"?encoding="UTF-8"
?>
<!
DOCTYPE?beans?PUBLIC?"-//SPRING//DTD?BEAN//EN"?
"http://www.springframework.org/dtd/spring-beans.dtd"
>
<
beans
>
??
<!--
?Configure?Datasource?
-->
??
<
bean?
id
="FIREBIRD_DATASOURCE"
?
????class
="org.springframework.jndi.JndiObjectFactoryBean"
>
?
????
<
property?
name
="jndiEnvironment"
>
?
??????
<
props
>
????????
<
prop?
key
="java.naming.factory.initial"
>
??????????weblogic.jndi.WLInitialContextFactory
????????
</
prop
>
????????
<
prop?
key
="java.naming.provider.url"
>
??????????t3://localhost:7001
????????
</
prop
>
??????
</
props
>
????
</
property
>
?
????
<
property?
name
="jndiName"
>
?
??????
<
value
>
????????jdbc/DBPool
??????
</
value
>
?
????
</
property
>
??
</
bean
>
??
<!--
?Configure?DAO?
-->
??
<
bean?
id
="EMP_DAO"
?class
="com.bea.dev2dev.dao.EmployeeDAOImpl"
>
????
<
property?
name
="dataSource"
>
??????
<
ref?
bean
="FIREBIRD_DATASOURCE"
></
ref
>
????
</
property
>
??
</
bean
>
??
<!--
?Configure?Business?Service?
-->
??
<
bean?
id
="EMP_BUSINESS"
?
??class
="com.bea.dev2dev.sampleapp.business.EmployeeBusinessServiceImpl"
>
????
<
property?
name
="dao"
>
??????
<
ref?
bean
="EMP_DAO"
></
ref
>
????
</
property
>
??
</
bean
>
??
</
beans
>
通過調用 JdbcDaoSupport 的 setDataSource() 方法,Spring bean 容器為 DAO 實現設置數據源對象。同時也為業務服務提供 DAO 實現。
第 4 步:測試
——最后寫 JUnit 測試類。依照 Spring 體系,我將在容器以外進行測試。按照第 3 步中的配置文件,我將使用 WebLogic Server 連接池。
package
?com.bea.dev2dev.business;
import
?java.util.
*
;
import
?junit.framework.
*
;
import
?org.springframework.context.ApplicationContext;
import
?org.springframework.context.support.FileSystemXmlApplicationContext;
public
?
class
?EmployeeBusinessServiceImplTest?
extends
?TestCase?{
????
private
?IEmployeeBusinessService?empBusiness;
????
private
?Map?salaryMap;
????List?expResult;
????
protected
?
void
?setUp()?
throws
?Exception?{
????????initSpringFramework();
????????initSalaryMap();
????????initExpectedResult();
????}
????
private
?
void
?initExpectedResult()?{
????????expResult?
=
?
new
?ArrayList();
????????Map?tempMap?
=
?
new
?HashMap();
????????tempMap.put(
"
EMP_NO
"
,
new
?Integer(
1
));
????????tempMap.put(
"
EMP_NAME
"
,
"
John
"
);
????????tempMap.put(
"
SALARY
"
,
new
?Double(
46.11
));
????????expResult.add(tempMap);
????}
????
private
?
void
?initSalaryMap()?{
????????salaryMap?
=
?
new
?HashMap();
????????salaryMap.put(
"
MIN_SALARY
"
,
"
1
"
);
????????salaryMap.put(
"
MAX_SALARY
"
,
"
50
"
);
????}
????
private
?
void
?initSpringFramework()?{
??????ApplicationContext?ac?
=
?
new
?FileSystemXmlApplicationContext
????????(
"
C:/SpringConfig/Spring-Config.xml
"
);?
??????empBusiness?
=
?
?????????????(IEmployeeBusinessService)ac.getBean(
"
EMP_BUSINESS
"
);
????}
????
protected
?
void
?tearDown()?
throws
?Exception?{
????}
????
/**
?????*?Test?of?getEmployeesWithinSalaryRange?method,?
?????*?of?class?
?????*?com.bea.dev2dev.business.EmployeeBusinessServiceImpl.
?????
*/
????
public
?
void
?testGetEmployeesWithinSalaryRange()?{
??????List?result?
=
?empBusiness.getEmployeesWithinSalaryRange
????????????????????(salaryMap);
??????assertEquals(expResult,?result);????????
????}?????
}
使用變量綁定
到目前為止,我已經演示了搜索最低工資和最高工資之間的雇員。讓我們再假設某個情景,比如業務用戶想讓搜索范圍顛倒一下。DAO 代碼是如此脆弱以致于需要修改才能對應這種改變。這個問題是由于用到的位置變量綁定(由"?"表示)是靜態的。Spring DAO 提供了以命名方式的變量綁定來化解這一問題。下列代碼在 IEmployeeDAO 中引入了以命名方式的變量綁定(由:"<名稱>"表示),注意查詢部分的變化:
import
?java.util.Map;
public
?
interface
?IEmployeeDAO?{
??
//
SQL?String?that?will?be?executed
??
public
?String?FIND_BY_SAL_RNG?
=
?
"
SELECT?EMP_NO,?EMP_NAME,?
"
??
+
?
"
SALARY?FROM?EMP?WHERE?SALARY?>=?:max?AND?SALARY?<=?:min
"
;
??
//
Returns?the?list?of?employees?falling?into?the?given?salary?range
??
//
The?input?parameter?is?the?immutable?map?object?obtained?from?
??
//
the?HttpServletRequest.?This?is?an?early?refactoring?based?on?
??
//
-?"Introduce?Parameter?Object"
??
public
?List?findBySalaryRange(Map?salaryMap);
}
大部分的 JDBC 驅動只支持位置變量綁定。因此在運行時,Spring DAO 還是把查詢轉換為位置變量綁定,并設置適當的綁定值。為了完成這一操作,現在你要使用
NamedParameterJdbcDaoSupport
和?
NamedParameterJdbcTemplate
類來代替 JdbcDaoSupport 和 JdbcTemplate。下面是修改后的 DAO 實現類:
import
?org.springframework.jdbc.core.namedparam.NamedParameterJdbcDaoSupport;
import
?org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
public
?
class
?EmployeeDAOImpl?
extends
?NamedParameterJdbcDaoSupport?
????
implements
?IEmployeeDAO{
??
public
?List?findBySalaryRange(Map?salaryMap){
????NamedParameterJdbcTemplate?tmplt?
=
?
?????????????????????????????
this
.getNamedParameterJdbcTemplate();
????
return
?tmplt.queryForList(IEmployeeDAO.FIND_BY_SAL_RNG
????????????????,salaryMap);?
??}
}
NamedParameterJdbcDaoSupport 的 getNamedParameterJdbcTemplate() 方法返回一個已經由數據源預初始化的 NamedParameterJdbcTemplate 實例。Spring Beanfactory 從配置文件獲取詳細信息完成初始化操作。在執行的時候 NamedParameterJdbcTemplate 每次都委派 JdbcTemplate 進行從命名參數到位置占位符的操作。你會發現命名參數的引入使任何潛在的 SQL 語句的改變都不會影響到 DAO 方法。
最后,如果你使用的數據庫不支持自動類型轉換的話,JUnit 測試類中的 initSalaryMap() 方法要做點小調整。
private
?
void
?initSalaryMap()?{
????????salaryMap?
=
?
new
?HashMap();
????????salaryMap.put(
"
MIN_SALARY
"
,
new
?Double(
1
));
????????salaryMap.put(
"
MAX_SALARY
"
,
new
?Double(
50
));
????}
Spring DAO 回調(Callbacks)
到目前為止我已經介紹了如何使 JDBC 代碼的靜態部分在 JdbcTemplate 類中實現封裝和通用特性來解決傳統 DAO 設計的弊端。現在讓我們把目光轉移到變量方面,比如設置變量綁定以及遍歷 ResultSet。盡管 Spring DAO 有了通用的解決方案,但在某些基于 SQL 的環境中,你可能要設置變量綁定。
通過把代碼轉換到 Spring DAO,我引入了一個微妙的運行時錯誤,這破壞了業務服務和客戶端之間的契約。錯誤的源頭可回溯到最初的 DAO,將不會返回一列 EmployeeTO 實例,而是通過 dbcTemplate.queryForList() 方法返回一列 map(每個 map 中存儲的值都是結果集的行)。
正如你了解的,JdbcTemplate 是基于模板方法設計模式,利用 JDBC API 定義 SQL 執行流程。這個流程必須被修改以修正被破壞的契約。首選方案是在子類中修改/繼承這個流程。你可以遍歷 JdbcTemplate.queryForList() 返回的列(list)接著用 EmployeeTO 實例替換 map 中的對象。不過,將導致混合使用靜態和動態代碼,這是我想努力避免的。第二種方案是把代碼加入多個流程來改變 JdbcTemplate 提供的鉤子。這必須要封裝 transfer object 的轉換代碼到不同的類中,接著通過鉤子連接它。這樣任何轉換邏輯的改變不會引起 DAO 的改變。
很明顯,第二種方案是首選,通過實現由 Spring 框架接口規范定義的方法的類來完成。這些方法被稱作回調,并通過框架的 JdbcTemplate 來注冊。當某些事件發生時(比如在框架無關的 transfer objects 中遍歷和轉換 ResultSet)這些方法會通過框架被調用。
第一步:transfer object
下面是你可能感興趣的 transfer object。注意 transfer object 已經被修正了:
package
?com.bea.dev2dev.to;
public
?
final
?
class
?EmployeeTO?
implements
?Serializable{
??????
private
?
int
?empNo;???
??????
private
?String?empName;???
??????
private
?
double
?salary;
??????
/**
?Creates?a?new?instance?of?EmployeeTO?
*/
??????
public
?EmployeeTO(
int
?empNo,String?empName,
double
?salary)?{
??????????
this
.empNo?
=
?empNo;
??????????
this
.empName?
=
?empName;
??????????
this
.salary?
=
?salary;
??????}
??????
public
?String?getEmpName()?{
??????????
return
?
this
.empName;
??????}
??????
public
?
int
?getEmpNo()?{
??????????
return
?
this
.empNo;
??????}
??????
public
?
double
?getSalary()?{
??????????
return
?
this
.salary;
??????}
??????
public
?
boolean
?equals(EmployeeTO?empTO){
??????????
return
?empTO.empNo?
==
?
this
.empNo;
??????}
}
第二步:實現回調接口
RowMapper 是實現從結果集轉換到 transfer object 的接口。例如:
package
?com.bea.dev2dev.dao.mapper;
import
?com.bea.dev2dev.to.EmployeeTO;
import
?java.sql.ResultSet;
import
?java.sql.SQLException;
import
?org.springframework.jdbc.core.RowMapper;
public
?
class
?EmployeeTOMapper?
implements
?RowMapper{
??
public
?Object?mapRow(ResultSet?rs,?
int
?rowNum)?
?????????????????????????????????????????
throws
?SQLException{
??????
int
?empNo?
=
?rs.getInt(
1
);
??????String?empName?
=
?rs.getString(
2
);
??????
double
?salary?
=
?rs.getDouble(
3
);
??????EmployeeTO?empTo?
=
?
new
?EmployeeTO(empNo,empName,salary);
??????
return
?empTo;
???}
}
注意實現類沒有調用 ResultSet 對象的 next() 方法。這一細節已經由框架處理好了,它可以從結果集提取相應行的準確值。回調實現類產生的任何 SQLException 也會由 Spring 框架處理。
第三步:插入回調接口
通常,當 SQL 查詢執行時,JdbcTemplate 利用默認的 RowMapper 實現類生成 map 列。現在需要注冊定制的回調實現類來改變 JdbcTemplate 的行為。注意,我現在使用 NamedParameterJdbcTemplate 的 query() 方法來代替 queryForList() 方法:
public
?
class
?EmployeeDAOImpl?
extends
?NamedParameterJdbcDaoSupport?
????
implements
?IEmployeeDAO{
??
public
?List?findBySalaryRange(Map?salaryMap){
????NamedParameterJdbcTemplate?daoTmplt?
=
?
??????????getNamedParameterJdbcTemplate();
????
return
?daoTmplt.query(IEmployeeDAO.FIND_BY_SAL_RNG,?salaryMap,
??????????
new
?EmployeeTOMapper());
??}
}
Spring DAO 框架利用查詢執行后返回的結果進行遍歷。每一步的遍歷,框架都調用 EmployeeTOMapper 類實現的 mapRow() 方法來進行從結果集的行轉換到 EmployeeTO transfer object 的轉換。
第四步:修改 JUnit 類
現在對返回的 transfer objects 進行測試,因此要修改測試方法。
public
?
class
?EmployeeBusinessServiceImplTest?
extends
?TestCase?{
??
private
?IEmployeeBusinessService?empBusiness;
??
private
?Map?salaryMap;
??????List?expResult;
??????
//
?all?methods?not?shown?in?the?listing?remain?the?
??????
//
?same?as?in?the?previous?example
??????
private
?
void
?initExpectedResult()?{
??????????expResult?
=
?
new
?ArrayList();
??????????EmployeeTO?to?
=
?
new
?EmployeeTO(
2
,
"
John
"
,
46.11
);
??????????expResult.add(to);
??????}
??????
/**
???????*?Test?of?getEmployeesWithinSalaryRange?method,?of?
???????*?class?com.bea.dev2dev.business.
???????*?EmployeeBusinessServiceImpl
???????
*/
??????
public
?
void
?testGetEmployeesWithinSalaryRange()?{
??????????List?result?
=
?empBusiness.
????????getEmployeesWithinSalaryRange(salaryMap);
??????????assertEquals(expResult,?result);????????
??????}
??????
public
?
void
?assertEquals(List?expResult,?List?result){
??????????EmployeeTO?expTO?
=
?(EmployeeTO)?expResult.get(
0
);
??????????EmployeeTO?actualTO?
=
?(EmployeeTO)?result.get(
0
);
??????????
if
(
!
expTO.equals(actualTO)){
???????????????
throw
?
new
?RuntimeException(
"
**?Test?Failed?**
"
);
??????????}?????
??????}
}
優勢
Spring JDBC 框架的優勢就是清晰,我示范了其重要的優勢和如何把 DAO 代碼減少到數行。代碼變得不再脆弱,這得感謝框架所提供一步到位的以命名方式的變量綁定,還分離了 transfer object 遷移邏輯到 mapper 中。長久以來的資源泄漏問題和錯誤處理不再是首要問題了。
Spring JDBC 的優勢是鼓勵你把現有的代碼遷移到該框架中。希望本文能盡可能的幫助你,讓你了解一些工具和
重構
的知識。例如,如果你沒有采用 P2I
提取接口
,重構時只有由現有 DAO 實現類創建接口。除此以外,再請關注一下本文的附加參考信息。
下載
你可以
在此
下載本文中的源碼。
結論
在本文中,首先我向大家講解了數據訪問對象(DAO)設計模式的基本概念,并從正反兩方面進行討論。Spring DAO、JDBC 框架的介入指出了傳統 DAO 的缺點。接著,脆弱的 DAO 代碼也被 Spring 框架命名參數的支持所修正。最后,回調特性示范了如何改變框架行為。
參考
J2EE 核心模式:數據訪問對象
(Sun 開發者網)——對 DAO 設計模式的詳細描述
Spring DAO 框架
——Spring DAO 官方文檔
Spring 和 WebLogic Server 集成
(Dev2Dev)——了解如何讓 Spring 與 WebLogic Server 集成
WebLogic 8.1 數據源配制
(文檔)——該文檔提供了利用管理控制臺配制數據源的詳細步驟
重構
——該網站介紹了重構的基礎知識和 Martin Fowler 書中關于重構細節的所有分類,重構:改善現有代碼的設計;該網站也包括了一系列的重構工具。
請注意!引用、轉貼本文應注明原譯者:Rosen Jiang 以及出處:
http://www.tkk7.com/rosen
posted on 2007-02-28 21:47
Rosen
閱讀(7616)
評論(3)
編輯
收藏
所屬分類:
模式與策略
評論
#
re: Spring 數據訪問對象(Data Access Object,DAO)框架入門(翻譯)
2007-03-19 15:11
checker
用來入門還不錯,贊。
回復
更多評論
#
re: Spring 數據訪問對象(Data Access Object,DAO)框架入門(翻譯)
2007-04-03 17:17
sandy
譯者辛苦了。
回復
更多評論
#
re: Spring 數據訪問對象(Data Access Object,DAO)框架入門(翻譯)
2007-04-19 06:17
no
不錯!譯者辛苦了!
回復
更多評論
新用戶注冊
刷新評論列表
只有注冊用戶
登錄
后才能發表評論。
網站導航:
博客園
IT新聞
Chat2DB
C++博客
博問
管理
相關文章:
《敏捷軟件開發模型—Scrum》ppt
Ajax輪詢以及Comet模式—寫在Servlet 3.0發布之前
Spring 數據訪問對象(Data Access Object,DAO)框架入門(翻譯)
單點登陸(Single Sign-On,SSO)標準的范圍(翻譯)
單點登陸(Single Sign-On,SSO)介紹(翻譯)
利用 Spring 和 EHCache 緩存結果(翻譯)
Powered by:
BlogJava
Copyright © Rosen
主站蜘蛛池模板:
午夜亚洲国产精品福利
|
亚洲免费综合色在线视频
|
无码人妻一区二区三区免费视频
|
国产精品亚洲精品观看不卡
|
日本免费人成视频在线观看
|
亚洲人成电影在线天堂
|
日韩精品久久久久久免费
|
久久99亚洲网美利坚合众国
|
亚洲香蕉免费有线视频
|
毛片免费在线观看网站
|
亚洲乱人伦精品图片
|
伊人免费在线观看
|
www亚洲精品少妇裸乳一区二区
|
国产亚洲精品美女久久久久
|
日韩免费视频在线观看
|
亚洲精品蜜夜内射
|
亚洲AV无码成人精品区大在线
|
有色视频在线观看免费高清在线直播
|
亚洲免费日韩无码系列
|
成全视频免费观看在线看
|
亚洲高清在线mv
|
精品国产免费观看一区
|
亚美影视免费在线观看
|
麻豆亚洲AV永久无码精品久久
|
免费中文熟妇在线影片
|
久久亚洲AV无码精品色午夜
|
女人体1963午夜免费视频
|
亚洲第一页中文字幕
|
免费观看在线禁片
|
亚洲va成无码人在线观看
|
国产成人免费a在线资源
|
中国极品美軳免费观看
|
亚洲人成网站在线观看播放动漫
|
女人18特级一级毛片免费视频
|
亚洲国产av美女网站
|
四虎影视永久免费观看地址
|
国产精品午夜免费观看网站
|
亚洲色四在线视频观看
|
四虎成人精品在永久免费
|
免费A级毛片无码专区
|
亚洲暴爽av人人爽日日碰
|