开发一个网上商店系统

  作者:板桥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模式的使用者。
1
图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表示了这样的实现原理。
1
图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页面的实现。

 

 

下页