DAO程式設計模式(J2EE) -轉載

fsz521job發表於2006-07-04
J2EE開發人員使用資料訪問物件(DAO)設計模式把底層的資料訪問邏輯和高層的商務邏輯分開.實現DAO模式能夠更加專注於編寫資料訪問程式碼.在這篇文章中,Java開發人員Sean C. Sullivan從三個方面討論DAO程式設計的結構特徵:事務劃分,異常處理,日誌記錄.[@more@]
在最近的18個月,我和一個優秀的軟體開發團隊一起工作,開發定製基於WEB的供應鏈管理應用程式.我們的應用程式訪問廣泛的持久層資料,包括出貨狀態,供應鏈制度,庫存,貨物發運,專案管理資料,和使用者屬性等.我們使用JDBC API連線我們公司的各種資料庫平臺,並且在整個應用程式中應用了DAO設計模式.
下圖顯示了應用程式和資料來源的關係:


透過在整個應用程式中應用資料訪問物件(DAO)設計模式使我們能夠把底層的資料訪問邏輯和上層的商務邏輯分開.我們為每個資料來源建立了提供CRUD(建立,讀取,更新,刪除)操作的DAO類.

在之篇文章中,我將向你介紹DAO的實現策略以及建立更好的DAO類的技術.我會明確的介紹日誌記錄,異常處理,和事務劃分三項技術.你將學在你的DAO類中怎樣把這三種技術結合在一起.這篇文章假設你熟悉JDBC API,SQL和關係性資料庫程式設計.

我們先來回顧一下DAO設計模式和資料訪問物件.
DAO基礎
DAO模式是標準的J2EE設計模式之一.開發人員使用這個模式把底層的資料訪問操作和上層的商務邏輯分開.一個典型的DAO實現有下列幾個元件:
1. 一個DAO工廠類;
2. 一個DAO介面;
3. 一個實現DAO介面的具體類;
4. 資料傳遞物件(有些時候叫做值物件).

具體的DAO類包含了從特定的資料來源訪問資料的邏輯。在下面的這段中你將學到設計和實現資料訪問物件的技術。
事務劃分:
關於DAO要記住的一件重要事情是它們是事務性物件。每個被DAO執行的操作(象建立,更新、或刪除資料)都是和事務相關聯的。同樣的,事務劃分(transaction demarcation)的概念是特別重要的。
事務劃分是在事務界定定義中的方式。J2EE規範為事務劃分描述了兩種模式:程式設計性事務(programmatic)和宣告性事務(declarative).下表是對這兩種模式的拆分:
宣告性事務劃分 程式設計性事務劃分
程式設計師使用EJB的佈署描述符宣告事務屬性 程式設計師擔負編寫事務邏輯程式碼的責任。
執行時環境(EJB容器)使用這些屬性來自動的管理事務。 應用程式透過一個API介面來控制事務。

我將把注意力集中的程式設計性事務劃分上。
象前面的介紹一樣,DAOs是一些事務物件。一個典型的DAO要執行象建立、更新、和刪除這的事務性操作。在設計一個DAO時,首先要問自己如下問題:
1、 事務將怎樣開始?
2、 事務將怎樣結束?
3、 那個物件將承擔起動一個事務的責任?
4、 那個物件將承擔結束一個事務的責任?
5、 DAO應該承擔起動和結束事務的責任?
6、 應用程式需要交叉訪問多個DAO嗎?
7、 一個事務包含一個DAO還是多個DAO?
8、 一個DAO包含其它的DAO中的方法嗎?

回答這些問題將有助於你為DAO物件選擇最好的事務劃分策略。對ADO中的事務劃分有兩個主要的策略。一種方法是使用DAO承擔事務劃分的責任;另一種是延期性事務,它把事務劃分到呼叫DAO物件的方法中。如果你選擇前者,你將要在DAO類中嵌入事務程式碼。如果你選擇後者,事務程式碼將被寫在DAO類的外部。我們將使用簡單的程式碼例項來更好的理解這兩種方法是怎樣工作的。
例項1展示了一個帶有兩種資料操作的DAO:建立(create)和更新(update):
public void createWarehouseProfile(WHProfile profile);
public void updateWarehouseStatus(WHIdentifier id, StatusInfo status);
例項2展示了一個簡單的事務,事務劃分程式碼是在DAO類的外部。注意:在這個例子中的呼叫者把多個DOA操作組合到這個事務中。

