开发一个网上商店系统

  作者:板桥banq

上页

8.3  商品类别管理功能的实现

虽然增、删、改、查通用数据操作框架在设计上比较复杂,但是使用起来非常方便。下面以本系统为例,展示该框架结合EJB 和EJB Service调用框架的使用。

8.3.1  创建Session Bean

创建EJB Session Bean CategoryManager,该EJB方法内容如下:
public interface CatalogManagerLocal extends javax.ejb.EJBLocalObject {
  
   public void createCategory(EventModel em) throws Exception;
   public void updateCategory(EventModel em) throws Exception;
   public void deleteCategory(EventModel em) throws Exception;
   public Category getCategoryById(String id);
   public Category getCategoryByName(String name);

   public PageIterator getCategories(int start, int count);
   public int getCategoryAllCount();

}
该无状态Session Bean主要是实体Bean Category的Facade类,主要集中了有关数据表category的新增、修改、删除和查询等操作。
CategoryManager的Bean实现类主要是实现上述方法内容,其中部分代码如下:
public class CatalogManagerBean implements SessionBean {
  SessionContext sessionContext;
  private CategoryHome chome;
  private CategoryDetailsHome cdhome;
  private SequenceGeneratorLocalHome sequenceHome;
  private CatalogDAO catalogDAO;

  private int categoryAllCount = 0; //缓存
  private Map pageIteratorCache = new HashMap(); //缓存

  public void ejbCreate() throws CreateException {
    try {
      ServiceLocator serviceLocator = new ServiceLocator();
      chome = (CategoryHome) serviceLocator.getLocalHome(JNDINames.
          CATEGORY_HOME);
      cdhome = (CategoryDetailsHome) serviceLocator.getLocalHome(JNDINames.
          CATEGORY_DETAILS_HOME);
      sequenceHome = (SequenceGeneratorLocalHome) serviceLocator.getLocalHome(
          JNDINames.SEQUENCEGENERATOR_HOME);
      catalogDAO = (CatalogDAO)
           serviceLocator.getDAO(JNDINames.CATALOG_DAO);
    } catch (Exception ex) {
      logger.error("create error:" + ex);
      throw new CreateException();
    }
  }
  …
}
数据对象Category的创建方法如下,由于设计了两个数据表Category和Category_details,因此增加数据Category时,需要对这两个数据库同时操作。这两个数据库之间是1:1关系,使用CMP的CMR实现这种1:1的关系:
public void createCategory(EventModel em) throws Exception {
    Category category = (Category) em.getModel();
    try {
      //从Sequence组件获取自增ID
      String Id = Integer.toString(getNewId(JNDINames.SEQUENCE_NAME));
      //在数据表category中插入记录
      CategoryLocal categoryLocal = chome.create(Id);
      //在数据表category_details中插入记录
      CategoryDetails categoryDetails = cdhome.create(Id, category.getName(),
          category.getDescription());
      //设置Category与CategoryDetails的1:1关系,使用CMP的CMR
      categoryLocal.setCategoryDetails(categoryDetails);
      //因为新增了新的记录,数据库记录总数变化,复位为0
      categoryAllCount = 0;
      category.setCatId(Id); //Id是自动生产的,放入EvenModel中
    } catch (Exception ex) {
      logger.error(ex);
      em.setErrors("db.error");
    }
}
在上述创建方法中,Sequence是一个专门用来产生ID的EJB组件,Category的主键catId值是从这个EJB组件中获得的。
CategoryManager其他方法的具体实现这里省略。
创建CategoryManager后,完成了商品类别管理功能的第一步。因为CategoryManager实现了对其他EJB(如实体Bean)的调用,因此需要进行一定的配置。

8.3.2  EJB配置

