[设计模式]DAO的实现方式探讨_Part II

来源:互联网 发布:淘宝 我的评价中在哪里 编辑:程序博客网 时间:2024/06/11 10:26

Data Access Object Pattern

Problem

You want to encapsulate data access and manipulation in a separate layer

Forces

  • You want to implement data access mechanisms to access and manipulate data in a persistent storage.

  • You want to decouple the persistent storage implementation from the rest of your application.

  • You want to provide a uniform data access API for a persistent mechanism to various types of data sources, such as RDBMS, LDAP, OODB, XML repositories, flat files, and so on.

  • You want to organize data access logic and encapsulate proprietary features to facilitate maintainability and portability.

Solution

Use a Data Access Object to abstract and encapsulate all access to the persistent store. The Data Access Object manages the connection with the data source to obtain and store data.

The Data Access Object (also known simply as DAO) implements the access mechanism required to work with the data source. Regardless of what type of data source is used, the DAO always provides a uniform API to its clients. The business component that needs data access uses the simpler interface exposed by the DAO for its clients. The DAO completely hides the data source implementation details from its clients. Because the interface exposed by the DAO to clients does not change when the underlying data source implementation changes, this allows you to change a DAO implementation without changing the DAO client's implementation. Essentially, the DAO acts as an adapter between the component and the data source.

The DAO is implemented as a stateless. It does not cache the results of any query execution, or any data that the client might need at a later point. This makes the DAOs lightweight objects and avoids potential threading and concurrency issues. The DAO encapsulates the details of the underlying persistence API. For example, when an application uses JDBC, the DAO encapsulates all the JDBC usage inside the data access layer and does not expose any exception, data structure, object, or interface that belongs to the java.sql.* or javax.sql.* packages to the clients outside the data access layer.

 

Custom Data Access Object Strategy

很简单,就是定义2个类。
一个是数据类(Pojo),另一个是相应的DAO类,提供CRUD方法。

ya1 jingya1 shetou4 kaixin1


关于在DAO类中数据源的取得,可以采用Service Locator Pattern + Thread Local Pattern。

续后设计我的持久层的时候会涉及到。 duyan
 

Data Access Object Factory Strategies

为了增加DAO对象创建的灵活性,可以结合使用抽象工厂(Abstract Factory)模式和工厂方法(Factory Method)模式。这2个模式在 [GoF] 的设计模式中都有论述。

工厂方法模式用来产生各种各样的DAO对象,当程序中仅仅使用一个数据源的时候,使用工厂方法模式就足够了。

有的时候,程序可能需要更换数据源,或者同时使用2个或者2个以上的数据源。这个时候通过使用抽象工厂模式,可以增加工厂的灵活性。
 
 

DAO Factory Method strategy


public class OracleDAOFactory extends DAOFactory {

// package level constant used look up the

// DataSource name using JNDI

static String DATASOURCE_DB_NAME = "java:comp/env/jdbc/CJPOraDB";

public CustomerDAO getCustomerDAO() throws DAOException {

return (CustomerDAO) createDAO(CustomerDAO.class);

}

// public static EmployeeDAO getEmployeeDAO()
//
// throws DAOException {
//
// return (EmployeeDAO) createDAO(EmployeeDAO.class);
//
// }

// create other DAO instances

// method to create a DAO instance. Can be optimized to

// cache the DAO Class instead of creating it everytime.

private static Object createDAO(Class classObj) throws DAOException {

return null;

// create a new DAO using classObj.newInstance() or

// obtain it from a cache and return the DAO instance

}
}
duyan duyan duyan

DAO Abstract Factory strategy

public abstract class DAOFactory {

// List of DAO types supported by the factory

public static final int CLOUDSCAPE = 1;

public static final int ORACLE = 2;

public static final int SYBASE = 3;

// There will be a method for each DAO that can be

// created. The concrete factories will have to

// implement these methods.

public abstract CustomerDAO getCustomerDAO() throws DAOException;

// public abstract EmployeeDAO getEmployeeDAO() throws DAOException;

public static DAOFactory getDAOFactory(int whichFactory) {

switch (whichFactory) {

case CLOUDSCAPE:

// return new CloudscapeDAOFactory();

case ORACLE:

return new OracleDAOFactory();

case SYBASE:

// return new SybaseDAOFactory();

default:

return null;

}
}
}
duyan duyan duyan

Transfer Object Collection strategy

这个策略是最常使用的了。我想写过JDBC的人,估计都用过。
就是把结果集的每条记录都映射成一个数据对象,然后把数据对象都放到一个容器实例中并返回该实例。