tx.begin(); // start the transaction
dao.createWarehouseProfile(profile);
dao.updateWarehouseStatus(id1, status1);
dao.updateWarehouseStatus(id2, status2);
tx.commit(); // end the transaction

這種事務事務劃分策略對在一個單一事務中訪問多個DAO的應用程式來說尤為重要。

你即可使用JDBC API也可以使用Java 事務API(JTA)來實現事務的劃分。JDBC事務劃分比JTA事務劃分簡單,但是JTA提供了更好的靈活性。在下面的這段中,我們會進一步的看事務劃分機制。
使用JDBC的事務劃分
JDBC事務是使用Connection物件來控制的。JDBC的連線介面(java.sql.Connection)提供了兩種事務模式:自動提交和手動提交。Java.sql.Connection為控制事務提供了下列方法:
.public void setAutoCommit(Boolean)
.public Boolean getAutoCommit()
.public void commit()
.public void rollback()
例項3展示怎樣使用JDBC API來劃分事務:
import java.sql.*;
import javax.sql.*;
// ...
DataSource ds = obtainDataSource();
Connection conn = ds.getConnection();
conn.setAutoCommit(false);
// ...
pstmt = conn.prepareStatement("UPDATE MOVIES ...");
pstmt.setString(1, "The Great Escape");
pstmt.executeUpdate();
// ...
conn.commit();
// ...

使用JDBC事務劃分,你能夠把多個SQL語句組合到一個單一事務中。JDBC事務的缺點之一就是事務範圍被限定在一個單一的資料庫連線中。一個JDBC事務不能夠跨越多個資料庫。接下來,我們會看到怎樣使用JTA來做事務劃分的。因為JTA不象JDBC那樣被廣泛的瞭解,所以我首先概要的介紹一下JTA。

JTA概要介紹
Java事務API(JTA Java Transaction API)和它的同胞Java事務服務(JTS Java Transaction Service),為J2EE平臺提供了分散式事務服務。一個分散式事務(distributed transaction)包括一個事務管理器(transaction manager)和一個或多個資源管理器(resource manager)。一個資源管理器(resource manager)是任意型別的持久化資料儲存。事務管理器(transaction manager)承擔著所有事務參與單元者的相互通訊的責任。下車站顯示了事務管理器和資源管理的間的關係。


JTA事務比JDBC事務更強大。一個JTA事務可以有多個參與者,而一個JDBC事務則被限定在一個單一的資料庫連線。下列任一個Java平臺的元件都可以參與到一個JTA事務中:
.JDBC連線
.JDO PersistenceManager 物件
.JMS 佇列
.JMS 主題
.企業JavaBeans(EJB)
.一個用J2EE Connector Architecture 規範編譯的資源分配器。

使用J他的事務劃分
要用JTA來劃分一個事務,應用程式呼叫javax.transaction.UserTransaction介面中的方法。示例4顯示了一個典型的JNDI搜尋的UseTransaction物件。
import javax.transaction.*;
import javax.naming.*;
// ...
InitialContext ctx = new InitialContext();
Object txObj = ctx.lookup("java:comp/UserTransaction");
UserTransaction utx = (UserTransaction) txObj;

應用程式有了UserTransaction物件的引用之後,就可以象示例5那樣來起動事務。

utx.begin();
// ...
DataSource ds = obtainXADataSource();
Connection conn = ds.getConnection();
pstmt = conn.prepareStatement("UPDATE MOVIES ...");
pstmt.setString(1, "Spinal Tap");
pstmt.executeUpdate();
// ...
utx.commit();
// ...

當應用程式呼叫commit()時,事務管理器使用兩段提交協議來結束事務。
JTA事務控制的方法
.javax.transaction.UserTransaction介面提供了下列事務控制方法:
.public void begin()
.public void commit()
.public void rollback()
.public void getStatus()
.public void setRollbackOnly()
.public void setTransactionTimeout(int)
應用程式呼叫begin()來起動事務,即可呼叫commit()也可以呼叫rollback()來結束事務。
使用JTA和JDBC
開發人員經常使用JDBC來作為DAO類中的底層資料操作。如果計劃使用JTA來劃分事務,你將需要一個實現了javax.sql.XADataSource,javax.sql.XAConnection和javax.sql.XAResource介面JDBC的驅動。實現了這些介面的驅動將有能力參與到JTA事務中。一個XADataSource物件是一個XAConnection物件的工廠。XAConnections是參與到JTA事務中的連線。