设置EJB相关配置是第二步,在JNDINames常量类中设置CATEGORY_HOME等常量内容,如下:
public final static String CATEGORY_HOME = "java:comp/env/ejb/Catalog";
在ejb-jar.xml中加入如下配置:
<ejb-local-ref>
                <description />
                <ejb-ref-name>ejb/Catalog</ejb-ref-name>
                <ejb-ref-type>Entity</ejb-ref-type>
                <local-home>com.jdon.estore.catalog.CategoryHome</local-home>
                <local>com.jdon.estore.catalog.CategoryLocal</local>
                <ejb-link>Category</ejb-link>
  </ejb-local-ref>
 在jboss.xml中,加入如下配置:
 <session>
            <ejb-name>CatalogManager</ejb-name>
            <local-jndi-name>CatalogManagerLocal</local-jndi-name>
            <ejb-local-ref>
                <ejb-ref-name>ejb/Catalog</ejb-ref-name>
                <local-jndi-name>Category</local-jndi-name>
            </ejb-local-ref>
 </session>
再设置实体Bean的数据源以及其他配置。
设置实体Bean CMP的数据源,在jbosscmp-jdbc.xml加入如下配置:
<defaults>
        <datasource>java:/EstoreDS</datasource>
        <datasource-mapping>mySQL</datasource-mapping>
</defaults>
java:/EstoreDS是JBoss的数据源JNDI,配置JBoss的数据源,以JBoss 3.22为例,在其deploy目录下,创建或编辑mysql-ds.xml,加入如下配置:
<local-tx-datasource>
    <jndi-name>EstoreDS</jndi-name>
    <connection-url>jdbc:mysql://localhost:3306/estore?
          useUnicode=true&amp;characterEncoding=UTF-8
    </connection-url>
    <driver-class>com.mysql.jdbc.Driver</driver-class>
    <user-name>banq</user-name>
    <password>12345</password>
</local-tx-datasource> 
数据源配置EstoreDS指向数据库estore,并且指定UTF-8编码。
以上基本为EJB的开发步骤,下面将进行Web层的开发。

 


8.3.3  创建Category相关类实现

前两步是EJB层实现,第三步是Web层的开发实现。
商品类别Category作为一个具体数据对象,需要实现类别数据的新增、修改、删除和查询功能。因此使用前面介绍的Strut框架下的增、删、改、查框架,只要具体实现3个类:Model的具体实现Catrgory、ActionForm的具体实现CategoryForm和ModelHandler的子类CategoryHandler。
Category主要是字段的set或get方法,它是继承实现框架的接口Model,代码如下:
public class Category implements Model{
 private String catId;   //类别主键
 private String name;  //类别名称
 private String description; //类别藐视
 private Set  products = new HashSet();
 public Category(){
 }
 public Category(String catId, String name, String description){
   this.catId = catId;
   this.name = name;
   this.description = description;
 }
 public String getCatId(){    return catId; }
 public void setCatId(String catId){    this.catId = catId; }
 public String getName(){    return name; }
 public void setName(String name){    this.name = name; }
 public String getDescription(){    return description; }
 public void setDescription(String description){   this.description = description; }
 public Set getProducts(){    return products; }
 public void setProducts(Set products){   this.products = products; }
}
CategoryForm内容基本与Category类似,这里省略。
创建ModelHandler的子类CategoryHandler如下:
public class CategoryHandler implements ModelHandler {
   //使用方法调用EJB框架 该框架在前面章节已经介绍
  private final static ServiceServerFactory sf = ServiceServerFactory.
      getInstance();
  //初始化一个CategoryForm
  public ModelForm initForm(HttpServletRequest request) {
    return new CategoryForm();
  }
  //查询数据库
  public Model findModelByKey(String keyValue, HttpServletRequest request) {
    //调用EJB CatalogManagerLocal
    CatalogManagerLocal catalogManager = (CatalogManagerLocal) sf.getService(
        FrameworkServices.CatalogEJB, request);
    return catalogManager.getCategoryById(keyValue);
  }
  //保存结果
  public void serviceAction(EventModel em, HttpServletRequest request) throws
      java.lang.
      Exception {
    CatalogManagerLocal catalogManager = (CatalogManagerLocal) sf.getService(
        FrameworkServices.CatalogEJB, request);
    try {
      switch (em.getActionType()) {
        case Event.CREATE:  //如果是新增保存
          catalogManager.createCategory(em);
          break;
        case Event.EDIT:    //如果是修改保存
          catalogManager.updateCategory(em);
          break;           //如果是删除
        case Event.DELETE:
          catalogManager.deleteCategory(em);
          break;
      }
    } catch (Exception ex) {
      throw new Exception("System operation Error:" + ex);
    }
  }
}

