开发一个网上商店系统(二)
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&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层的开发。