不过,在返回的结果集很大的情况下,使用这种策略会非常消耗资源,因为该策略会针对结果集的每条记录都创建一个相应的数据对象。此时,可以考虑使用其它的策略,比如CachedRowSet strategy, Read Only RowSet strategy, 或者 RowSet Wrapper List strategy(最开始提到的那个 duyan ).

Cached RowSet Strategy

顾名思义,猜也能猜出来这个策略的含义。

JDK5.0中提供了5个RowSet接口的标准子接口。

JdbcRowSet - A connected RowSet providing JavaBean semantics.

CachedRowSet - A disconnected RowSet, providing JavaBean semantics and robust synchronization mechanisms.

WebRowSet - A disconnected RowSet provided synchronization mechanism that interact with XML data sources.

FilteredRowSet - A disconnected RowSet that permits a filtered inbound and outbound view on the RowSet data.

JoinRowSet - A disconnected RowSet that allows SQL JOIN relationships to be established between multiple CachedRowSets.


Sun公司提供了一套这些标准接口的实现,基本满足一般的需要。另外,不同的厂商也提供了自己的实现,比如Oracle也提供了一套。具体的选择需要根据项目的实际情况确定。


示例代码:

public class CustomerDAO {
。。。 。。。
// Create the CachedRowSet using the
// ResultSet from query execution
public RowSet findCustomersRS(CustomerTO criteria)

throws DAOException {



Connection con = getConnection();

// javax.sql.RowSet rowSet = null;
// CachedRowSetImpl rowSet = null;
OracleCachedRowSet rowSet = null;

String searchSQLString = getSearchSQLString(criteria);

try {

con = getConnection();

java.sql.Statement stmt = con.createStatement( );

java.sql.ResultSet rs = stmt.executeQuery(searchSQLString);

rowSet = new OracleCachedRowSet();

rowSet.populate(rs);

} catch (SQLException anException) {

// handle exception...

} finally {

try {
con.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}

return rowSet;

}
。。。 。。。
}

CachedRowSet

关于这个接口要特别的提一下,因为该接口提供了分页功能!! se1 kaixin1

以下说明及示例代码摘自JDK5.0的Javadoc:

CachedRowSet crs = new CachedRowSetImpl();
crs.setMaxRows(20);
crs.setPageSize(4);
crs.populate(rsHandle, 10);
运行此代码时,将使用 rsHandle 中从第 10 行开始的 4 行数据填充 crs。

下一个代码片断展示了如何使用方法 execute 填充 CachedRowSet 对象,该方法可以采用 Connection 对象作为一个参数,也可以不采用。此代码向 execute 传递 Connection 对象 conHandle。

注意,以下代码片断和上述代码片断有两处差别。首先,没有调用方法 setMaxRows,所以没有对 crs 可以包含的行数设置限制。(记住,对于 crs 在内存中可以存储的数据量,总是有一个最高限制。)第二个差别是不能向方法 execute 传递 ResultSet 对象中起始检索行的行号。此方法始终从第一行开始检索。

CachedRowSet crs = new CachedRowSetImpl();
crs.setPageSize(5);
crs.execute(conHandle);

在任意给定时间,内存中仅存储一个 CachedRowSet 对象的数据。

只要当前页不是各行的最后一页,方法 nextPage 就返回 true,没有其他页时,则返回 false。因此,可在 while 循环中使用它来检索所有页,正如在以下代码行中所演示的。

CachedRowSet crs = CachedRowSetImpl();
crs.setPageSize(100);
crs.execute(conHandle);

while(crs.next() {
. . . // operate on first chunk of 100 rows in crs, row by row
}

while(crs.nextPage()) {
while(crs.next()) {
. . . // operate on the subsequent chunks (of 100 rows each) in crs,
// row by row
}
}
运行此代码片断后,应用程序会遍历所有 1000 行,但是每次内存中的数据只有 100 行。


CachedRowSet 接口还定义了方法 previousPage。
通过将指针定位于每页最后一行的后面(如以下代码片断所执行的),方法 previous 就可以在每页中从最后一行遍历到第一行。代码也可将指针置于每页第一行的前面,然后在 while 循环中使用 next 方法,以在每页中从最第一行遍历到最后一行。

crs.afterLast();
while(crs.previous()) {
. . . // navigate through the rows, last to first
{
while(crs.previousPage()) {
crs.afterLast();
while(crs.previous()) {
. . . // go from the last row to the first row of each page
}
}

Read Only RowSet Strategy

这种策略和Cached RowSet Strategy的区别就是前者自己实现了RowSet接口,通过内部的成员变量实现了接口的方法,屏蔽掉RowSet的更新功能,从而实现了“只读”的目的。

public class CustomerDAO {
。。。 。。。
// Create the ReadOnlyRowSet using the
// ResultSet from query execution
public RowSet findCustomersRORS(CustomerTO criteria, int startAtRow,
int howManyRows) throws DAOException {

Connection con = getConnection();

javax.sql.RowSet rowSet = null;

String searchSQLString = getSearchSQLString(criteria);

try {

con = getConnection();

java.sql.Statement stmt = con.createStatement();

java.sql.ResultSet rs = stmt.executeQuery(searchSQLString);

rowSet = new ReadOnlyRowSet();

((ReadOnlyRowSet) rowSet).populate(rs, startAtRow, howManyRows);

} catch (SQLException anException) {

// handle exception...

} finally {

try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}

}

return rowSet;

}
。。。 。。。
}




public class ReadOnlyRowSet implements RowSet, Serializable {

private Object[] dataRows;

/** this is a read only row set */
public boolean isReadOnly() {
return true;
}

public void setReadOnly(boolean flag) throws SQLException {
throw new SQLException("ReadOnlyRowSet: Method not supported");
}

// Populates the rowset without the first startRow rows
// of the ResultSet and with a maximum number
// of rows specified by howManyRows
public void populate(ResultSet resultSet, int startRow, int howManyRows) throws SQLException {

// miscellaneous code not shown for brevity...
// Create a list to hold the row values
List dataRows = new LinkedList();

// determine the number of columns from the mete data
int numberOfColumns = resultSet.getMetaData().getColumnCount();

// Discard initial rows if beginAtRow was specified
setStartPosition(startRow, resultSet);

// if number of rows is unspecified,
// get all rows from resultset
if (howManyRows <= 0) {
howManyRows = Integer.MAX_VALUE;
}

int processedRows = 0;

while ((resultSet.next()) && (processedRows++ < howManyRows)) {

Object[] values = new Object[numberOfColumns];

// Read values for current row and save
// them in the values array
for (int i = 0; i < numberOfColumns; i++) {

Object columnValue = this.getColumnValue(resultSet, i);
values[i] = columnValue;

}

// Add the array of values to the linked list
dataRows.add(values);
}

} // end of row set constructor

// sets the result set to start at the given row number
private void setStartPosition(int startAtRow, ResultSet resultSet)
throws SQLException {

if (startAtRow > 0) {

if (resultSet.getType() !=ResultSet.TYPE_FORWARD_ONLY) {

// Move the cursor using JDBC 2.0 API
if (!resultSet.absolute(startAtRow)) {
resultSet.last();
}

} else {

// If the result set does not support JDBC 2.0
// skip the first beginAtRow rows
for (int i = 0; i < startAtRow; i++) {
if (!resultSet.next()) {
resultSet.last();
break;
}

}

}

}

}

// Reads a column value for the current row and
// create an appropriate java object to hold it.
// Return null if error reading value or for SQL null.
private Object getColumnValue( ResultSet resultSet, int columnIndex) {
return null;
}

// implement the RowSet and ResultInterface methods
public void addRowSetListener(RowSetListener listener) {
// TODO Auto-generated method stub

}

public void clearParameters() throws SQLException {
// TODO Auto-generated method stub

}

public void execute() throws SQLException {
// TODO Auto-generated method stub

}

public String getCommand() {
// TODO Auto-generated method stub
return null;
}
。。。 。。。
}


个人感觉这种策略实现起来巨麻烦(你可以自己数一数RowSet有多少个方法需要实现 dayan1 ),而做这么多仅仅是为了对业务层实现只读功能,性价比很低,有点儿得不偿失。另外,持久层的对象和异常仍然会暴露给业务层。

理论上来说,良好的封装应该避免把持久层的对象和异常暴露给业务层,通过实现RowSet Wrapper List Strategy就可以做到这一点。所以,我觉得要么就赋给业务层对RowSet对象全部的控制权,要么就完全隐藏RowSet,而这个只读策略实际用处不大。
dazui1 dazui1 dazui1


P.S.
粗略估计,实现RowSet接口需要重写197个方法 jingya1 dayan1 shui1 touxiang 。就算必须要使用这种策略的话,那么继承一个已有的实现,比如说Sun提供的CachedRowSetImpl,然后覆盖掉isReadOnly() 和setReadOnly(boolean flag)方法,比较而言似乎是一个更好的选择。
duyan duyan duyan