8.3.4  Web配置

第五步是Web层配置,需要配置web.xml对EJB的引用,配置modelmapping.xml和struts_config.xml。
在web.xml增加EJB引用解释如下:
<ejb-local-ref>
    <ejb-ref-name>ejb/CatalogManager</ejb-ref-name>
    <ejb-ref-type>Session</ejb-ref-type>
    <local-home>com.jdon.estore.catalog.CatalogManagerLocalHome</local-home>
    <local>com.jdon.estore.catalog.CatalogManagerLocal</local>
    <ejb-link>CatalogManager</ejb-link>
</ejb-local-ref>
配置modelmapping.xml如下:
<modelmappings>
    <modelmapping formName = "categoryForm"
                   key="catId"
                   model="com.jdon.estore.model.Category"
                   handler = "com.jdon.estore.web.catalog.CategoryHandler" />
</modelmappings >
配置struts_config.xml,增加如下语句:
<form-beans>
  <form-bean name="categoryForm" type="com.jdon.estore.web.catalog.CategoryForm" />
</form-beans>
<action-mappings>
    <!--  以下是category的配置  -->
    <action attribute="categoryForm"
type="com.jdon.strutsutil.ModelViewAction"
validate="false" scope="request"
path="/admin/categoryAction">
      <forward name="create" path="/admin/category.jsp" />
      <forward name="edit" path="/admin/category.jsp" />
    </action>
    <action name="categoryForm"
type="com.jdon.strutsutil.ModelSaveAction"
input="/admin/category.jsp" scope="request"
path="/admin/saveCategoryAction">
      <forward name="success" path="/admin/categoryOk.jsp" />
      <forward name="failure" path="/admin/categoryOk.jsp" />
    </action>
      …
  </action-mappings>
  <message-resources null="false" parameter="com.jdon.estore.ApplicationResources" />
</struts-config>
在struts-config.xml中,关于一个数据对象总是有两个action配置。第一个action配置是ModelViewAction,用于控制界面输出;第二个是ModelSaveAction,用于保存结果。

8.3.5  创建Category.jsp

最后一步是编制JSP页面,创建category.jsp如下:
<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>
<%@ taglib uri="/WEB-INF/struts-template.tld" prefix="template" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ page contentType="text/html; charset=UTF-8" %>
<html:html>
<head><title>类别管理</title></head>
<body>
<html:errors/>
<p><html:form action="/admin/saveCategoryAction.do" method="POST">
<html:hidden property="action" /> <!—注意:使用本框架,一定要加本语句!!  -->
<html:hidden property="catId"/>
类别名称:<html:text property="name"/>
<br>
类别描述:<html:textarea rows="4" cols="32" property="description"/>
<br>
<logic:equal name="categoryForm" property="action" value="create">
  <html:submit property="submit" value="新增"/>
</logic:equal>
<logic:equal name="categoryForm" property="action" value="edit">
  <html:submit property="submit" value="修改"/>
</logic:equal>
<html:reset value ="复位"/>
<script>
function Delid(){
   if (confirm( '删除本类别 ! \n\n确定吗 ? '))
   {
      document.categoryForm.action.value ="delete";
      return true;
   }else{
      return false;
   }
}
</script>
<input type="submit" value="删除" onclick="return Delid()" >
</html:form>
</body>
</html:html>
该JSP是商品类别新增、修改、删除公用页面,通过表单字段action记录当前操作性质,因此action字段是使用增、删、改、查框架的对JSP页面要求的惟一必需设置。
新增商品类别数据时,调用/admin/categoryAction.do,通过Struts-config.xml配置可以知道,categoryAction推出的页面是category.jsp,这时表单字段action的值是create。
修改商品类别数据时,调用/admin/categoryAction.do?action=edit&catId=1551,该调用表示编辑catId为1551的商品类别数据。categoryAction根据action的值,确证是编辑修改调用,同时根据catId值,查询数据库,获得Category数据后,推出category.jsp,这时显示的是各个字段有数据的表单,表单字段action的值是edit。
自此,关于商品类别Category数据的增、删、改和查功能基本完成。在成熟框架下开发调试,将节省大量开发时间,稳定性高,在调试中出现的错误基本是由于粗心导致的书写错误,一次性成功率很高。

