开发一个网上商店系统
作者:板桥banq
上页
8.5.1 DAO模式
首先从EJB层实现开始讨论批量查询的实现。由于是批量快速读取数据库,在这种情况下,不推荐使用CMP或其他实体Bean技术来实现。因为实体Bean的事务等机制成为多余,变成了性能累赘,直接使用JDBC访问数据库将能获得最好的性能。
但是在一般情况,不推荐在EJB的Session Bean中直接使用JDBC调用语句,而是通过DAO模式来间接地访问数据源。
DAO模式是J2EE核心模式中一种,DAO模式主要是在业务核心和具体数据源之间增加了一层,这样实现了两者的解耦。因为具体持久层数据源是可能多样化的,可能是XML或者可能是关系数据库,在具体关系数据库中,也可能有不同的产品如Oracle或MySQL,通过使用DAO模式,业务核心部分就无需涉及具体数据库是如何实现数据库操作的,如图8-10所示。
以本项目为例,需要将有关“读”操作都使用JDBC直接调用,封装这些“读”操作在一个统一类CatalogDAO中,这个DAO模式由下列几个类组成:
CatalogDAO:这是一个接口,包含了业务核心需要实现的一些方法。
CatalogDAOJDBC:CatalogDAO的子类实现,使用JDBC和SQL语句实现接口的方法。
CatalogDAOFactory:通过配置文件指定CatalogDAOJDBC为CatalogDAO的具体实现。
CatalogManagerBean:EJB的Session Bean,是DAO模式的使用者。

