开发一个网上商店系统
作者:板桥banq
上页
8.4.3 创建Product相关类实现
第三步是创建数据增、删、改、查框架的具体实现,Product、ProductForm和ProductHandler。
Product主要代码如下:
public class Product implements Model {
private String productId;
private String catId;
private String name;
private byte[] image;
private String description;
private Collection items = new ArrayList();
public String getProductId() {
return productId;
}
public void setProductId(String productId) {
this.productId = productId;
}
…
}
ProductForm基本与Product内容相似,代码省略。
ProductHandler是继承ModelHandler,暂时不编写具体实现内容。因为商品数据管理有点特殊,需要上传图片等,等创建好JSP页面后,再在两者之间调整。
8.4.4 Web配置
第四步是配置文件编写,在modelmapping.xml加入如下代码:
<modelmapping formName = "productForm"
key="productId"
model="com.jdon.estore.model.Product"
handler = "com.jdon.estore.web.catalog.ProductHandler" />
在struts_config.xml中加入如下代码:
<form-bean name="productForm" type="com.jdon.estore.web.catalog.ProductForm" />
<action attribute="productForm"
type="com.jdon.strutsutil.ModelViewAction"
validate="false" scope="request"
path="/admin/productAction">
<forward name="create" path="/admin/product.jsp" />
<forward name="edit" path="/admin/product.jsp" />
</action>
<action name="productForm"
type="com.jdon.strutsutil.ModelSaveAction"
input="/admin/product.jsp"
scope="request"
path="/admin/saveProductAction">
<forward name="success" path="/admin/productOk.jsp" />
<forward name="failure" path="/admin/productOk.jsp" />
</action>
对比发现,Product的配置和前面Category的配置相差不多,这就降低了系统的配置风险。
8.4.5 创建product.jsp
第五步是创建product.jsp页面,主要代码如下:
<html:errors/>
<html:form action="/admin/saveProductAction.do" method="POST"
onsubmit="return checkform(this)">
<html:hidden property="action" /> <!-- 关键一句,必须 -->
<html:hidden property="productId"/><!-- 关键一句,必须 -->
商品类别:
<html:select property="catId" >
<html:options name="productForm" property="values" labelProperty="labels"/>
</html:select>
<input type="button" value="新增/编辑 商品类别" onclick="MM_openBrWindow('<%=request.getContextPath()%>/admin/categoryListAction.do','width=400,height=250,scrollbars=yes')">
商品名称:<html:text property="name" size="20" />
商品说明:<html:textarea rows="4" cols="32" property="description"/>
商品图片:
<img src="<%=request.getContextPath()%>/imageShowAction.do?Id=<bean:write name="productForm" property="productId"/>" name="ico" />
<input type="button" value="上传图片"
onclick="MM_openBrWindow('uploadImg.jsp','width=400,height=250,scrollbars=yes,resizable=yes')">
<logic:equal name="productForm" property="action" value="create">
<html:submit property="submit" value="新增"/>
</logic:equal>
<logic:equal name="productForm" property="action" value="edit">
<html:submit property="submit" value="修改"/>
</logic:equal>
<html:reset value ="复位"/>
</html:form>
<!-- 在编辑情况下,陈列本商品所有的品种 开始-->
<logic:equal name="productForm" property="action" value="edit">
<form method="POST" action="" name="listForm">
<html:hidden name="productForm" property="productId"/>
<tr>
<td bgcolor="#ffffff">品种名称</td>
<td bgcolor="#ffffff">单价</td>
<td bgcolor="#ffffff">销售价</td>
<td bgcolor="#ffffff">库存</td>
<td bgcolor="#ffffff"> </td>
</tr> <tr>
<logic:iterate id="item" name="productForm" property="items">
<tr>
<td bgcolor="#ffffff"><bean:write name="item" property="name"/></td>
<td bgcolor="#ffffff"><bean:write name="item" property="unitcost"/></td>
<td bgcolor="#ffffff"><bean:write name="item" property="listprice"/></td>
<td bgcolor="#ffffff"><bean:write name="item" property="qty"/></td>
<td bgcolor="#ffffff">
<input type="radio" name="itemId" value="<bean:write name="item" property="itemId" />">
</td>
</tr>
</logic:iterate>
<tr> <td bgcolor="#ffffff" colspan ="5" >
<input type="button" name="create" value="新增品种"
onclick="addAction('<%=request.getContextPath()%>/admin/itemAction.do')" >
<input type="button" name="edit" value="编辑品种"
onclick="editAction('<%=request.getContextPath()%>/admin/itemAction.do?action=edit', 'itemId')" >
</td></tr></table>
</form>
</logic:equal>
<!-- 在编辑情况下,这是本段总的条件 结束-->
</td></tr></table>
</body>
</html:html>
该product.jsp编辑功能页面示意图如8-9所示。
在商品数据新增、修改界面中,需要选择商品所属类别。商品类别是在CategoryForm中,而当前ActionForm是ProductForm,那么如何在product.jsp中通过下列语句实现商品类别列表?
<html:select property="catId" >
<html:options name="productForm" property="values" labelProperty="labels"/>
</html:select>
这就需要在productHandler中加入相应的内容:

图8-9 商品编辑功能页面
public class ProductHandler implements ModelHandler {
private final static String module = ProductHandler.class.getName();
private final static ServiceServerFactory sf = ServiceServerFactory.
getInstance();
protected final static CacheFactory cacheFactory = CacheFactory.getInstance();
//本方法是JSP页面产生前执行
public ModelForm initForm(HttpServletRequest request) throws Exception{
CatalogManagerLocal catalogManager = (CatalogManagerLocal) sf.getService(
FrameworkServices.CatalogEJB, request);
int AllCount = catalogManager.getCategoryAllCount();
//获得所有类别ID
PageIterator pageIterator = catalogManager.getCategories(0, AllCount);
ProductForm form = new ProductForm();
List labels = new ArrayList(pageIterator.getSize());
List values = new ArrayList(pageIterator.getSize());
labels.add("");
values.add("");
while (pageIterator.hasNext()) {
String catId = (String) pageIterator.next();
//首先从缓存中获取
Category model = (Category) cacheFactory.getObect(catId);
if (model == null) {
model = catalogManager.getCategoryById(catId);
if (model.CACHEABLE) {
cacheFactory.putObect(catId, model);
}
}
labels.add(model.getName());
values.add(model.getCatId());
}
pageIterator.reset();
//转换成数组,保存到productForm中
form.setLabels((String[])labels.toArray(new String[0]));
form.setValues((String[])values.toArray(new String[0]));
return form;
}
//查询一个Product实例
public Model findModelByKey(String keyValue, HttpServletRequest request) throws Exception{
ProductManagerLocal productManager = (ProductManagerLocal) sf.getService(
FrameworkServices.ProductEJB, request);
return productManager.getProductById(keyValue);
}
//实行最后的数据库保存等操作
public void serviceAction(EventModel em, HttpServletRequest request) throws
java.lang.Exception {
ProductManagerLocal productManager = (ProductManagerLocal) sf.getService(
FrameworkServices.ProductEJB, request);
//从HttpSession中获得上传的图片,保存到数据库中
byte[] data = (byte[]) UploadAction.getUploadData(request);
if (data != null) {
Product product = (Product) em.getModel();
product.setImage(data);
}
try {
switch (em.getActionType()) {
case Event.CREATE:
productManager.createProduct(em);
break;
case Event.EDIT:
productManager.updateProduct(em);
break;
case Event.DELETE:
productManager.deleteProduct(em);
break;
}
} catch (Exception ex) {
throw new Exception("System operation Error:" + ex);
}
}
}
8.4.6 商品图片上传功能
在productHandler中,有从HttpSession中取出上传的图片的功能。UploadAction是负责上传功能,接受上传的图片后,转换成byte数组保存在HttpSession中,代码如下:
public class UploadAction extends Action {
public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response) throws Exception {
UploadForm theForm = (UploadForm) form;
//retrieve the file representation
FormFile file = theForm.getTheFile();
String size = (file.getFileSize() + " bytes");
try {
//retrieve the file data
ByteArrayOutputStream baos = new ByteArrayOutputStream();
InputStream stream = file.getInputStream();
byte[] buffer = new byte[8192];
int bytesRead = 0;
while ( (bytesRead = stream.read(buffer, 0, 8192)) != -1) {
baos.write(buffer, 0, bytesRead);
}
byte[] data = baos.toByteArray();
baos.close();
//close the stream
stream.close();
//save to the session , then load it! 暂时保存到HttpSession中
HttpSession session = request.getSession(true);
session.setAttribute(UploadForm.module, data);
//destroy the temporary file created
} catch (Exception ex) {
throw new Exception(ex);
} finally {
file.destroy();
}
//return a forward to display.jsp
return mapping.findForward("display");
}
//供外部调用,获取上传的数据。productHandler调用的是该方法
public static byte[] getUploadData(HttpServletRequest request) {
//load the image in the session
HttpSession session = request.getSession();
byte[] data = null;
if (session != null) {
data = (byte[]) session.getAttribute(UploadForm.module);
session.removeAttribute(UploadForm.module);
}
return data;
}
}
UploadAction是接受客户上传的图片,然后暂时保存在客户HttpSession中,ProductHandler将HttpSession中的图片取出后,保存到数据库中,可见ProductHandler的serviceAction一段代码。
UploadForm是ActionForm的子类,UploadAction是从UploadForm中接受上传图片的,UploadForm代码如下:
public class UploadForm extends ActionForm {
public final static String module = UploadForm.class.getName();
public static final String ERROR_PROPERTY_MAX_LENGTH_EXCEEDED =
"com.jdon.strutsutil.file.MaxLengthExceeded";
private FormFile theFile;
public FormFile getTheFile() { return theFile; }
public void setTheFile(FormFile theFile) { this.theFile = theFile; }
public ActionErrors validate(ActionMapping mapping,
HttpServletRequest request) {
ActionErrors errors = null;
//检查上传图片大小是否超过在struts_config.xml中规定的大小
Boolean maxLengthExceeded = (Boolean)
request.getAttribute(MultipartRequestHandler.
ATTRIBUTE_MAX_LENGTH_EXCEEDED);
if ( (maxLengthExceeded != null) && (maxLengthExceeded.booleanValue())) {
errors = new ActionErrors();
errors.add(ERROR_PROPERTY_MAX_LENGTH_EXCEEDED,
new ActionError("maxLengthExceeded"));
}else{
//检查上传文件是否为图片文件
String fileName = theFile.getFileName();
if ( (!fileName.toLowerCase().endsWith(".gif")) &&
! (fileName.toLowerCase().endsWith(".jpg")) &&
! (fileName.toLowerCase().endsWith(".png"))) {
errors = new ActionErrors();
errors.add("notImage", new ActionError("notImage"));
}
}
return errors;
}
}
在UploadForm中可以检查图片大小。在Struts 1.1中,图片大小是在struts_config.xml中设置的,相关设置如下:
<form-beans>
<form-bean name="uploadForm" type="com.jdon.strutsutil.file.UploadForm" />
…
</form-beans>
<action-mappings>
<action name="uploadForm"
type="com.jdon.strutsutil.file.UploadAction"
input="/admin/uploadImg.jsp" scope="request"
path="/admin/uploadAction">
<forward name="display" path="/admin/uploadOk.jsp" />
</action>
<action type="com.jdon.estore.web.catalog.ImgShowAction"
validate="false" path="/imageShowAction" />
…
</action-mappings>
配置中/imageShowAction是专门用来显示图片的Action实现。当图片上传后,或者商品数据列表时,都需要显示图片。ImgShowAction先从缓存中获取图片数据byte数组,如果缓存没有,从数据库images中根据Id读取;如果没有输入参数Id,则从客户HttpSession中查询,是否是客户刚刚上传。
8.5 商品批量查询和多页显示
在本系统中,一个商品类别下可能有很多商品,这些商品可能需要多个页面才能完全显示。这涉及到一个批量查询、多页显示的框架设计。
该框架需要分别在EJB和Web两个层面实现。EJB实现需要使用DAO+JDBC模式,而Web主要是解决多页显示的问题。
通常的思路是:从数据库中将当前页面需要显示的数据对象查询后,组成数据Model集合,传送到Web层用于显示。
这种思路有一个缺点:将不能使用设置在Web层的Model缓存系统。Web层的Model缓存保存着大量之前调用过的数据Model。在批量规模查询时,不使用这些缓存将浪费很多性能,特别是这些数据Model被不同客户反复调用查询时。
相比而下,前面章节“Jive论坛系统”中介绍的Jive缓存设计是比较合理而且灵活的方案。它不是查询出当前页面完整的数据对象,而只是查询出当前页面的数据对象ID集合。这样做的优点有两个:
因为只是查询ID,相对提高查询速度,节约查询内存。
EJB层返回ID集合后,Web层可以根据ID,首先查询缓存系统的Model数据,如果没有,则再根据ID查询数据库。
因此,批量查询的设计方案如下:
获得整个数据表记录总数。
获取当前页面所有数据的ID集合,该部分也可实现缓存。
在ModelListAction中在iterator中遍历时,根据ID获取完整数据对象,先查询缓冲,如果没有,再根据ID读取数据库。完整数据对象包装成List,赋予到ListForm(ActionForm的一个子类实现),
使用PagerTag等标签库显示多页标识,标签库主要从ListForm中获取相关信息。
下页