Java中DAO的實現

J2EE 開發人員使用數據訪問對象(Data Access Object DAO)設計模式,以便將低級別的數據訪問邏輯與高級別的業務邏輯分離。實現 DAO 模式涉及比編寫數據訪問代碼更多的內容。在本文中,Java 開發人員 Sean C. Sullivan 討論了 DAO 編程中三個經常被忽略的方面:事務界定、異常處理和日誌記錄。html

圖 1 顯示了應用程序和數據源之間的關係:


圖 1. 應用程序和數據源 
 java

在整個應用程序中使用數據訪問對象(DAO)使咱們能夠將底層數據訪問邏輯與業務邏輯分離開來。咱們構建了爲每個數據源提供 CRUD (建立、讀取、更新、刪除)操做的 DAO 類。
在本文中,我將爲您介紹構建更好的 DAO 類的 DAO 實現策略和技術。更確切地說,我將討論日誌、異常處理和事務界定。您將學到如何將這三者結合到本身的 DAO 類中。本文假定您熟悉 JDBC API、SQL 和關係數據庫編程。
咱們將以對 DAO 設計模式和數據訪問對象的概述開始。
DAO基礎
DAO 模式是標準 J2EE 設計模式之一。開發人員用這種模式將底層數據訪問操做與高層業務邏輯分離開。一個典型的 DAO 實現有如下組件:
  • 一個 DAO 工廠類
  • 一個 DAO 接口
  • 一個實現了 DAO 接口的具體類
  • 數據傳輸對象(有時稱爲值對象)
具體的 DAO 類包含訪問特定數據源的數據的邏輯。在下面一節中您將學習設計和實現數據訪問對象的技術。

事務界定程序員

關於 DAO 要記住的重要一點是它們是事務性對象。由 DAO 所執行的每個操做 -- 如建立、更新或者刪除數據 -- 都與一個事務相關聯。所以, 事務界定的概念就變得特別重要了。
事務界定是定義事務邊界的方式。J2EE 規範描述了兩種事務界定的模型:編程式(programmatic)和聲明式(declarative)。表 1 分析了這兩種模型:
表 1. 兩種事務界定的模型
 
聲明式事務界定 編程式事務界定
程序員用 EJB 部署描述符聲明事務屬性。 程序員負責編寫事務邏輯。
運行時環境(EJB 容器)用這些屬性自動管理事務。 應用程序經過一個 API 控制事務。
咱們將側重於編程式事務界定。
設計考慮
如前所述,DAO 是事務性對象。一個典型的 DAO 執行像建立、更新和刪除這樣的事務性操做。在設計 DAO 時,首先要問本身如下問題:
  • 事務要如何開始?
  • 事務應如何結束?
  • 哪個對象將負責開始一個事務?
  • 哪個對象將負責結束一個事務?
  • DAO 是否要負責事務的開始和結束?
  • 應用程序是否須要經過多個 DAO 訪問數據?
  • 事務涉及到一個 DAO 仍是多個 DAO?
  • 一個 DAO 是否調用另外一個 DAO 的方法?
瞭解上述問題的答案將有助於您選擇最適合的 DAO 的事務界定策略。在 DAO 中有兩種主要的界定事務的策略。一種方式是讓 DAO 負責界定事務,另外一種將事務界定交給調用這個 DAO 方法的對象處理。若是選擇了前一種方式,那麼就將事務代碼嵌入到 DAO 中。若是選擇後一種方式,那麼事務界定代碼就是在 DAO 類外面。咱們將使用簡單的代碼示例幫助您更好理解每一種方式是如何工做的。
清單 1 顯示了一個有兩種數據操做的 DAO:建立和更新:


清單 1. DAO 方法

sql

public void createWarehouseProfile(WHProfile profile);
public void updateWarehouseStatus(WHIdentifier id, StatusInfo status);

 

清單 2 顯示了一個簡單的事務。事務界定在 DAO 類外面。注意在這個例子中調用者是如何在一個事務中結合多個 DAO 操做的。


清單 2. 調用者管理的事務

數據庫

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(Java Transaction API JTA)實現事務界定。 JDBC 事務界定比 JTA 事務界定要簡單,可是 JTA 提供了更多的靈活性。在下面一節中我將更深刻地分析事務界定的機制。
用 JDBC 進行事務界定
JDBC 事務是用  Connection 對象控制的。JDBC Connection 接口(  java.sql.Connection )提供了兩種事務模式:自動提交和手工提交。  java.sql.Connection 提供瞭如下控制事務的方法:
  • public void setAutoCommit(boolean)
  • public boolean getAutoCommit()
  • public void commit()
  • public void rollback()