8.4  商品管理功能的实现

前面讨论了商品类别Category的实现,一个商品类别下有很多商品,因此,Category和Product之间是一对多的关系。本节讨论商品Product的数据操作功能实现,同样也是五大步骤,简洁而快速。

8.4.1  创建ProductManager

第一步是创建EJB Session Bean ProductManager,该EJB接口方法如下:
public interface ProductManagerLocal extends javax.ejb.EJBLocalObject {
  public void createProduct(EventModel em) throws Exception;
  public void updateProduct(EventModel em) throws Exception;
  public void deleteProduct(EventModel em) throws Exception;
  public Product getProductById(String Id);
  public byte[] getImage(String Id);

  public Item getItemById(String itemId);
  public void createItem(EventModel em) throws Exception;
  public void updateItem(EventModel em) throws Exception;
  public void deleteItem(EventModel em) throws Exception;
  public void createAttrs(EventModel em) throws Exception;
  public void updateAttrs(EventModel em) throws Exception;
  public void deleteAttrs(EventModel em) throws Exception;
  public Attribute getItemAttrsById(String itemAttrsId);
}
ProductManager主要是对数据表Product和item的操作,ProductManagerBean部分内容如下:
public void createProduct(EventModel em) throws Exception {
    Product product = (Product) em.getModel();
    try {
      //获取自增Id
      String Id = Integer.toString(getNewId(JNDINames.SEQUENCE_NAME));
      ProductLocal productLocal = productHome.create(Id); //创建product新记录
      Images images = imagesHome.create(Id); //创建图片表新记录
      productLocal.setImages(images);
      product.setProductId(Id);
      updateProduct(em);  //更新product表的其他字段

      //建立与Category表的N:1关系,插入相应的商品类别
      String catId = product.getCatId();
      CategoryLocal categoryLocal = chome.findByPrimaryKey(catId);
      categoryLocal.getProduct().add(productLocal); //CMR操作
    } catch (Exception ex) {
      logger.error(ex);
      em.setErrors("db.error");
    }
  }
  //修改商品数据
  public void updateProduct(EventModel em) throws Exception {
    Product product = (Product) em.getModel();
    try {
      ProductLocal productLocal = productHome.findByPrimaryKey(product.
          getProductId());
      productLocal.setDescription(product.getDescription());
      productLocal.setName(product.getName());

      //改变所属的商品类别
      CategoryLocal categoryLocal = chome.findByPrimaryKey(product.getCatId());
      if (categoryLocal != productLocal.getCategory()) {
        productLocal.setCategory(categoryLocal);
      }
      //如果图片内容发生修改,更新数据库
      if (product.getImage() != null) {
        Images images = productLocal.getImages();
        images.setData(product.getImage());
      }
    } catch (Exception ex) {
      logger.error(ex);
      em.setErrors("db.error");
    }
  }
  //if cascade delete is true , this will delete all items;
  public void deleteProduct(EventModel em) throws Exception {
    Product product = (Product) em.getModel();
    try {//如果CMR配置中cascade delete激活,将删除商品下的所有品种
      productHome.remove(product.getProductId());
    } catch (Exception ex) {
      logger.error(" --->> customer create error:" + ex);
      em.setErrors("db.error");
    }
  }
  //得到某个Product数据Model
  public Product getProductById(String Id) {
    logger.debug(" looking for  id=" + Id);
    try {
      ProductLocal productLocal = productHome.findByPrimaryKey(Id);
      return getProduct(productLocal);
    } catch (FinderException ex) {
      logger.warn(ex);
    } catch (Exception ex) {
      logger.error(ex);
    }
    return null;
  }

8.4.2  EJB配置

