J2EE開發人員使用數據訪問對象(DAO)設計模式把底層的數據訪問邏輯和高層的商務邏輯分開.實現DAO模式可以更加專一於編寫數據訪問代碼.
咱們先來回顧一下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時,首先要問本身以下問題:
一、 事務將怎樣開始?
二、 事務將怎樣結束?
三、 那個對象將承擔起動一個事務的責任?
四、 那個對象將承擔結束一個事務的責任?
五、 DAO應該承擔起動和結束事務的責任?
六、 應用程序須要交叉訪問多個DAO嗎?
七、 一個事務包含一個DAO仍是多個DAO?
八、 一個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()來結束事務。
使用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中的異常處理
咱們已經看了事務劃分和日誌記錄,而且如今對於它們是怎樣應用於數據訪問對象的有一個深刻的理解。咱們第三部分也是最後要討論的是異常處理。下面的一些簡單的異常處理方針使用你的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兼容的應用程序服務器。
DAO解決了JSP 頁面維護難的問題,使JSP不能用JAVA.SQL.*全部的數據庫代碼用PreapreStatemnet,hirnate,ibait的來作
DAO屬於J2EE數據庫的操做,
它封裝了一個項目的全部的數據庫操做
全部的
客戶---VO----DAO
VO對象和表中的字段徹底對於
查詢所有是返回LIST,所須要用Iterator來進行輸出
import java.util.* ;
import cn.mldn.lxh.vo.* ;
// 規定出了操做person表在此項目裏的所有方法
public interface PersonDAO
{
// 增長操做
public void insert(Person person) throws Exception ;
// 修改操做
public void update(Person person) throws Exception ;
// 刪除操做
public void delete(String id) throws Exception ;
// 按ID查詢操做
public Person queryById(String id) throws Exception ;
// 查詢所有
public List queryAll() throws Exception ;
// 模糊查詢
public List queryByLike(String cond) throws Exception ;
}
package cn.mldn.lxh.dao.impl ;
import java.sql.* ;
import java.util.* ;
import cn.mldn.lxh.vo.* ;
import cn.mldn.lxh.dbc.* ;
import cn.mldn.lxh.dao.* ;
// 此類須要完成具體的數據庫操做,須要JDB代碼
public class PersonDAOImpl implements PersonDAO
{
// 增長操做
public void insert(Person person) throws Exception
{
String sql = "INSERT INTO person (id,name,password,age,email) VALUES (?,?,?,?,?)" ;
PreparedStatement pstmt = null ;
DataBaseConnection dbc = null ;
// 下面是針對數據庫的具體操做
try
{
// 鏈接數據庫
dbc = new DataBaseConnection() ;
pstmt = dbc.getConnection().prepareStatement(sql) ;
pstmt.setString(1,person.getId()) ;
pstmt.setString(2,person.getName()) ;
pstmt.setString(3,person.getPassword()) ;
pstmt.setInt(4,person.getAge()) ;
pstmt.setString(5,person.getEmail()) ;
// 進行數據庫更新操做
pstmt.executeUpdate() ;
pstmt.close() ;
}
catch (Exception e)
{
throw new Exception("操做出現異常") ;
}
finally
{
// 關閉數據庫鏈接
dbc.close() ;
}
}
// 修改操做
public void update(Person person) throws Exception
{
String sql = "UPDATE person SET name=?,password=?,age=?,email=? WHERE id=?" ;
PreparedStatement pstmt = null ;
DataBaseConnection dbc = null ;
// 下面是針對數據庫的具體操做
try
{
// 鏈接數據庫
dbc = new DataBaseConnection() ;
pstmt = dbc.getConnection().prepareStatement(sql) ;
pstmt.setString(1,person.getName()) ;
pstmt.setString(2,person.getPassword()) ;
pstmt.setInt(3,person.getAge()) ;
pstmt.setString(4,person.getEmail()) ;
pstmt.setString(5,person.getId()) ;
// 進行數據庫更新操做
pstmt.executeUpdate() ;
pstmt.close() ;
}
catch (Exception e)
{
throw new Exception("操做出現異常") ;
}
finally
{
// 關閉數據庫鏈接
dbc.close() ;
}
}
// 刪除操做
public void delete(String id) throws Exception
{
String sql = "DELETE FROM person WHERE id=?" ;
PreparedStatement pstmt = null ;
DataBaseConnection dbc = null ;
// 下面是針對數據庫的具體操做
try
{
// 鏈接數據庫
dbc = new DataBaseConnection() ;
pstmt = dbc.getConnection().prepareStatement(sql) ;
pstmt.setString(1,id) ;
// 進行數據庫更新操做
pstmt.executeUpdate() ;
pstmt.close() ;
}
catch (Exception e)
{
throw new Exception("操做出現異常") ;
}
finally
{
// 關閉數據庫鏈接
dbc.close() ;
}
}
// 按ID查詢操做
public Person queryById(String id) throws Exception
{
Person person = null ;
String sql = "SELECT id,name,password,age,email FROM person WHERE id=?" ;
PreparedStatement pstmt = null ;
DataBaseConnection dbc = null ;
// 下面是針對數據庫的具體操做
try
{
// 鏈接數據庫
dbc = new DataBaseConnection() ;
pstmt = dbc.getConnection().prepareStatement(sql) ;
pstmt.setString(1,id) ;
// 進行數據庫查詢操做
ResultSet rs = pstmt.executeQuery() ;
if(rs.next())
{
// 查詢出內容,以後將查詢出的內容賦值給person對象
person = new Person() ;
person.setId(rs.getString(1)) ;
person.setName(rs.getString(2)) ;
person.setPassword(rs.getString(3)) ;
person.setAge(rs.getInt(4)) ;
person.setEmail(rs.getString(5)) ;
}
rs.close() ;
pstmt.close() ;
}
catch (Exception e)
{
throw new Exception("操做出現異常") ;
}
finally
{
// 關閉數據庫鏈接
dbc.close() ;
}
return person ;
}
// 查詢所有
public List queryAll() throws Exception
{
List all = new ArrayList() ;
String sql = "SELECT id,name,password,age,email FROM person" ;
PreparedStatement pstmt = null ;
DataBaseConnection dbc = null ;
// 下面是針對數據庫的具體操做
try
{
// 鏈接數據庫
dbc = new DataBaseConnection() ;
pstmt = dbc.getConnection().prepareStatement(sql) ;
// 進行數據庫查詢操做
ResultSet rs = pstmt.executeQuery() ;
while(rs.next())
{
// 查詢出內容,以後將查詢出的內容賦值給person對象
Person person = new Person() ;
person.setId(rs.getString(1)) ;
person.setName(rs.getString(2)) ;
person.setPassword(rs.getString(3)) ;
person.setAge(rs.getInt(4)) ;
person.setEmail(rs.getString(5)) ;
// 將查詢出來的數據加入到List對象之中
all.add(person) ;
}
rs.close() ;
pstmt.close() ;
}
catch (Exception e)
{
throw new Exception("操做出現異常") ;
}
finally
{
// 關閉數據庫鏈接
dbc.close() ;
}
return all ;
}
// 模糊查詢
public List queryByLike(String cond) throws Exception
{
List all = new ArrayList() ;
String sql = "SELECT id,name,password,age,email FROM person WHERE name LIKE ? or email LIKE ?" ;
PreparedStatement pstmt = null ;
DataBaseConnection dbc = null ;
// 下面是針對數據庫的具體操做
try
{
// 鏈接數據庫
dbc = new DataBaseConnection() ;
pstmt = dbc.getConnection().prepareStatement(sql) ;
// 設置模糊查詢條件
pstmt.setString(1,"%"+cond+"%") ;
pstmt.setString(2,"%"+cond+"%") ;
// 進行數據庫查詢操做
ResultSet rs = pstmt.executeQuery() ;
while(rs.next())
{
// 查詢出內容,以後將查詢出的內容賦值給person對象
Person person = new Person() ;
person.setId(rs.getString(1)) ;
person.setName(rs.getString(2)) ;
person.setPassword(rs.getString(3)) ;
person.setAge(rs.getInt(4)) ;
person.setEmail(rs.getString(5)) ;
// 將查詢出來的數據加入到List對象之中
all.add(person) ;
}
rs.close() ;
pstmt.close() ;
}
catch (Exception e)
{
throw new Exception("操做出現異常") ;
}
finally
{
// 關閉數據庫鏈接
dbc.close() ;
}
return all ;
}
};
package cn.mldn.lxh.factory ;
import cn.mldn.lxh.dao.* ;
import cn.mldn.lxh.dao.impl.* ;
public class DAOFactory
{
public static PersonDAO getPersonDAOInstance()
{
return new PersonDAOImpl() ;
}
};html