清單 3 顯示瞭如何用 JDBC API 界定一個事務:


清單 3. 用 JDBC API 進行事務界定

apache

      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簡介
Java 事務 API(JTA) 及其同門兄弟 Java 事務服務(Java Transaction Service JTS)爲 J2EE 平臺提供了分佈式事務服務。一個 分佈式的事務涉及一個事務管理器和一個或者多個資源管理器。一個 資源管理器是任何類型的持久性的數據存儲。事務管理器負責協調全部事務參與者之間的通訊。事務管理器與資源管理器之間的關係如圖 2 所示:


圖 2. 一個事務管理器和資源管理器 
 編程

JTA 事務比 JDBC 事務功能更強。JDBC 事務侷限爲一個數據庫鏈接,而 JTA 事務能夠有多個參與者。全部下列 Java 平臺組件均可以參與 JTA 事務:
  • JDBC 鏈接
  • JDO PersistenceManager 對象
  • JMS 隊列
  • JMS 主題
  • 企業 JavaBeans
  • 符合 J2EE 鏈接體系結構(J2EE Connector Architecture)規範的資源適配器

 

使用 JTA 的事務界定
要用 JTA 進行事務界定,應用程序要調用  javax.transaction.UserTransaction 接口中的方法。清單 4 顯示了對 UserTransaction 對象的典型 JNDI 查詢:


清單 4. 一個對 UserTransaction 對象的 JDNI 查詢設計模式

      import javax.transaction.*;
import javax.naming.*;
// ...
InitialContext ctx = new InitialContext();
Object txObj = ctx.lookup("java:comp/UserTransaction");
UserTransaction utx = (UserTransaction) txObj;

 

當應用程序找到了  UserTransaction 對象後,就能夠開始事務了,如清單 5 所示:


清單 5. 用 JTA 開始一個事務服務器

     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 int getStatus()
  • public void setRollbackOnly()
  • public void setTransactionTimeout(int)
應用程序調用  begin() 開始事務。應用程序調用  commit() 或者  rollback() 結束事務。
使用 JTA 和 JDBC
開發人員一般在 DAO 類中用 JDBC 進行底層數據操做。若是計劃用 JTA 界定事務,那麼就須要有一個實現 javax.sql.XADataSource 、  javax.sql.XAConnection 和  javax.sql.XAResource 接口的 JDBC 驅動程序。一個實現了這些接口的驅動程序將能夠參與 JTA 事務。一個  XADataSource 對象就是一個  XAConnection 對象的工廠。 XAConnection s 是參與 JTA 事務的 JDBC 鏈接。
您將須要用應用服務器的管理工具設置  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() 和  serTransaction.rollback() 。
選擇最好的方式
咱們討論瞭如何用 JDBC 和 JTA 界定事務。每一種方式都有其優勢,您須要決定哪種最適合於您的應用程序。
在最近的許多項目中,咱們小組是用 JDBC API 進事務界定來構建 DAO 類的。這些 DAO 類能夠總結以下:
  • 事務界定代碼嵌入在 DAO 類中。
  • DAO 類使用 JDBC API 進行事務界定。
  • 調用者不能界定事務。
  • 事務範圍侷限於單個 JDBC 鏈接。
JDBC 事務並不老是適合複雜的企業應用程序。若是您的事務要跨越多個 DAO 或者多個數據庫,那麼下列實現策略也許更合適:
  • 事務用 JTA 界定。
  • 事務界定代碼從 DAO 中分離出來。
  • 調用者負責界定事務。
  • DAO 加入一個全局事務。
JDBC 方式因爲其簡單性而具備吸引力,JTA 方式提供了更大的靈活性。您所選擇的實現將取決於應用程序的特定需求。
日誌記錄和 DAO
一個良好實現的 DAO 類將使用日誌記錄來捕捉有關其運行時行爲的細節。您能夠選擇記錄異常、配置信息、鏈接狀態、JDBC 驅動程序元數據、或者查詢參數。日誌對於開發的全部階段都頗有用。我常常在開發時、測試時和生產中分析應用程序日誌。
在本節,我將展現一個顯示如何將 Jakarta Commons Logging 加入到 DAO 中的代碼示例。在這以前,讓咱們回顧一下一些基本知識。
選擇日誌庫
許多開發人員使用一種原始格式進行日誌記錄:  System.out.println 和  System.err.println 。  Println 語句速度快且使用方便,可是它們沒有提供全功能的日誌記錄系統所具備的功能。表 2 列出了 Java 平臺的日誌庫:
表 2. Java 平臺的日誌庫
 