第二步是JNDI和EJB引用配置。在ProductManager的ejbCreate创建对很多实体Bean的调用,如下:
public void ejbCreate() throws CreateException {
    try {
      ServiceLocator serviceLocator = new ServiceLocator();
      sequenceHome = (SequenceGeneratorLocalHome) serviceLocator.getLocalHome(
          JNDINames.SEQUENCEGENERATOR_HOME);
      chome = (CategoryHome) serviceLocator.getLocalHome(JNDINames.
          CATEGORY_HOME);
      productHome = (ProductHome) serviceLocator.getLocalHome(
          JNDINames.PRODUCT_HOME);
      itemHome = (ItemHome)
           serviceLocator.getLocalHome(JNDINames.ITEM_HOME);

      itemAttrsHome = (ItemAttrsHome) serviceLocator.getLocalHome(JNDINames.
          ITEM_ATTRS_HOME);
      inventoryHome = (InventoryHome) serviceLocator.getLocalHome(JNDINames.
          INVENTORY_HOME);
      imagesHome = (ImagesHome) serviceLocator.getLocalHome(JNDINames.
          IMAGES_HOME);
    } catch (Exception ex) {
      logger.error("create error:" + ex);
      throw new CreateException();
    }
}
这些实体Bean的JNDI值如下:
public class JNDINames {
  public static final String SEQUENCE_NAME = "ESTORE";
  public final static String CATEGORY_HOME = "java:comp/env/ejb/Catalog";
  public final static String CATEGORY_DETAILS_HOME =
      "java:comp/env/ejb/Catalog_details";
  public static final String SEQUENCEGENERATOR_HOME =
      "java:comp/env/ejb/SequenceGenerator";
  public final static String PRODUCT_HOME = "java:comp/env/ejb/Product";
  public final static String ITEM_HOME = "java:comp/env/ejb/Item";
  public final static String ITEM_ATTRS_HOME = "java:comp/env/ejb/ItemAttrs";
  public final static String INVENTORY_HOME = "java:comp/env/ejb/Inventory";
  public final static String IMAGES_HOME = "java:comp/env/ejb/Images";
  public final static String CATALOG_DAO = "java:comp/env/DAO/Catalog";
  public final static String CATALOG_DATASOURCE = "java:comp/env/jdbc/Catalog";
}
需要在ProductManager配置中配置这些JNDI引用,在ejb-jar.xml中的ProductManager加入如下配置:
<session>
     <display-name>ProductManager</display-name>
     <ejb-name>ProductManager</ejb-name>
    <local-home>com.jdon.estore.catalog.ProductManagerLocalHome</local-home>
     <local>com.jdon.estore.catalog.ProductManagerLocal</local>
     <ejb-class>com.jdon.estore.catalog.ProductManagerBean</ejb-class>
     <session-type>Stateless</session-type>
     <transaction-type>Container</transaction-type>
    <ejb-local-ref>
                <description />
                <ejb-ref-name>ejb/Product</ejb-ref-name>
                <ejb-ref-type>Entity</ejb-ref-type>
                <local-home>com.jdon.estore.catalog.ProductHome</local-home>
                <local>com.jdon.estore.catalog.ProductLocal</local>
                <ejb-link>Product</ejb-link>
     </ejb-local-ref>
     <ejb-local-ref>
                <description />
                <ejb-ref-name>ejb/Item</ejb-ref-name>
                <ejb-ref-type>Entity</ejb-ref-type>
                <local-home>com.jdon.estore.catalog.ItemHome</local-home>
                <local>com.jdon.estore.catalog.ItemLocal</local>
                <ejb-link>Item</ejb-link>
     </ejb-local-ref>
     ……
</session>
前面章节已经介绍过,EJB引用配置实现两个步骤,一个是ejb-jar.xml配置;还有一个是和具体容器产品相关的配置,JBoss的配置是jboss.xml配置,代码如下:
<session>
     <ejb-name>ProductManager</ejb-name>
     <local-jndi-name>ProductManagerLocal</local-jndi-name>
     <ejb-local-ref>
                <ejb-ref-name>ejb/Product</ejb-ref-name>
                <local-jndi-name>Product</local-jndi-name>
     </ejb-local-ref>
     <ejb-local-ref>
                <ejb-ref-name>ejb/Item</ejb-ref-name>
                <local-jndi-name>Item</local-jndi-name>
     </ejb-local-ref>
     …
</session>
所有的CMP实体Bean是统一使用一个数据源JNDI。前面商品类别管理功能实现中已经配置了数据库源,因此,这里就无需再设置了。
EJB的主要开发两步内完成,以下是Web层的开发。

 

 

下页