图8-10 DAO模式
建立CatalogDAO接口如下:
public interface CatalogDAO {
public Category getCategory(String catId) throws Exception;
public int getCategoryAllCount() throws Exception;
public PageIterator getCategories(int start, int count) throws Exception;
//获得商品相关数据
public Product getProduct(String productId) throws Exception;
public int getProductAllCount(String catId) throws Exception;
public PageIterator getProducts(String catId, int start, int count) throws
Exception;
//搜索数据库
public PageIterator searchItems(String query, int start, int size) throws
Exception;
//获取数据库图片数据
public byte[] getImage(String Id) throws Exception;
}
通过创建一个DAOFactory,来灵活指定CatalogDAO的具体实现。DAOFactory代码如下:
public Object getDAO(String jndiDAOName) throws ServiceLocatorException {
Object object = null;
try {
String className = (String) ic.lookup(jndiDAOName);
object = Class.forName(className).newInstance();
} catch (NamingException ne) {
throw new ServiceLocatorException(ne);
} catch (Exception se) {
throw new ServiceLocatorException(se);
}
return object;
}
这是通过JNDI查询后,装载一个CatalogDAO的具体实现子类,编制一个CatalogDAO的子类CatalogDAOJDBC代码如下:
public class CatalogDAOJDBC implements CatalogDAO {
private final static Logger logger = Logger.getLogger(CatalogDAOJDBC.class);
private DataSource dataSource = null;
//查询一个商品完整数据
private final static String GET_PRODUCT =
"select a.name, a.description from product a where productId = ?";
//查询一个类别所有商品的数据集合
private final static String GET_PRODUCTS =
"select a.productId from product a join category_product b " +
"on a.productId = b.productId where b.catId = ?";
//获得一个商品的所有具体品种
private final static String GET_ITEMS =
"select a.name, a.unitcost, a.listprice from "+
"item a join product_item b on a.itemId = b.itemId " +
"where b.productId = ? ";
//获得一个类别下所有商品记录总数
private final static String GET_PRODUCTS_ALLCOUNT =
"select count(1) from product a join category_product b " +
"on a.productId = b.productId where b.catId = ?";
//获得DataSource
protected DataSource getDataSource() throws Exception {
try {
if (dataSource == null) {
ServiceLocator sl = new ServiceLocator();
dataSource = (DataSource)
sl.getDataSource(JNDINames.CATALOG_DATASOURCE);
}
return dataSource;
} catch (ServiceLocatorException slx) {
logger.error(slx);
throw new Exception(slx);
}
}
//查询获得一个商品Product完整数据对象
public Product getProduct(String productId) throws Exception {
logger.debug(" by JDBC get product id=" + productId);
Connection c = null;
PreparedStatement ps = null;
ResultSet rs = null;
Product ret = null;
try {
c = getDataSource().getConnection();
ps = c.prepareStatement(GET_PRODUCT,
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY);
ps.setString(1, productId);
rs = ps.executeQuery();
if (rs.first())
ret = new Product(productId, rs.getString(1), rs.getString(2));
rs.close();
ps.close();
logger.debug(" get the items of product " );
ps = c.prepareStatement(GET_ITEMS,
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY);
ps.setString(1, productId);
rs = ps.executeQuery();
while(rs.next()){
Item item = new Item(rs.getString(1), rs.getFloat(2), rs.getFloat(3));
ret.getItems().add(item);
}
} catch (SQLException se) {
throw new Exception("SQLException: " + se.getMessage());
} finally {
if (rs != null) rs.close();
if (ps != null) ps.close();
if (c != null) c.close();
}
return ret;
}
//获得类别下所有商品数据表记录总数
public int getProductAllCount(String catId) throws Exception {
Connection c = null;
PreparedStatement ps = null;
ResultSet rs = null;
int ret = 0;
try {
c = getDataSource().getConnection();
ps = c.prepareStatement(GET_PRODUCTS_ALLCOUNT,
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY);
ps.setString(1, catId);
rs = ps.executeQuery();
if (rs.first())
ret = rs.getInt(1);
} catch (SQLException se) {
throw new Exception("SQLException: " + se.getMessage());
} finally {
if (rs != null) rs.close();
if (ps != null) ps.close();
if (c != null) c.close();
}
return ret;
}
//获得类别下所有商品ID集合
public PageIterator getProducts(String catId, int start, int count) throws
Exception {
logger.debug("--> getProducts from start=" + start + " size=" + count);
Connection c = null;
PreparedStatement ps = null;
ResultSet rs = null;
PageIterator pageIterator = null;
boolean hasNext = false;
try {
c = getDataSource().getConnection();
DbUtil.testConnection(c);
ps = c.prepareStatement(GET_PRODUCTS,
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY);
ps.setString(1, catId);
rs = ps.executeQuery();
if (DbUtil.supportsFetchSize)
rs.setFetchSize(count);
if (start >= 0 && rs.absolute(start + 1)) {
List items = new ArrayList(count);
do {
items.add(rs.getString(1));
}
while ( (hasNext = rs.next()) && (--count > 0));
//将items集合转换成数组
String[] itemsA = (String[]) items.toArray(new String[0]);
pageIterator = new PageIterator(itemsA, start, hasNext);
} else //生成空的ID集合
pageIterator = new PageIterator(PageIterator.EMPTY, start, hasNext);
logger.debug("--> getProducts succefully ..");
} catch (SQLException se) {
logger.error(se);
throw new Exception("SQLException: " + se.getMessage());
} finally {
if (rs != null) rs.close();
if (ps != null) ps.close();
if (c != null) c.close();
}
return pageIterator; //返回ID集合
}
}
在EJB Session Bean ProductManagerBean中调用上述DAO模式如下:
public class ProductManagerBean implements SessionBean {
…
private CatalogDAO catalogDAO;
//在ejnCreate中初始化DAO
public void ejbCreate() throws CreateException {
try {
ServiceLocator serviceLocator = new ServiceLocator();
catalogDAO = (CatalogDAO)
serviceLocator.getDAO(JNDINames.CATALOG_DAO);
} catch (Exception ex) {
logger.error("create error:" + ex);
throw new CreateException();
}
}
…
public PageIterator getProducts(String catId, int start, int count) {
PageIterator pageIterator = null;
try {
String pIKey = getPIKey(catId, start);//获得key
//首先从缓冲中获取
pageIterator = (PageIterator)queryCache.get(pIKey);
if (pageIterator == null){
//从数据库读取
pageIterator = catalogDAO.getProducts(catId, start, count);
int allCount = catalogDAO.getProductAllCount(catId);
pageIterator.setAllCount(allCount);
queryCache.put(pIKey, pageIterator);
}else{
logger.debug("read pageIterator from cache ..........");
}
} catch (Exception ex) {
logger.warn("getCategories error: " + ex);
}
return pageIterator;
}
…
}
上述代码中getProducts方法是获得批量查询中某个页面的数据对象ID集合,ID集合是封装在PageIterator这个对象中。
PageIterator是一个很重要的类,和Model一样,是在Web和EJB之间传送数据,不过PageIterator是传送的ID集合和相关遍历操作。
PageIterator是一个迭代器,是迭代模式的实现。它的原理非常类似Jive中的ForumThreadBlockIteraor以及PetStore中的Page,准确地说是两者的结合,代码如下:
public class PageIterator implements Iterator, Serializable{
public final static Object[] EMPTY = new Object[0];
//总数
private int allCount = 0;
//一个页面中的所有元素的ID
private Object[] keys;
private int currentIndex = -1;
private Object nextElement = null;
//以上参数是对keys内所有元素实现遍历
int start;
boolean hasNext;
//以上两个参数是有关页为单位的信息
public PageIterator(Object[] keys, int start, boolean hasNext) {
this.keys = keys;
this.start = start;
this.hasNext = hasNext;
}
public int getAllCount(){
return allCount;
}
public void setAllCount(int allCount){
this.allCount = allCount;
}
//复位,以便再次使用
public void reset(){
currentIndex = -1;
nextElement = null;
}
//以下是Iterator具体方法实现…
//是否有下一个
public boolean hasNext() {
if (currentIndex + 1 >= keys.length && nextElement == null) {
return false;
}
if (nextElement == null) {
nextElement = getNextElement();
if (nextElement == null) {
return false;
}
}
return true;
}
//获取下一个
public Object next() throws java.util.NoSuchElementException {
Object element = null;
if (nextElement != null) {
element = nextElement;
nextElement = null;
} else {
element = getNextElement();
if (element == null) {
throw new java.util.NoSuchElementException();
}
}
return element;
}
//获得ID
public Object getNextElement() {
while (currentIndex + 1 < keys.length) {
currentIndex++;
Object element = keys[currentIndex];
if (element != null) {
return element;
}
}
return null;
}
//是否有下一个页面
public boolean isNextPageAvailable() {
return hasNext;
}
…
}
以上是EJB的实现,EJB主要提高下列两个功能:
提供查询结果的总数。
提供每个页面的数据对象ID集合。
8.5.2 Strut框架下设计和实现
设计目标的两个功能已经在EJB中实现,剩余的3个功能将需要在Web中实现。在Strut框架下,这3个功能将分别在Action、ActionForm和taglib 3个组件技术中实现。
创建Actionde子类ModelListAction,主要功能是从EJB Service获得PageIterator实例,然后将PageIterator数据转换打包进入ActionForm子类ModelListForm中。这样,在ModelListForm中包含的是当前页面的数据记录集合,图8-11表示了这样的实现原理。