日誌庫 開放源代碼? URL
java.util.logging 不是 [url]http://java.sun.com/j2se/[/url]
Jakarta Log4j [url]http://jakarta.apache.org/log4j/[/url]
Jakarta Commons Logging [url]http://jakarta.apache.org/commons/logging.html[/url]
Jakarta Commons Logging 能夠與  java.util.logging 或者 Jakarta Log4j 一同使用。Commons Logging 是一個日誌抽象層,它隔離了應用程序與底層日誌實現。使用 Commons Logging,您能夠經過改變配置文件更換底層日誌實現。Commons Logging 在 Jakarta Struts 1.1 和 Jakarta HttpClient 2.0 中使用。
一個日誌記錄示例
清單 7 顯示瞭如何在 DAO 類中使用 Jakarta Commons Logging:


清單 7. DAO 類中的 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 的公共接口中的方法是否拋出檢查過的異常?
  • 若是是的話,拋出何種檢查過的異常?
  • 在 DAO 實現類中如何處理異常?
在使用 DAO 模式的過程當中,咱們的小組開發了一些處理異常的原則。聽從這些原則能夠極大地改進您的 DAO:
  • DAO 方法應該拋出有意義的異常。
  • DAO 方法不該該拋出 java.lang.Exception 。 java.lang.Exception 太通常化了。它不傳遞關於底層問題的任何信息。 
  • DAO 方法不該該拋出 java.sql.SQLException 。SQLException 是一個低級別的 JDBC 異常。一個 DAO 應該力爭封裝 JDBC 而不是將 JDBC 公開給應用程序的其他部分。 
  • 只有在能夠合理地預期調用者能夠處理異常時,DAO 接口中的方法才應該拋出檢查過的異常。若是調用者不能以有意義的方式處理這個異常,那麼考慮拋出一個未檢查的(運行時)異常。
  • 若是數據訪問代碼捕獲了一個異常,不要忽略它。忽略捕獲的異常的 DAO 是很難進行故障診斷的。
  • 使用連接的異常將低級別的異常轉化爲高級別的異常。
  • 考慮定義標準 DAO 異常類。Spring Framework 提供了很好的一套預約義的 DAO 異常類。

 

實現實例: MovieDAO[ daoexamples.sourceforge.net上下載 MovieDAO 源代碼]
MovieDAO 是一個展現本文中討論的全部技術的 DAO:事務界定、日誌和異常處理。  MovieDAO 源代碼。代碼分爲三個包:
  • daoexamples.exception
  • daoexamples.movie
  • daoexamples.moviedemo
DAO 模式的這個實現包含下面列出的類和接口:
  • daoexamples.movie.MovieDAOFactory
  • daoexamples.movie.MovieDAO
  • daoexamples.movie.MovieDAOImpl
  • daoexamples.movie.MovieDAOImplJTA
  • daoexamples.movie.Movie
  • daoexamples.movie.MovieImpl
  • 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 接口的兩個實現。每個實現使用一種不一樣的方式進行事務界定,如表 3 所示:
表 3. MovieDAO 實現
 
  MovieDAOImpl MovieDAOImplJTA
實現 MovieDAO 接口?
經過 JNDI 得到 DataSource?
從 DataSource 得到 java.sql.Connection 對象?
DAO 在內部界定事務?
使用 JDBC 事務?
使用一個 XA DataSource?
參與 JTA 事務?
MovieDAO 演示應用程序
這個演示應用程序是一個名爲  daoexamples.moviedemo.DemoServlet 的 servlet 類。  DemoServlet 使用這兩個 Movie DAO 查詢和更新表中的電影數據。
這個 servlet 展現瞭如何將支持 JTA 的  MovieDAO 和 Java 消息服務(Java Message Service)結合到一個事務中,如清單 8 所示。


清單 8. 將 MovieDAO 和 JMS 代碼結合到一個事務中

	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 1.3 的應用服務器上運行。

 

結束語
正如本文所展現的,實現 DAO 模式須要作比編寫低級別的數據訪問代碼更多的工做。如今,經過選擇一個適合您的應用程序的事務界定策略、經過在 DAO 類中加入日誌記錄,以及經過聽從幾項簡單的異常處理原則,您能夠構建更好的 DAO。
相關文章
相關標籤/搜索