你需要使用應用程式伺服器管理工具來建立XADataSource物件。對於特殊的指令請參考應用程式伺服器文件和JDBC驅動文件。

J2EE應用程式使用JNDI來查詢資料來源。一旦應用程式有了一個資料來源物件的引用,這會呼叫javax.sql.DataSource.getConnection()來獲得資料庫的連線。

XA連線區別於非XA連線。要記住的是XA連線是一個JTA事務中的參與者。這就意味著XA連線不支援JDBC的自動提交特性。也就是說應用程式不必在XA連線上呼叫java.sql.Connection.commit()或java.sql.Connection.rollback()。相反,應用程式應該使用UserTransaction.begin()、UserTransaction.commit()和UserTransaction.rollback().

選擇最好的方法
我們已經討論了JDBC和JTA是怎樣劃分事務的。每一種方法都有它的優點,回此你需要決定為你的應用程式選擇一個最適應的方法。

在我們團隊許多最近的對於事務劃分的專案中使用JDBC API來建立DAO類。這DAO類總結如下:
.事務劃分程式碼被嵌入到DAO類內部
.DAO類使用JDBC API來進行事務劃分
.呼叫者沒有劃分事務的方法
.事務範圍被限定在一個單一的JDBC連線

JDBC事務對複雜的企業應用程式不總是有效的。如果你的事務將跨越多個DAO物件或
多個資料庫,那麼下面的實現策略可能會更恰當。:
.用JTA對事務進行劃分
.事務劃分程式碼被DAO分開
.呼叫者承擔劃分事務的責任
.DAO參與一個全域性的事務中

JDBC方法由於它的簡易性而具有吸引力,JTA方法提供了更多靈活性。你選擇什麼樣的實現將依賴於你的應用程式的特定需求。

日誌記錄和DAO
一個好的DAO實現類將使用日誌記錄來捕獲有關它在執行時的行為細節。你可以選擇記錄異常、配置資訊、連線狀態、JDBC驅動程式的後設資料或查詢引數。日誌對開發整個階段都是有益的。我經常檢查應用程式在開發期間、測試期間和產品中的日誌記錄。
在這段中,我們將展現一段如何把Jakarta Commaons Logging結合中一個DAO中的例子。在我們開始之前,讓我們先回顧一些基礎知識。

選擇一個日誌例庫
許多開發人員使用的基本日誌形式是:System.out.println和System.err.println.Println語句。這種形式快捷方便,但它們不能提供一個完整的日誌系統的的能力。下表列出了Java平臺的日誌類庫:
日誌類庫 開源嗎? URL
Java.util.logging 否

Jakarta Log4j 是
Jakarta Commons Logging 是 http:/Jakarta.apache.org/commons/logging.html

Java.util.logging是J2SE1.4平臺上的標準的API。但是,大多數開發人員都認為Jakarta Log4j提供了更大的功能性和靈活性。Log4j超越java.util.logging的優點之一就是它支援J2SE1.3和J2SE1.4平臺。

Jakarta Commons Logging能夠被用於和java.util.loggin或Jakarta Log4j一起工作。Commons Logging是一個把你的應用程式獨立於日誌實現的提取層。使用Commons Logging你能夠透過改變一個配置檔案來與下面的日誌實現來交換資料。Commons Logging被用於JAKARTA Struts1.1和Jakarta HttpClient2.0中。

一個日誌示例
示例7顯示了在一個DOA類中怎樣使用Jakarta Commons Logging

import org.apache.commons.logging.*;
class DocumentDAOImpl implements DocumentDAO
{
static private final Log log = LogFactory.getLog(DocumentDAOImpl.class);
public void deleteDocument(String id)
{
// ...
log.debug("deleting document: " + id);
// ...
try
{
// ... data operations ...
}
catch (SomeException ex)
{
log.error("Unable to delete document", ex);
// ... handle the exception ...
}
}
}

日誌是評估應用程式的基本部分。如果你在一個DAO中遇到了失敗,日誌經常會為理解發生的什麼錯誤提供最好的資訊。把日誌結合到你的DAO中,確保得到除錯和解決問題的有效手段。