图8-11 批量查询Web实现顺序图
ModelListAction的代码如下:
public abstract class ModelListAction extends Action {
private final static String module = ModelListAction.class.getName();
protected final static CacheFactory cacheFactory = CacheFactory.getInstance();
//Action方法
public ActionForward execute(ActionMapping actionMapping,
ActionForm actionForm,
HttpServletRequest request,
HttpServletResponse response) throws
Exception {
int start = 0;
int count = 20;
ModelListForm listForm = new ModelListForm();
String startStr = request.getParameter("start");
if ( (startStr != null) && (startStr.length() != 0)) {
start = Integer.parseInt(startStr);
listForm.setStart(start);
}
String countStr = request.getParameter("count");
if ( (countStr != null) && (countStr.length() != 0)) {
count = Integer.parseInt(countStr);
listForm.setCount(count);
}
//获得PageIterator
PageIterator pageIterator = getPageIterator(request, start, count);
listForm.setAllCount(pageIterator.getAllCount());
listForm.setHasNextPage(pageIterator.isNextPageAvailable());
while (pageIterator.hasNext()) {
Object key = pageIterator.next();
//首先从缓存中获取
Model model = (Model) cacheFactory.getObect(key);
if (model == null) {
model = findModelByKey(request, key);
if (model.CACHEABLE) {
cacheFactory.putObect(key, model);
}
}
listForm.getList().add(model); //将完整的数据Model包装进入listForm
}
pageIterator.reset(); //复位,备下次再次使用
//保存到request的attribute中
FormBeanUtil.save(listForm, actionMapping, request);
//返回查询结果页面
return actionMapping.findForward("success");
}
//需要具体实现的抽象方法
public abstract PageIterator getPageIterator(HttpServletRequest request, int start,
int count);
//需要具体实现的抽象方法
public abstract Model findModelByKey(HttpServletRequest request, Object key);
}
ModelListForm是用于JSP显示的数据对象,代码如下:
public class ModelListForm extends ActionForm {
private int allCount = 0; //查询结果总数
private int start = 0; //本是从总数的第几条记录开始
private int count = 20; //每页显示的记录总数
private boolean hasNextPage = false; //是否有下页
private Collection list = new ArrayList(); //数据Model的集合
public Collection getList() { return list; }
public void setList(Collection list) { this.list = list; }
public int getCount() { return (this.count); }
public void setCount(int count) { this.count = count; }
public int getAllCount() { return (this.allCount); }
public void setAllCount(int allCount) { this.allCount = allCount; }
public int getStart() { return (this.start); }
public void setStart(int start) { this.start = start; }
public boolean getHasNextPage() { return hasNextPage; }
public void setHasNextPage(boolean hasNextPage) {
this.hasNextPage = hasNextPage;
}
}
ModelListAction中的一些方法需要具体应用实现。
以商品数据Product为例,创建ProductListAction继承ModeListAction,代码如下:
public class ProductListAction extends ModelListAction {
private final static ServiceServerFactory sf = ServiceServerFactory.
getInstance();
//调用EJB 获得一个类别下的所有商品ID集合
public PageIterator getPageIterator(HttpServletRequest request, int start,
int count) {
ProductManagerLocal productManager = (ProductManagerLocal) sf.getService(
FrameworkServices.ProductEJB, request);
String catId = request.getParameter("catId");
return productManager.getProducts(catId, start, count);
}
//调用EJB 获得一个商品对象的完整数据
public Model findModelByKey(HttpServletRequest request, Object key) {
ProductManagerLocal productManager = (ProductManagerLocal) sf.getService(
FrameworkServices.ProductEJB, request);
return productManager.getProductByIdJDBC((String)key);
}
}
配置struts-config.xml如下:
<action attribute="listForm"
type="com.jdon.estore.web.catalog.ProductListAction"
validate="false" scope="request"
path="/productListAction">
<forward name="success" path="/productList.jsp" />
</action>
<action attribute="listForm"
type="com.jdon.estore.web.catalog.ProductListAction"
validate="false" scope="request"
path="/admin/productListAction">
<forward name="success" path="/admin/productList.jsp" />
</action>
配置中有两个Action:/productListAction和/admin/productListAction。前者适用于商店客户浏览商品;后者适用于商店管理者查询商品,进行商品管理,它们都重用了ProductListAction类。下面将是JSP页面的实现。
下页