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.
Structure
The diagram in Figure 8.1 shows the structure of the Data Access Object pattern.
Figure 8.1 Data Access Object Class Diagram
Participants and Responsibilities
Figure 8.2 Data Access Object Sequence Diagram (part 1)
The diagram in Figure 8.2 shows the interaction of the various participants of the Data Access Object pattern for a basic operation to get data from a data source. Figure 8.3 shows part two of the diagram.
Figure 8.3 Data Access Object Sequence Diagram (part 2)
Client
The Client is an object that requires access to the data source to obtain and store data. The Client can be a Business Object, a Session Façde, an Application Services (357), a Value List Handler (444), a Transfer Object Assembler (433), or any other helper object that needs access to persistent data.
DataAccessObject
The DataAccessObject is the primary role object of this pattern. The DataAccessObject abstracts the underlying data access implementation for the Client to enable transparent access to the data source. The DataAccessObject implements create (insert), find (load), update (store), and delete operations.
DataSource
The DataSource represents a data source implementation. A DataSource could be a database, such as an RDBMS, OODBMS, XML repository, flat file system, and so on. A DataSource can also be another system (legacy/mainframe), service (B2B service or credit card bureau), or some kind of repository (LDAP).
ResultSet
The ResultSet represents the results of a query execution. For an RDMBS DataSource, when an application is using JDBC API, this role is fulfilled by an instance of the java.sql.ResultSet.
Data
The Data represents a transfer object used as a data carrier. The DataAccessObject can use a Transfer Object (415) to return data to the client. The DataAccessObject could also receive the data from the client as a Transfer Object (415) to update the data in the data source.
Strategies
Custom Data Access Object Strategy
When you decide to write your own data access layer, you can use the Data Access Object and the various strategies described in this section. Note that most of these strategies are complementary, and can be combined to make your Data Access Objects (462) more powerful and flexible. However, some of these strategies might add too much complexity to your implementation, so use them judiciously.
In its most basic form, a Data Access Object provides the operations to create, delete, update, and find data in a database. A sample implementation for this is shown in Example 8.2. The Data Access Object typically exchanges data with its clients using Transfer Objects (415), as shown in Example 8.1. Furthermore, a Data Access Object also provides other methods to find multiple results (finder methods) to return a List or other Collection objects.
The sample code in Example 8.1 shows a Transfer Object (415) for customer data used by the CustomerDAO. The CustomerDAO sample implementation is shown in Example 8.2 with the methods to create, update, find, and delete a customer record from the database.
Example 8.1 CustomerTO.java: Transfer Object Used by CustomerDAO and Its Clients
1 package com.corej2eepatterns.to; 2 3 public class CustomerTO implements java.io.Serializable { 4 private String id; 5 private String name; 6 private String address; 7 . . . 8 9 public String getId(){ return id; } 10 public void setId(String id){ this.id = id; } 11 public String getName(){ return name; } 12 public void setName(String name){ this.name = name; } 13 public String getAddress(){ return address; } 14 public void setAddress(String address){ 15 this.address = address; 16 } 17 // other getters and setters 18 . . . 19 }
Example 8.2 CustomerDAO.java: Data Access Object
1 package com.corej2eepatterns.dao; 2 3 // imports 4 5 public class CustomerDAO { 6 protected static final String FIELDS_INSERT = 7 "customer_name, customer_address, " + 8 "customer_contact, customer_phone, customer_email"; 9 10 protected static final String FIELDS_RETURN = 11 "customer_id, " + FIELDS_INSERT; 12 13 protected static String INSERT_SQL = 14 "insert into customer ( " + FIELDS_INSERT + 15 " ) " + "values ( ?, ?, ?, ?, ?)"; 16 17 protected static String SELECT_SQL = "select " + 18 FIELDS_RETURN + 19 " from customer where customer_id = ? "; 20 21 protected static String UPDATE_SQL = 22 "update customer set customer_name = ?, " + 23 "customer_address = ?, customer_contact = ?, " + 24 "customer_phone = ?, customer_email = ? " + 25 "where customer_id = ? "; 26 27 protected static String DELETE_SQL = 28 "delete from Customer where customer_id = ? "; 29 30 // the data source used to connect to the back-end database 31 private DataSource datasource; 32 33 public CustomerDAO() throws DAOException { 34 try { 35 // Shown here for clarity only. Typically, looking 36 // up a data source is done by a Service Locator 37 // and the DAO just uses the Service Locator to 38 // obtain a data source. 39 InitialContext initialContext = 40 new InitialContext(); 41 datasource = (DataSource) initialContext.lookup( 42 OracleDAOFactory.DATASOURCE_DB_NAME); 43 } catch (NamingException e) { 44 throw new DAOException ( 45 "Cannot locate data source at " + 46 DAOFactory.DATASOURCE_DB_NAME, e); 47 } 48 } 49 50 public String create(CustomerTO cust) throws DAOException { 51 // initialize variables 52 Connection con = getConnection(); 53 String customerId = null; 54 55 PreparedStatement prepStmt = null; 56 try { 57 // create and setup statement 58 prepStmt = con.prepareStatement(INSERT_SQL); 59 int i = 1; 60 prepStmt.setString(i++, cust.getName()); 61 prepStmt.setString(i++, cust.getAddress()); 62 . . . 63 64 // execute the statement 65 prepStmt.executeUpdate(); 66 67 // obtain the newly created customer id value 68 . . . 69 70 } catch (Exception e) { 71 // handle exception 72 } finally { 73 // close connections 74 } 75 76 // return the newly created customer id value 77 return customerId; 78 } 79 80 public CustomerTO find(String customerId) 81 throws DAOException { 82 // initialize variables 83 CustomerTO cust = null; 84 Connection con = getConnection(); 85 PreparedStatement prepStmt = null; 86 ResultSet rs = null; 87 88 try { 89 // setup statement and retrieve results 90 prepStmt = con.prepareStatement(SELECT_SQL); 91 prepStmt.setString(1, customerId); 92 rs = prepStmt.executeQuery(); 93 if (rs.next()) { 94 //create the transfer object using data from rs 95 cust = new CustomerTO(); 96 cust.setId(rs.getString(1)); 97 cust.setName(rs.getString(2)); 98 . . . 99 } 100 } catch (Exception e) { 101 // handle exception 102 } finally { 103 // close connections 104 } 105 return cust; 106 } 107 108 public void update(CustomerTO cust) throws DAOException { 109 Connection con = null; 110 PreparedStatement prepStmt = null; 111 try { 112 // prepare statement 113 con = getConnection(); 114 115 prepStmt = con.prepareStatement(UPDATE_SQL); 116 int i = 1; 117 118 // add fields first 119 prepStmt.setString(i++, cust.getName()); 120 prepStmt.setString(i++, cust.getAddress()); 121 . . . 122 123 // now add where parameters 124 prepStmt.setString(i++, cust.getId()); 125 int rowCount = prepStmt.executeUpdate(); 126 prepStmt.close(); 127 if (rowCount == 0) { 128 throw new DAOException( 129 "Update Error:Customer Id:" + cust.getId()); 130 } 131 } catch (Exception e) { 132 // handle exception 133 } finally { 134 // close connections 135 } 136 } 137 138 public void delete(String customerId) throws Exception { 139 // setup variables 140 Connection con = getConnection(); 141 PreparedStatement prepStmt = null; 142 143 try { 144 // execute database update 145 prepStmt = con.prepareStatement(DELETE_SQL); 146 prepStmt.setString(1, customerId); 147 prepStmt.executeUpdate(); 148 } catch (Exception e) { 149 // handle exception 150 } finally { 151 // close connections 152 } 153 } 154 155 // other methods for finders, etc. 156 . . . 157 }
Data Access Object Factory Strategies
You can make Data Access Object creation highly flexible by adopting the Abstract Factory [GoF] and the Factory Method [GoF] patterns.
When applications use a single type of persistent store (such as Oracle RDBMS), and there is no need to change the underlying storage from one implementation to another, implement the DAO Factory Method strategy to produce a variety of DAOs needed by the application.
Example 8.3 OracleDAOFactory.java: Data Access Object Strategy Using Factory Method [GoF]
1 package com.corej2eepatterns.dao; 2 3 // imports 4 public class OracleDAOFactory extends DAOFactory { 5 6 // package level constant used look up the 7 // DataSource name using JNDI 8 static String DATASOURCE_DB_NAME = 9 "java:comp/env/jdbc/CJPOraDB"; 10 11 public static CustomerDAO getCustomerDAO() 12 throws DAOException { 13 return (CustomerDAO) createDAO(CustomerDAO.class); 14 } 15 16 public static EmployeeDAO getEmployeeDAO() 17 throws DAOException { 18 return (EmployeeDAO) createDAO(EmployeeDAO.class); 19 } 20 21 // create other DAO instances 22 . . . 23 24 // method to create a DAO instance. Can be optimized to 25 // cache the DAO Class instead of creating it everytime. 26 private Object createDAO(Class classObj) 27 throws DAOException { 28 // create a new DAO using classObj.newInstance() or 29 // obtain it from a cache and return the DAO instance 30 } 31 }
While the DAO Factory Method strategy is most commonly used, you can further extend the flexibility of the factory implementation by adopting the Abstract Factory pattern. This need for further flexibility arises if you’re likely to change persistent stores frequently or if you use more than one. To accomplish this, you must encapsulate multiple types of data sources, such that each Data Access Object factory provides the DAO pattern implementation for one type of persistent store. While most applications typically use a single type of data source (such as Oracle RDBMS), and therefore the DAO Factory Method strategy might be adequate, more sophisticated and commercial frameworks can benefit from the added flexibility of the DAO Abstract Factory strategy.
The sample code for a DAO Abstract Factory strategy is shown in Example 8.4.
Example 8.4 DAOFactory.java: Data Access Object Factory Strategy Using Abstract Factory [GoF]
1 package com.corej2eepatterns.dao; 2 3 // imports 4 5 // Abstract class DAO Factory 6 public abstract class DAOFactory { 7 8 // List of DAO types supported by the factory 9 public static final int CLOUDSCAPE = 1; 10 public static final int ORACLE = 2; 11 public static final int SYBASE = 3; 12 . . . 13 14 // There will be a method for each DAO that can be 15 // created. The concrete factories will have to 16 // implement these methods. 17 public abstract CustomerDAO getCustomerDAO() 18 throws DAOException; 19 public abstract EmployeeDAO getEmployeeDAO() 20 throws DAOException; 21 . . . 22 23 public static DAOFactory getDAOFactory(int whichFactory) { 24 switch (whichFactory) { 25 case CLOUDSCAPE: 26 return new CloudscapeDAOFactory(); 27 case ORACLE: 28 return new OracleDAOFactory(); 29 case SYBASE: 30 return new SybaseDAOFactory(); 31 . . . 32 default: 33 return null; 34 } 35 } 36 }
Transfer Object Collection Strategy
In addition to the basic functions of create, find, update, and delete, most application clients also need a number of methods that can find multiple results based on a search criteria. The Data Access Object can implement a number of finder methods that accept a search criteria and return a specified number of rows with the results. The Data Access Object creates an SQL statement using the transfer object criteria, and executes a query against the data source to obtain a ResultSet. The Data Access Object then processes the ResultSet to retrieve as many matching result rows as requested by the calling client. For each row, the Data Access Object creates a Transfer Object (415) and adds it to a collection that is returned to the client.
This strategy is suitable when the client needs to obtain a small set of results as transfer objects. If the result set obtained from the query execution is large, then this strategy can be greatly resource intensive because it must create a transfer object instance for data in each row. If you need larger results sets, consider using strategies such as CachedRowSet strategy, Read Only RowSet strategy, or RowSet Wrapper List strategy.
A sample implementation of the Transfer Object Collection strategy is shown in Figure 8.5.
Example 8.5 CustomerDAO.java: Transfer Object Collection Strategy
1 package com.corej2eepatterns.dao; 2 3 // imports 4 5 public class CustomerDAO { 6 . . . 7 8 // Create a list of Transfer Objects and return it 9 public List findCustomers(CustomerTO criteria) 10 throws DAOException { 11 12 Connection con = getConnection(); 13 ResultSet rs = null; 14 ArrayList custList = new ArrayList(); 15 String searchSQLString = getSearchSQLString(criteria); 16 17 try { 18 con = getConnection(); 19 java.sql.Statement stmt = 20 con.createStatement(. . . );. 21 rs = stmt.executeQuery(searchSQLString); 22 while(rs.next()) { 23 //create the transfer object using data from rs 24 cust = new CustomerTO(); 25 cust.setId(rs.getString(1)); 26 cust.setName(rs.getString(2)); 27 . . . 28 29 // add the TO to the list 30 custList.add(cust); 31 } 32 } catch (Exception e) { 33 // handle exception 34 } finally { 35 // close connections 36 } 37 return custList; 38 } 39 40 . . . 41 42 }
Cached RowSet Strategy
When performing an operation, such as a search, that returns multiple elements, the Data Access Object certainly has the option of creating a collection of transfer objects and returning it to the client. If the search operation returns a large list, this strategy can become quite expensive with all those transfer object creations. To avoid that performance overhead, you can leverage the JDBC RowSet interface by using a disconnected, cached row set implementation. Row sets can be used for read and updates, and you can choose from a variety of publicly available implementations. Sun’s implementation is CachedRowSet, JdbcRowSet and WebRowSet [SUNRS]; other vendors such as Oracle provide their own RowSet implementations [ORARS]. You can use one of these public implementations if it meets your needs.
Exercise caution when using RowSets, connected or disconnected, with implementations that support the update functionality. If all you need is a results list that you use for read-only purposes (for instance, to display the results in a web page), a better alternative is to use the Read Only RowSet strategy or RowSet Wrapper List strategy.
- 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.
A sample DAO that uses a Cached RowSet implementation (sun.jdbc.rowset.CachedRowSet) is shown in Example 8.6.
Example 8.6 CustomerDAO.java: Cached RowSet Strategy
1 package com.corej2eepatterns.dao; 2 3 // imports 4 5 public class CustomerDAO { 6 . . . 7 8 // Create the CachedRowSet using the 9 // ResultSet from query execution 10 public RowSet findCustomersRS(CustomerTO criteria) 11 throws DAOException { 12 13 Connection con = getConnection(); 14 javax.sql.RowSet rowSet = null; 15 String searchSQLString = getSearchSQLString(criteria); 16 try { 17 con = getConnection(); 18 java.sql.Statement stmt = 19 con.createStatement(. . . );. 20 java.sql.ResultSet rs = 21 stmt.executeQuery(searchSQLString); 22 rowSet = new CachedRowSet(); 23 rowSet.populate(rs); 24 } catch (SQLException anException) { 25 // handle exceptionノ 26 } finally { 27 con.close(); 28 } 29 return rowSet; 30 } 31 32 . . . 33 34 }
Read Only RowSet Strategy
While the Cached RowSet strategy is useful for creating any kind of RowSet, most J2EE applications need only the read-only functionality of a RowSet. Instead of using an updateable RowSet implementation, you can implement your own RowSet that caters only to read-only functionality. This strategy provides an example of such a RowSet implementation called Read Only RowSet. The class diagram in Figure 8.4 shows the structure of the Read Only RowSet strategy.
The Client asks the DataAccessObject to perform a search. The DataAccessObject typically fulfills the role of the Client itself, as explained in the Rowset Wrapper List strategy.
Figure 8.4 Read Only Rowset Strategy Class Diagram
The sequence diagram in Figure 8.5 shows how the Read Only RowSet strategy works.
Figure 8.5 Read Only RowSet Strategy Sequence Diagram
The Client uses a DataAccessObject to execute a search query. The DataAccessObject executes the query and obtains a ResultSet, which it uses to create an instance of ReadOnlyRowSet. The ReadOnlyRowSet extracts values from the given ResultSet and stores them in its own array shown as DataRows.
As the DataRows list is being populated, the ReadOnlyRowSet extracts only as many rows from the ResultSet as the Client requested. Once the rows are extracted and the DataRows list is populated with the requested number of rows, the connection is closed and the ResultSet is no longer needed. Hence, the ReadOnlyRowSet described in this strategy is a disconnected cached RowSet implementation.
The following code example shows the implementation of the Read Only RowSet strategy. The sample code in Example 8.7, a Data Access Object, creates a ReadOnlyRowSet instance obtaining a ResultSet instance after a query execution. The client of the CustomerDAO Data Access Object invokes the findCustomersRORS() method and passes a CustomerTO transfer object as the search criteria, indicating the maximum number of rows to return using the howManyRows argument.
Example 8.7 CustomerDAO.java: Creating the ReadOnlyRowSet
1 package com.corej2eepatterns.dao; 2 3 // imports 4 5 public class CustomerDAO { 6 . . . 7 8 // Create the ReadOnlyRowSet using the 9 // ResultSet from query execution 10 public RowSet findCustomersRORS(CustomerTO criteria, 11 int startAtRow, int howManyRows) 12 throws DAOException { 13 14 Connection con = getConnection(); 15 javax.sql.RowSet rowSet = null; 16 String searchSQLString = getSearchSQLString(criteria); 17 18 try { 19 con = getConnection(); 20 java.sql.Statement stmt = con.createStatement(. . .); 21 java.sql.ResultSet rs = 22 stmt.executeQuery(searchSQLString); 23 rowSet = new ReadOnlyRowSet(); 24 rowSet.populate(rs, startAtRow, howManyRows); 25 } catch (SQLException anException) { 26 // handle exceptionノ 27 } finally { 28 con.close(); 29 } 30 return rowSet; 31 } 32 33 . . . 34 }
Example 8.8 ReadOnlyRowSet.java: Populating the RowSet
1 package com.corej2eepatterns.dao.rowset; 2 3 // imports 4 5 public class ReadOnlyRowSet implements RowSet, Serializable { 6 7 . . . 8 9 private Object[] dataRows; 10 11 . . . 12 13 /** this is a read only row set */ 14 public boolean isReadOnly() { 15 return true; 16 } 17 18 public void setReadOnly(boolean flag) throws SQLException { 19 throw new SQLException( 20 “ReadOnlyRowSet: Method not supported”); 21 } 22 23 // Populates the rowset without the first startRow rows 24 // of the ResultSet and with a maximum number 25 // of rows specified by howManyRows 26 public void populate(ResultSet resultSet, 27 int startRow, int howManyRows) 28 throws SQLException { 29 30 // miscellaneous code not shown for brevityノ 31 32 // Create a list to hold the row values 33 List dataRows = . . . ; 34 35 // determine the number of columns from the mete data 36 int numberOfColumns = 37 resultSet.getMetaData().getColumnCount(); 38 39 // Discard initial rows if beginAtRow was specified 40 setStartPosition(startAtRow, resultSet); 41 42 // if number of rows is unspecified, 43 // get all rows from resultset 44 if (howManyRows <= 0) { 45 howManyRows = Integer.MAX_VALUE; 46 } 47 int processedRows = 0; 48 while ((resultSet.next()) && 49 (processedRows++ < howManyRows)) { 50 Object[] values = new Object[numberOfColumns]; 51 52 // Read values for current row and save 53 // them in the values array 54 for (int i=0; i<numberOfColumns; i++) { 55 Object columnValue = 56 this.getColumnValue(resultSet, i); 57 values[i] = columnValue; 58 } 59 60 // Add the array of values to the linked list 61 dataRows.add(values); 62 } 63 64 } // end of row set constructor 65 66 . . . 67 68 // sets the result set to start at the given row number 69 private void setStartPosition( 70 int startAtRow, ResultSet resultSet) 71 throws SQLException { 72 if (startAtRow > 0) { 73 if (resultSet.getType() != 74 ResultSet.TYPE_FORWARD_ONLY) { 75 // Move the cursor using JDBC 2.0 API 76 if (!resultSet.absolute(startAtRow)) { 77 resultSet.last(); 78 } 79 } else { 80 // If the result set does not support JDBC 2.0 81 // skip the first beginAtRow rows 82 for (int i=0; i< startAtRow; i++) { 83 if (!resultSet.next()) { 84 resultSet.last(); 85 break; 86 } 87 } 88 } 89 } 90 } 91 92 // Reads a column value for the current row and 93 // create an appropriate java object to hold it. 94 // Return null if error reading value or for SQL null. 95 private Object getColumnValue( 96 ResultSet resultSet, int columnIndex) { 97 98 . . . 99 100 } 101 102 // implement the RowSet and ResultInterface methods 103 . . . 104 105 }
RowSet Wrapper List Strategy
While the Transfer Object Collection strategy is one way to implement your finder methods, it might prove expensive if the query returns a large set of results and you end up creating a large collection of transfer objects, which are used sparingly by the client. Clients typically search and use the first few result items and discard the rest. When an application is executing a query that returns a large set of results, the RowSet Wrapper List strategy might be more efficient though it requires additional work and adds more complexity.
You do not want to expose the RowSet strategies to external clients of the Data Access Object, regardless of what RowSet implementation you choose to use. It is better to encapsulate all usage of any objects that originate in the java.sql and javax.sql packages inside the Data Access Object. Such encapsulation ensures that the client is not dependent on low-level JDBC interfaces, such as ResultSet or RowSet interfaces. The RowSet Wrapper List strategy internally uses the Read Only RowSet strategy to obtain the query results and wraps the RowSet instance to expose the results in an object-oriented way. The wrapping is done by a custom List implementation that provides the traversal and caching functions to manipulate the RowSet in an object-oriented manner by exposing the data in the rowset as objects.
The advantage of the RowSet Wrapper List strategy is that you create and return Transfer Objects (415) only if necessary; that is, if and when the client needs it. On the other hand, for the Transfer Object Collection strategy, the Data Access Object must create all the Transfer Objects (415) before it returns a Collection to the client.
The class diagram in Figure 8.6 shows the interactions of the RowSet Wrapper List strategy.
Figure 8.6 Rowset Wrapper List Strategy Class Diagram
The sequence diagram in Figure 8.7 shows the interactions of the RowSet Wrapper List strategy.
The DataAccessObject, having obtained a ReadOnlyRowSet using the Read Only RowSet strategy, proceeds to create a RowSetWrapperList by passing the ReadOnlyRowSet instance. The DataAccessObject returns the RowSetWrapperList to the Client, which uses that list to traverse the query results.
The RowSetWrapperList implements the List interface to provide the traversal function for the data in the ReadOnlyRowSet. The RowSetWrapperList encapsulates all data access layer functionality away from the Client, which does not have to know anything about RowSet, ResultSet, or any other classes in the java.sql or javax.sql packages. The RowSetWrapperList returns a Transfer Object (415) when the client wants to access the previous or next row in the results.
Figure 8.7 RowSet Wrapper List Strategy Sequence Diagram
The following code examples show the implementation of the RowSet Wrapper List strategy. The sample code in Example 8.9 shows the CustomerDAO creating a RowSetWrapperList instance using the ReadOnlyRowSet obtained in the previous example.
A custom list iterator, DataRowListIterator, is implemented as an inner class of RowSetWrapperList, as shown in Example 8.11.
Finally, a simple DAO client CustomerDAOClient that uses CustomerDAO and the RowSetWrapperList is shown in Example 8.13.
Example 8.9 CustomerDAO.java: Creating the RowSetWrapperList
1 package com.corej2eepatterns.dao; 2 3 // imports 4 5 public class CustomerDAO { 6 . . . 7 8 public List findCustomersRL( 9 CustomerTO cust, int startAtRow, int howManyRows) 10 throws Exception{ 11 // create the search SQL string 12 String searchSQLString = getSearchSQLString(cust); 13 14 // execute the search 15 return executeSearch(searchSQLString, 16 StartAtRow, howManyRows); 17 } 18 19 private List executeSearch( 20 String searchSQLString, int startAtRow, 21 int howManyRows) 22 throws Exception { 23 24 RowSetWrapperList results = null; 25 try { 26 RowSet rowSet = getRORowSet(searchSQLString, 27 int startAtRow, 28 int howManyRows); 29 30 results = new RowSetWrapperList (rowSet); 31 } catch (Exception ex) { 32 throw new Exception(ex); 33 } 34 35 return results; 36 } 37 38 private String getSearchSQLString(CustomerTO cust) { 39 // convenience method to create and return 40 // a “SELECT” SQL statement as a String, incorporating 41 // non-null values in the supplied CustomerTO transfer 42 // object into the WHERE clause 43 } 44 45 . . . 46 47 }
Example 8.10 RowSetWrapperList.java
1 package com.corej2eepatterns.to.list; 2 3 // imports 4 5 // Read only implementation of the List interface. 6 // It supports iterators and absolute positioning. 7 8 public class RowSetWrapperList 9 implements List, Serializable { 10 11 // variable to hold a RowSet instance 12 private RowSet rowSet; 13 14 15 public RowSetWrapperList(RowSet rowSet) { 16 this.rowSet = rowSet; 17 . . . 18 } 19 20 // return the current row as a transfer object 21 public Object get(int index) { 22 try { 23 rowSet.absolute(index); 24 } catch (SQLException anException) { 25 // handle exception 26 } 27 // create a new transfer object and return 28 return 29 TORowMapper.createCustomerTO(this); 30 } 31 . . . 32 33 // Returns a Sub List of the current list. 34 public List subList(int fromIndex, int toIndex) { 35 // Create a new RowSet with the required rows 36 ReadOnlyRowSet roRowSet = new ReadOnlyRowSet(); 37 roRowSet.populate(this.rowSet, fromIndex, toIndex); 38 39 // Create a new RowSetWrapperList instance and 40 // return it 41 return 42 new RowSetWrapperList(roRowSet); 43 } 44 45 // Returns an iterator over the elements in this list in 46 // proper sequence. It is possible to define multiple 47 // independent iterators for the same RowSetWrapperList 48 // object. 49 50 public Iterator iterator() { 51 try { 52 rowSet.beforeFirst(); 53 } catch (SQLException anException) { 54 System.out.println( 55 "Error moving RowSet before first row." + 56 anException); 57 } 58 59 return this.listIterator(); 60 } 61 62 // Create a List Iterator that can iterate over the 63 // rowset 64 public ListIterator listIterator() { 65 // ListResultIterator is implemented as an inner class 66 return new DataRowListIterator(); 67 } 68 69 // implement the List interface methods 70 71 . . . 72 73 }
Example 8.11 RowSetWrapperList.java: Inner class DataRowListIterator
1 package com.corej2eepatterns.to.lists; 2 3 // imports 4 5 public class RowSetWrapperList 6 implements List, Serializable { 7 8 . . . 9 10 private class DataRowListIterator implements ListIterator { 11 int currentRow=0; 12 // sets the rowset cursor to next and returns 13 // the Transfer object 14 public Object next() { 15 // get transfer object for next row of RowSetWrapperList 16 currentRow++; 17 return this.get(currentRow); 18 } 19 20 public Object previous() { 21 // get transfer object for previous row of 22 // RowSetWrapperList 23 currentRow--; 24 return this.get(currentRow); 25 26 } 27 28 // Implement the List Iterator interface 29 public boolean hasNext() { 30 // Check the cursor position in the rowSet using 31 // isLast, isAfterLast, isEmpty methods and 32 // return true or false accordingly 33 } 34 35 public boolean hasPrevious() { 36 // Check the cursor position in the rowSet using 37 // isFirst, isBeforeFirst, isEmpty methods and 38 // return true or false accordingly 39 } 40 41 // implement other ListIterator methods 42 43 public int nextIndex() { 44 . . . 45 } 46 47 public int previousIndex() { 48 . . . 49 } 50 51 // optional methods not implemented throw 52 // UnsupportedException 53 public void set(Object o) { 54 throw new UnsupportedOperationException(); 55 } 56 57 public void add(Object o) { 58 throw new UnsupportedOperationException(); 59 } 60 . . . 61 }
Example 8.12 TORowMapper.java: A Sample Implementation
1 package com.corej2eepatterns.util; 2 3 // imports 4 public class TORowMapper { 5 6 // create Customer TO 7 public CustomerTO createCustomerTO(RowSet rowSet) { 8 CustomerTO to = new CustomerTO(); 9 to.setId(getString(rowSet), 0); 10 to.setName(getString(rowSet), 1); 11 . . . 12 } 13 14 // create other TOs 15 . . . 16 17 // implement primitive methods used by create methods 18 protected boolean wasNull(RowSet rowSet) { 19 try { 20 return rowSet.wasNull(); 21 } catch (SQLException e) { 22 throw new RuntimeException(e.getMessage()); 23 } 24 } 25 26 protected String getString(RowSet rowSet, int columnIndex) { 27 try { 28 return rowSet.getString(columnIndex); 29 } catch (SQLException e) { 30 throw new RuntimeException(e.getMessage()); 31 } 32 } 33 34 protected boolean getBoolean( 35 RowSet rowSet, int columnIndex) { 36 try { 37 return rowSet.getBoolean(columnIndex); 38 } catch (SQLException e) { 39 throw new RuntimeException(e.getMessage()); 40 } 41 } 42 43 protected java.util.Date getDate( 44 RowSet rowSet, int columnIndex) { 45 try { 46 return rowSet.getDate(columnIndex); 47 } catch (SQLException e) { 48 throw new RuntimeException(e.getMessage()); 49 } 50 } 51 52 // Other primitive getXXX methods for all required 53 // data types 54 . . . 55 56 }
Example 8.13 A Sample DAO Client: Using the RowSetWrapperList
1 package com.corej2eepatterns.rowset; 2 3 // imports 4 5 public class CustomerDAOClient { 6 7 . . . 8 9 public void search() { 10 try { 11 CustomerDAO dao = new CustomerDAO(); 12 CustomerTO criteria = new CustomerTO(); 13 14 criteria.setZip("94539"); 15 16 // search for all customers with Zip code 94539 17 // results to contain a maximum of 1000 matching rows 18 List searchResults = 19 dao.findCustomersRL(criteria, 0, 999); 20 21 int resultSize = searchResults.size(); 22 for (int rowNum=0; rowNum < resultSize; rowNum++) { 23 CustomerTO customerTO = 24 (CustomerTO)searchResults.get(rowNum); 25 System.out.println("Customer Row #" + 26 rowNum + " has "); 27 System.out.println("Customer Id = " + 28 customerTO.getId()); 29 System.out.println("Name = " + 30 customerTO.getName()); 31 . . . 32 } 33 34 . . . 35 36 // get a ListIterator 37 ListIterator iterator = searchResults.listIterator(); 38 39 // use the iterator to traverse to next or 40 // previous row 41 . . . 42 43 } catch (Exception e) { 44 // handle exception 45 } 46 } 47 . . . 48 }