DAO中的異常處理
我們已經看了事務劃分和日誌記錄,並且現在對於它們是怎樣應用於資料訪問物件的有一個深入的理解。我們第三部分也是最後要討論的是異常處理。下面的一些簡單的異常處理方針使用你的DAO更容易使用,更加健壯和更具有可維護性。

在實現DAO模式的時候,要考濾下面的問題:
.在DAO的public介面中的方法將丟擲被檢查的異常嗎?
.如果是,將丟擲什麼樣的檢查性異常?
.在DAO實現類中怎能樣處理異常。
在用DAO模式工作的過程中,我們的團隊為異常處理開發了一組方針。下面的這些方針會很大程度的改善你的DAO:
.DAO方法應該丟擲有意義的異常。
.DAO方法不應該丟擲java.lang.Exception異常。因為java.lang.Exception太一般化,它不能包含有關潛在問題的所有資訊。
.DAO方法不應該丟擲java.sql.SQLException異常。SQLException是一個底層的JDBC異常,DAO應用努力封裝JDBC異常而不應該把JDBC異常留給應用程式的其它部分。
.在DAO介面中的方法應該只丟擲呼叫者期望處理的檢查性異常。如果呼叫者不能用適當的方法來處理異常,考濾丟擲不檢查性(執行時run-time)異常。

.如果你的資料訪問程式碼捕獲了一個異常,不可要忽略它。忽略捕獲異常的DAO是很處理的。
.使用異常鏈把底層的異常傳遞給高層的某個處理器。
.考濾定義一個標準的DAO異常類。Spring框架提供了一個優秀的預定義的DAO異常類的集合。
看Resources,檢視有異常和異常處理技術的更詳細資訊。

實現示例:MovieDAO
MoveDAO是一個示範了在這篇文章中所討論的所有技術,包括事務劃分、日誌記錄和異常處理。你會在Resources段找到MovieDAO的原始碼。它被分下面的三個包:
.daoexamples.exception
.daoexamples.move
.daoexamples.moviedemo

這個DAO模式的實現由下面的類和介面組成:
.daoexamples.movie.MovieDAOFactory
.daoexamples.movie.MovieDAO
.daoexamples.movie.MovieDAOImpl
.daoexamples.movie.MovieDAOImplJTA
.daoexamples.movie.Movie
.daoexamples.movie.MovieImple
.daoexamples.movie.MovieNotFoundException
.daoexamples.movie.MovieUtil

MovieDAO介面定義了DAO的資料操作。這個介面有如下五個方法:
.public Movie findMovieById(String id)
.public java.util.Collection findMoviesByYear(String year)
.public void deleteMovie(String id)
.public Movie createMovie(String rating,String year,String title)
.public void updateMovie(String id,String rating,String year,String title)
daoexamples.movie包包含了兩個MovieDAO介面的實現。每個實現使用了一個同的事務劃分方法,如下表所示:
MovieDAOImpl MovieDAOImplJTA
實現了MovieDAO介面嗎? Yes Yes
透過JNDI獲得DataSource嗎? Yes Yes
從一個DataSource獲得java.sql.Connection物件嗎? Yes Yes
DAO界定內部的事務嗎? Yes No
使用JDBC事務嗎? Yes No
使用一個XA DataSource嗎? No Yes
分擔JTA事務嗎? No Yes


MovieDAO 示範應用程式
這個示範應用程式是一個叫做daoexamples.moviedemo.DemoServlet.DemoServlet的servlet類,它使用Movie DAO來查詢和更新一個表中的movie資料。

這個servlet示範了把JTA感知的MovieDAO和Java訊息服務組合到一個單一的事務中,如示例8所示:
UserTransaction utx = MovieUtil.getUserTransaction();
utx.begin();
batman = dao.createMovie("R",
"2008",
"Batman Reloaded");
publisher = new MessagePublisher();
publisher.publishTextMessage("I'll be back");
dao.updateMovie(topgun.getId(),
"PG-13",
topgun.getReleaseYear(),
topgun.getTitle());
dao.deleteMovie(legallyblonde.getId());
utx.commit();

要執行這個範例應用程式,在你的應用程式伺服器中配置一個XA 資料來源和一個非XA資料來源。然後佈署daoexamples.ear檔案。這個應用程式將執行在任何與J2EE相容的應用程式伺服器。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/300209/viewspace-846970/,如需轉載,請註明出處,否則將追究法律責任。

相關文章