JavaData Access Objects(DAO)編程模入門
J2EE開發人員使用Data Access Objects(DAO)設計模式把底層的資料訪問邏輯和高層的商務邏輯分開。實現DAO模式能夠更加專註於編寫資料存取碼。這篇文章中,Java開發人員Sean C. Sullivan從三個方面討論DAO編程的結構特徵:事務劃分,異常處理,日誌記錄。
在最近的18個月,我和一個優秀的軟體Team Dev一起工作,開發定製基於WEB的供應鏈管理應用程式.我們的應用程式訪問廣泛的持久層資料,包括出貨狀態,供應鏈制度,庫存,貨物發運,專案管理資料,和使用者屬性等.我們使用JDBC API串連我們公司的各種資料庫平台,並且在整個應用程式中應用了DAO設計模式.
通過在整個應用程式中應用Data Access Objects(DAO)設計模式使我們能夠把底層的資料訪問邏輯和上層的商務邏輯分開.我們為每個資料來源建立了提供CRUD(建立,讀取,更新,刪除)操作的DAO類.
在本文中,我將向你介紹DAO的實現策略以及建立更好的DAO類的技術.我會明確的介紹日誌記錄,異常處理,和事務劃分三項技術.你將學在你的DAO類中怎樣把這三種技術結合在一起.這篇文章假設你熟悉JDBC API,SQL和關係性資料庫編程.
我們先來回顧一下DAO設計模式和Data Access Objects.
DAO基礎
DAO模式是標準的J2EE設計模式之一.開發人員使用這個模式把底層的資料訪問操作和上層的商務邏輯分開.一個典型的DAO實現有下列幾個組件:
1. 一個DAO工廠類;
2. 一個DAO介面;
3. 一個實現DAO介面的具體類;
4. 資料傳遞對象(有些時候叫做值對象).
具體的DAO類包含了從特定的資料來源訪問資料的邏輯。在下面的這段中你將學到設計和實現Data Access Objects的技術。
事務劃分:
關於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 規範編譯的資源分派器。
使用JTA的事務劃分
要用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()來結束事務。JavaData Access Objects(DAO)編程模入門2005-06-23 16:58 作者: muzi_li 翻譯 出處: Java研究組織 責任編輯:方舟
使用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 |
否 |
http://java.sun.com/j2ee |
| Jakarta Log4j |
是 |
http://hajarta.apache.org/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中的異常處理
我們已經看了事務劃分和日誌記錄,並且現在對於它們是怎樣應用於Data Access Objects的有一個深入的理解。我們第三部分也是最後要討論的是異常處理。下面的一些簡單的異常處理方針使用你的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和JavaMessage Service組合到一個單一的事務中,如樣本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相容的應用程式伺服器。