分享

J2EE应用系统JPetstore

 coolboybobo 2010-11-10

 J2EE应用系统JPetstore

IBatis.comiBATIS-Jpetstore为例,我们使用Jdon框架对其重构成为Jdon-JPetstore,本章开发环境是Eclipse(本章案也适用其他开发工具),部署运行环境是JBoss

Eclipse是一个非常不错的开源开发工具,使用Eclipse开发和使用JBuilder将有完全不同的开发方式。我们使用Eclipse基于Jdon框架开发一个完全Web应用,或者可以说,开发一个轻量(lightweight)的J2EE应用系统。

通过这个轻量系统开发,说明Jdon框架对完全POJO架构的支持,因为EJB分布式集群计算能力,随着访问量提升,可能需要引入EJB架构,这时只要使用EJB session Bean包装POJO服务则可以无缝升级到EJB。使用Jdon框架可实现方便简单地架构升迁。

Eclipse安装简要说明

1.下载Eclipse:http://www. 的下载点中选择tds ISP 比较快。

2.安装免费插件:

编辑Jsp需要lomboz http://www./projects/download.jsp

注意对应的Eclipse版本。

编辑XML,使用Xmlbuddy: http:///

基本上这两个插件就够了。

 

如果希望开发 Hibernate,插件:

http://www./hibernatesync

 

代码折叠

http://www./eclipse/update-site/site.xml

 

3. 关键学习ant的编写build.xml,在build.xml将你的jsp javaclass打包成warjarear就可以。都可以使用antjar打包,build.xml只要参考一个模板就可以:SimpleJdonFrameworkTest.rar 有一个现成的,可拷贝到其它项目后修改后就可用.

然后在这个模板上修改,参考 ant的命令参考:

http://ant./manual/tasksoverview.html

网上有中文版的ant参考,在google搜索就能找到。

关键是学习antbuild.xml编辑,SimpleJdonFrameworkTest.rar 有一个现成的,可拷贝到其它项目后修改后就可用.

ant编译替代Eclipse的缺省编译:选择项目属性-->Builders ---> new --> Ant Builder --->选择本项目的build.xml workspace 选择本项目

eclipse开发就这些,非常简单,不象Jbuilder那样智能化,导致项目目录很大。eclipse只负责源码开发,其它都由ant负责

架构设计要点

Jdon-JPetstore除了保留iBATIS-JPetstore 4.0.5的域模型、持久层ibatis实现以及Jsp页面外,其余部分因为使用了Jdon框架而和其有所不同。

保留域模型和Jsp页面主要是在不更改系统需求的前提下,重构其架构实现为Jdon框架,通过对比其原来的实现或SpringJPetstore实现,可以发现Jdon框架的使用特点。

在原来jpetstore iBatis包会延伸到表现层,例如它的分页查询PaginatedListiBatis只是持久层框架,它的作用范围应该只限定在持久层,这是它的专业范围,如果超过范围,显得 ….。所以,在Jdon-Jpetstore中将iBatis封装在持久层(砍掉PaginatedList这只太长的手),Jdon框架是一种中间层框架,联系前后台的工作应该由Jdon这样的中间层框架完成。

iBatis 4.0.5版本中,它使用了一个利用方法映射Reflection的小框架,这样,将原来需要在Action实现方法整入了ActionForm中实现,ActionForm变成了一个复杂的对象:页面表单抽象以及与后台Service交互,在ActionForm中调用后台服务是通过单态模式实现,这是在一般J2EE开发中忌讳的一点,关于单态模式的讨论可见:http://www./jive/article.jsp?forum=91&thread=17578

Jdon框架使用了微容器替代单态,消除了Jpetstore的单态隐患,而且也简化了ActionForm和服务层的交互动作(通过配置实现)。

用户注册登陆模块实现

用户域建模(Model

首先,我们需要从域建模开始,建立正确的领域模型,以用户账号为例,根据业务需求我们确立用户账号的域模型Account,该模型需要继承Jdon框架中的com.jdon.controller.model.Model

 

public class Account extends Model {

 

  private String username;

  private String password;

  private String email;

  private String firstName;

  private String lastName;

  ……

 

}

username是主键。

域模型建立好之后,就可以花开两朵各表一支,表现层和持久层可以同时开发,先谈谈持久层关于用户模型的CRUD功能实现。

持久层Account CRUD实现

主要是用户的新增和修改,主要用于注册新用户和用户资料修改。

public interface AccountDao {

  Account getAccount(String username);  //获得一个Account

  void insertAccount(Account account);  //新增

  void updateAccount(Account account); //修改

}

持久层可以使用多种技术实现,例如Jdon框架的JdbcTemp代码实现比较方便,如果你的sql语句可能经常改动,使用iBatissql语句XML定义有一定好处,本例程使用Jpetstore原来的持久层实现iBatis。见源码包中的Account.xml

 

表现层Account表单创建(ModelForm

这是在Domain Model建立后最重要的一步,是前台表现层Struts开发的起步,表单创建有以下注意点:

表单类必须继承com.jdon.model.ModelForm

表单类基本是Domain Model的影子,每一个Model对应一个ModelForm实例,所谓对应:就是字段名称一致。ModelForm实例是由Model实例复制获得的。

 

public class AccountForm extends ModelForm {

 

  private String username;

  private String password;

  private String email;

  private String firstName;

  private String lastName;

  ……

 

}

当然AccountForm可能有一些与显示有关的字段,例如注册时有英文和中文选择,以及类别的选择,那么增加两个字段在AccountForm中:

  private List languages;

  private List categories;

这两个字段需要初始化值的,因为在AccountForm对应的Jsp的页面中要显示出来,这样用户才可能进行选择。选择后的值将放置在专门的字段中。

有两种方式初始化这两个字段:

1. AccountForm构造方法中初始化,前提是:这些初始化值是常量,如:

public AccountForm() {

    languages new ArrayList();

    languages.add("english");

    languages .add("japanese");

}

2.如果初始化值是必须从数据库中获取,那么采取前面章节介绍的使用ModelHandler来实现,这部分又涉及配置和代码实现,缺省时我们考虑通过jdonframework.xml配置实现。

 

Account CRUDstruts-config.xml的配置

第一步配置ActionForm

上节编写了ModelForm代码,ModelForm也就是strutsActionForm,在struts-config.xml中配置ActionForm如下:

 <form-bean name="accountFrom" type="com.jdon.framework.samples.jpetstore.presentation.form.AccountForm"/>

第二步配置Action

这需要根据你的CRUD功能实现需求配置,例如本例中用户注册和用户修改分开,这样,配置两套ModelViewActionModelSaveAction,具体配置见源码包中的struts-config-security.xml,这里将struts-config.xml根据模块划分成相应的模块配置,实现多模块开发,本模块是用户注册登陆系统,因此取名struts-config-security.xml

Account CRUDJdonframework.xml配置

    <model key="username" class ="com.jdon.framework.samples.jpetstore.domain.Account">

      <actionForm name="accountForm"/>

      <handler>

        <service ref="accountService">

          <initMethod   name="initAccount" />                           

          <getMethod    name="getAccount" />

          <createMethod name="insertAccount" />

          <updateMethod name="updateAccount" />

          <deleteMethod name="deleteAccount" />

        </service>

      </handler>

    </model> 

.其中有一个initMethod主要用于AccuntForm对象的初始化。其他都是增删改查的常规实现。

Account CRUD Jsp页面实现

在编辑页面EditAccountForm.jsp中加入:

<html:hidden name="accountFrom" property="action" value="create" />

在新增页面NewAccountForm.jsp加入:

<html:hidden name="accountFrom" property="action" value="edit" />

所有的字段都是直接来自accountFrom

 

整理模块配置

商品模块功能完成,struts提供了多模块开发,因此我们可以将这一模块单独保存在一个配置中:/WEB-INF/struts-config-security.xml,这样以后扩展修改起来方便。

 

商品查询模块实现

iBATIS-JPetstore中没有单独的CategoryForm,而是将三个ModelCategoryProduct、,Item合并在一个CatalogBean中,这样做的缺点是拓展性不强,将来这三个Model也许需要单独的ActionForm

由于我们使用Jdon框架的CRUD功能配置实现,因此,不怕细分这三个Model带来代码复杂和琐碎。

由于原来的Jpetstore“偷懒”,没有实现Category Product等的CRUD功能,只实现它们的查询功能,因此,我们使用Jdon框架的批量查询来实现查询。

持久层 Product批量查询实现

商品查询主要有两种批量查询,根据其类别IDCategoryId查询所有该商品目录下所有的商品;根据关键字搜索符合条件的所有商品,下面以前一个功能为例子:

iBatis-jpetstore使用PaginatedList作为分页的主要对象,该对象需要保存到HttpSession中,然后使用PaginatedListNextPage等直接遍历,这种方法只适合在小数据量合适,J2EE编程中不推荐向HttpSession放入大量数据,不利于cluster

根据Jdon批量查询的持久层要求,批量查询需要两种SQL语句实现:符合条件的ID集合和符合条件的总数:以及单个Model查询。

  //获得ID集合

 List getProductIDsListByCategory(String categoryId, int pagessize);

  //获得总数

  int getProductIDsListByCategoryCount(String categoryId);

  //单个Model查询

  Product getProduct(String productId) ;

这里我们需要更改一下iBatis原来的Product.xml配置,原来,它设计返回的是符合条件的所有Product集合,而我们要求是Product ID集合。

修改Product.xml如下:

<resultMap id="productIDsResult" class="java.lang.String">

    <result property="value" column="PRODUCTID"/>

</resultMap>              

                  

<select id="getProductListByCategory" resultMap="productIDsResult" parameterClass="string">

    select PRODUCTID from PRODUCT where CATEGORY = #value#

</select>

        

<select id="getProductListByCategoryCount"  resultClass="java.lang.Integer" parameterClass="string">

    select count(1) as value from PRODUCT where CATEGORY = #value#

</select>

    ProductDaoIBatis DAO实现,读取Product.xml中配置:

    public List  getProductIDsListByCategory(String categoryId, int start, int pagessize) {

        return sqlMapDaoTemplate.queryForList(

                "getProductListByCategory", categoryId, start, pagessize);

    }

 

    public int getProductIDsListByCategoryCount(String categoryId){

        Integer countI = (Integer)sqlMapDaoTemplate.queryForObject(

                "getProductListByCategoryCount", categoryId);

        return countI.intValue();

    }  

这样,结合配置的iBatis DAOJdon框架批量查询,在ProductManagerImp中创建PageIterator,当然这部分代码也可以在ProductDao实现,创建PageIterator代码如下:

      public PageIterator getProductIDsListByCategory(String categoryId, int start, int count)

       {

           PageIterator pageIterator = null;

            try {

                List list = productDao.getProductIDsListByCategory(categoryId, start, count);

                int allCount = productDao.getProductIDsListByCategoryCount(categoryId);

                int currentCount = start + list.size();

                pageIterator = new PageIterator(allCount, list.toArray(), start,

                        (currentCount < allCount)?true:false);

 

         } catch (DaoException daoe) {

             Debug.logError(" Dao error : " + daoe, module);

         }

       

       return pageIterator;

表现层Product批量查询实现

    根据批量查询的编程步骤,在表现层主要是实现ModelListAction继承、配置和Jsp编写,下面分步说:

    第一步,创建一个ModelListAction子类ProductListAction,实现两个方法:getPageIteratorfindModelByKeygetPageIterator方法如下:

      public PageIterator getPageIterator(HttpServletRequest request, int start,

            int count) {

        ProductManager productManager = (ProductManager) WebAppUtil.getService(

                "productManager", request);

        String categoryId = request.getParameter("categoryId");

        return productManager.getProductIDsListByCategory(categoryId, start, count);

                

    }

    findModelByKey方法如下:

      public Model findModelByKey(HttpServletRequest request, Object key) {

        ProductManager productManager = (ProductManager) WebAppUtil.getService(

                "productManager", request);

        return productManager.getProduct((String)key);

    }

    由于我们实现的是查询一个商品目录下所有商品功能,因此,需要显示商品目录名称,而前面操作的都是Product模型,所以在显示页面也要加入商品目录Category模型,我们使用ModelListActioncustomizeListForm方法:

    public void customizeListForm(ActionMapping actionMapping,

            ActionForm actionForm, HttpServletRequest request,

            ModelListForm modelListForm) throws Exception {

        ModelListForm listForm = (ModelListForm) actionForm;

        ProductManager productManager = (ProductManager) WebAppUtil.getService(

                "productManager", request);

        String categoryId = request.getParameter("categoryId");

        Category category = productManager.getCategory(categoryId);

        listForm.setOneModel(category);

    }

    第二步,配置struts-config.xml,配置ActionFormAction

    <form-bean name="productListForm" type="com.jdon.strutsutil.ModelListForm"/>

    action配置如下:

    <action path="/shop/viewCategory"

type="com.jdon.framework.samples.jpetstore.presentation.action.ProductListAction"

      name="productListForm" scope="request"

      validate="false" >

      <forward name="success" path="/catalog/Category.jsp"/>

    </action>

  第三步,编写Category.jsp

  productListForm中取出我们要显示两个模型,一个是oneModel中的Category;另外一个是Product Model集合listJsp语法如下:

<bean:define id="category" name="productListForm" property="oneModel" />

<bean:define id="productList" name="productListForm" property="list" />

我们可以显示商品目录名称如下:

<h2><bean:write name="category" property="name" /></h2>

这样我们就可以遍历productList中的Product如下:

<logic:iterate id="product" name="productList" >

  <tr bgcolor="#FFFF88">

  <td><b><html:link paramId="productId" paramName="product" paramProperty="productId" page="/shop/viewProduct.shtml"><font color="BLACK"><bean:write name="product" property="productId" /></font></html:link></b></td>

  <td><bean:write name="product" property="name" /></td>

  </tr>

</logic:iterate>

加上分页标签库如下:

  <MultiPages:pager actionFormName="productListForm " page="/shop/viewCategory.do"

               paramId="categoryId" paramName="category" paramProperty="categoryId">

<MultiPages:prev><img src="../images/button_prev.gif" border="0"></MultiPages:prev>

<MultiPages:index />

<MultiPages:next><img src="../images/button_next.gif" border="0"></MultiPages:next>

</MultiPages:pager>

至此,一个商品目录下的所有商品批量查询功能完成,由于是基于框架的模板化编程,直接上线运行成功率高。

商品搜索批量查询:

参考上面步骤,商品搜索也可以顺利实现,从后台到前台按照批量查询这条线索分别涉及的类有:

持久层实现:ProductDao中的三个方法:

List searchProductIDsList(String keywords, int start, int pagessize); //ID集合

 

int searchProductIDsListCount(String keywords); //总数

 

Product getProduct(String productId) ; //单个Model

表现层:建立ProductSearchAction类,配置struts-config.xml如下:

    <action path="/shop/searchProducts"

type="com.jdon.framework.samples.jpetstore.presentation.action.ProductSearchAction"

      name="productListForm" scope="request"

      validate="false">

      <forward name="success" path="/catalog/SearchProducts.jsp"/>

    </action>

与前面使用的都是同一个ActionFormproductListForm

编写SearchProducts .jsp,与Category.jsp类似,相同的是ActionForm;不同的是action

商品条目Item批量查询

条目Item批量实现与Product批量查询类似:

持久层:ItemDao提供三个方法:

List getItemIDsListByProduct(String productId, int start, int pagessize);//ID集合

 

  int getItemIDsListByProductCount(String productId);//总数

 

  Item getItem(String itemId); //单个Model

表现层:创建一个ItemListAction继承ModelListAction:完成getPageIteratorfindModelByKey,如下:

public class ItemListAction extends ModelListAction {

 

    public PageIterator getPageIterator(HttpServletRequest request, int start,

            int count) {

        ProductManager productManager = (ProductManager) WebAppUtil.getService(

                "productManager", request);

        String productId = request.getParameter("productId");

        return productManager.getItemIDsListByProduct(productId, start, count);

    }

 

    public Model findModelByKey(HttpServletRequest request, Object key) {

        ProductManager productManager = (ProductManager) WebAppUtil.getService(

                "productManager", request);

        return productManager.getItem((String)key);

    }

 

    public void customizeListForm……….

}

与前面的ProductListAction相比,非常类似,不同的是Model名称不一样,一个是Product一个是Item;

struts-config.xml配置如下:

    <form-bean name="itemListForm" type="com.jdon.strutsutil.ModelListForm"/>

 

    <action path="/shop/viewProduct"

type="com.jdon.framework.samples.jpetstore.presentation.action.ItemListAction"

      name="itemtListForm" scope="request"

      validate="false">

      <forward name="success" path="/catalog/Product.jsp"/>

    </action>

比较前面product的配置,非常类似,其实itemListFormproductListForm是同一个ModelListForm类型,可以合并起来统一命名为listForm,节省ActionForm的配置。

Product.jsp页面与前面的Category.jsp SearchProdcuts.jsp类似。

<bean:define id="product" name="itemListForm" property="oneModel" />

<bean:define id="itemList" name="itemListForm" property="list" />

分页显示:

  <MultiPages:pager actionFormName="itemListForm" page="/shop/viewProduct.do"

              paramId="productId" paramName="product" paramProperty="productId">

    …..

商品条目Item单条查询

单个显示属于CRUD中的一个查询功能,我们需要建立Model对应的ModelForm,将Item的字段拷贝到ItemForm中。配置这个ActionForm如下:

    <form-bean name="itemForm"

type="com.jdon.framework.samples.jpetstore.presentation.form.ItemForm"/>

第二步:因为这个功能属于CRUD一种,无需编程,但是需要配置jdonframework.xml

   <model key="itemId" class ="com.jdon.framework.samples.jpetstore.domain.Item">

      <actionForm name="itemForm"/>

      <handler>

        <service ref="productManager">

          <getMethod name="getItem" />

        </service>

      </handler>

    </model>

配置中只要一个方法getMethod就可以,因为只用到CRUD中的读取方式。

第三步:配置struts-config.xml如下:

    <action  path="/shop/viewItem"  type="com.jdon.strutsutil.ModelDispAction"

                   name="itemForm" scope="request"

                   validate="false">

      <forward name="success" path="/catalog/Item.jsp" />

      <forward name="failure" path="/catalog/Product.jsp" />

    </action>

第四步编辑Item.jsp,现在开始发现一个问题,Item.jsp中不只是显示Item信息,还有Product信息,而前面我们定义的是Item信息,如果使得Item.jsp显示Product信息呢,这就从设计起源Domain Model上考虑,在ItemModel中有Product引用:

  private Product product;

  public Product getProduct() { return product; }

  public void setProduct(Product product) { this.product = product; }

ItemProduct的多对一关系其实应该在域建模开始就考虑到了。

那么,我们只要在持久层查询Item时,能够将其中的Product字段查询就可以。在持久层的iBatisProduct.xml实现有下列SQL语句:

  <select id="getItem" resultMap="resultWithQuantity" parameterClass="string">

    select

      I.ITEMID, LISTPRICE, UNITCOST, SUPPLIER, I.PRODUCTID, NAME,

      DESCN, CATEGORY, STATUS, ATTR1, ATTR2, ATTR3, ATTR4, ATTR5, QTY

    from ITEM I, INVENTORY V, PRODUCT P where P.PRODUCTID = I.PRODUCTID and I.ITEMID = V.ITEMID and I.ITEMID = #value#

  </select>

这段语法实际在查询Item时,已经将Product查询出来,这样Item Model中已经有Product数据,因为ActionFormModel映射,因此,前台Jsp也可以显示Product数据。

Item.jsp中,进行下面定义:

<bean:define id="product" name="itemForm " property="product" />

<bean:define id="item" name="itemForm " />

itemFormproduct属性定义为product即可;这样不必大幅度修改原来的Item.jsp了。

整理模块配置

商品模块功能完成,struts提供了多模块开发,因此我们可以将这一模块单独保存在一个配置中:/WEB-INF/struts-config-catalog.xml,这样以后扩展修改起来方便。

 

购物车模块实现

购物车属于一种有状态数据,也就是说,购物车的scope生命周期是用户,除非这个用户离开,否则购物车一直在内存中存在。

有态POJO服务

现在有两种解决方案:

第一,将购物车状态作为数据类,保存到ActionForm中,设置scopesession,这种形式下,对购物车的数据操作如加入条目等实现不很方便,iBatis-jpetstore 4.0.5就采取这个方案,在数据类Cart中存在大量数据操作方法,那么Cart这个类到底属于数据类Model?还是属于处理服务类呢?

在我们J2EE编程中,通常使用两种类来实现功能,一种是数据类,也就是我们设计的Model;一种是服务类,如POJO服务或EJB服务,服务属于一种处理器,处理过程。使用这两种分类比较方便我们来解析业务需求,EJB中实体BeanSession Bean也是属于这两种类型。

iBatis-jpetstore 4.0.5则是将服务和数据类混合在一个类中,这也属于一种设计,但是我们认为它破坏了解决问题的规律性,而且造成数据和操作行为耦合性很强,在设计模式中我们还使用桥模式来分离抽象和行为,因此这种做法可以说是反模式的。那么我们采取数据类和服务分离的方式方案来试试看:

第二.购物车功能主要是对购物车这个ModelCRUD,与通常的CRUD区别是,数据是保存到HttpSession,而不是持久化到数据库中,是数据状态保存不同而已。所以如果我们实现一个CartService,它提供addupdatedelete等方法,只不过操作对象不是数据库,而是其属性为购物车Cart,然后将该CarService实例保存到HttpSession,实现每个用户一个CartService实例,这个我们成为有状态的POJO服务。

这种处理方式类似EJB架构处理,如果我们业务服务层使用EJB,那么使用有态会话Bean实现这个功能。

现在问题是,Jdon框架目前好像没有提供有状态POJO服务实例的获得,那么我们自己在WebAppUtil.getService获得实例后,保存到HttpSession中,下次再到HttpSession中获得,这种有状态处理需要表现层更多代码,这就不能使用Jdon框架的CRUD配置实现了,需要我们代码实现ModelHandler子类。

考虑到可能在其他应用系统还有这种需求,那么能不能将有状态的POJO服务提炼到Jdon框架中呢?关键使用什么方式加入框架,因为这是设计目标服务实例的获得,框架主要流程代码又不能修改,怎么办?

Jdon框架的AOP功能在这里显示了强大灵活性,我们可以将有状态的POJO服务实例获得作为一个拦截器,拦截在原来POJO服务实例获得之前。在Jdon框架设计中,目标服务实例的获得一般只有一次。

创建有状态POJO服务拦截器com.jdon.aop.interceptor. StatefulInterceptor,再创建一个空接口:com.jdon.controller.service.StatefulPOJOService,需要实现有状态实例的POJO类只要继承这个接口就可以。

配置aspect.xml,加入这个拦截器:

  <interceptor name="statefulInterceptor" class="com.jdon.aop.interceptor.StatefulInterceptor"

pointcut="pojoServices" />      

这里需要注意的是:你不能让一个POJO服务类同时继承Poolable,然后又继承Stateful,因为这是两种不同的类型,前者适合无状态POJO;后者适合CartService这样有状态处理;这种选择和EJB的有态/无态选择是一样的。

ModelService设计

购物车模块主要围绕域模型Cart展开,需要首先明确Cart是一个什么样的业务模型,购物车页面是类似商品条目批量查询页面,不过购物车中显示的不但是商品条目,还有数量,那么我们专门创建一个Model来指代它,取名为CartItemCartItemItem父集,多了一个数量。

这样购物车页面就是CartItem的批量查询页面,然后还有CartItemCRUD操作,所以购物车功能主要是CartItemCRUD和批量查询功能。

iBatis 4.0.5原来设计了专门Cart Model,其实这个Cart主要是一个功能类,因为它的数据项只有一个MapList,这根本不能代表业务需求中的一个模型。虽然iBatis 4..0.5也可以自圆其说实现了购物车功能,但是这种实现是随心所欲,无规律性可遵循,因而以后维护起来也是困难,维护人员理解困难,修改起来也没有章程可循,甚至乱改一气。

CartItem可以使用iBatis原来的CartItem,这样也可保持Cart.jsp页面修改量降低。删除原来的Cart这个Model,建立对应的CartService,实现原来的Cart一些功能。

public interface CartService {

       CartItem getCartItem(String itemId);

       void addCartItem(EventModel em);

       void updateCartItem(EventModel em);

       void deleteCartItem(EventModel em);

       PageIterator getCartItems();

}

CartServiceImpCartService子类,它是一个有状态POJO服务,代码简要如下:

public class CartServiceImp implements CartService, Stateful{

    private ProductManager productManager;

    //将原来iBatis Cart类中两个属性移植到CartServiceImp

    private final Map itemMap = Collections.synchronizedMap(new HashMap());

    private  List itemList = new ArrayList();

 

    public CartServiceImp(ProductManager productManager) {

        super();

        this.productManager = productManager;

    }

    ……

}

itemMap是装载CartItem的一个Map,是类属性,由于CartServiceImp是有状态的,每个用户一个实例,那么也就是每个用户有自己的itemMap列表,也就是购物车。

CartServiceImp中的 getCartItemIDs是查询购物车当前页面的购物条目,属于批量分页查询实现,这里有一个需要考量的地方,是getCartItems方法还是getCartItemIDs方法?也就是返回CartIem的实例集合还是CartItemItemId集合?按照前面标准的Jdon框架批量分页查询实现,应该返回CartItemItemId集合,然后由Jdon框架的ModelListAction根据ItemId首先从缓存中获得CartItem实例,但是本例CartItem本身不是持久化在数据库,而也是内存HttpSession中,所以ModelListAction这种流程似乎没有必要。

如果将来业务需求变化,购物车状态不是保存在内存而是数据库,这样,用户下次登陆时,可以知道他上次购物车里的商品条目,那么采取Jdon框架标准查询方案还是有一定扩展性的。

这里,我们就事论事,采取返回CartIem的实例集合,展示一下如何灵活应用Jdon框架的批量查询功能。下面是CartServiceImp getCartItems方法详细代码:

 public PageIterator getCartItems(int start, int count) {

        int offset = itemList.size() - start; //获得未显示的总个数

        int pageCount = (count < offset)?count:offset;

        List pageList = new ArrayList(pageCount); //当前页面记录集合

        for(int i=start; i< pageCount + start;i++){

            pageList.add(itemList.get(i));

        }

        int allCount = itemList.size();

        int currentCount = start + pageCount;

        return new PageIterator(allCount, pageList.toArray(new CartItem[0]), start,

                (currentCount < allCount)?true:false);

}

getCartItems方法是从购物车所有条目itemList中查询获得当前页面的条目,并创建一个PageIterator

注意,现在这个PageIteratorkeys属性中装载的不是数据ID集合,而是完整的CartItem集合,因为上面代码中pageList中对象是从itemList中获得,而itemList中装载的都是CartItem

表现层购物车显示功能

由于PageIterator中封装的是完整Model集合,而不是ID集合,所以现在表现层有两种方案,继承框架的ModelListAction;或重新自己实现一个Action,替代ModelListAction

这里使用继承框架的ModelListAction方案,巧妙地实现我们的目的,省却编码:

public class CartListAction extends ModelListAction {

 

    public PageIterator getPageIterator(HttpServletRequest request, int arg1, int arg2) {

        CartService cartService = (CartService)WebAppUtil.getService("cartService", request);

        return cartService.getCartItems();

    }

 

    public Model findModelByKey(HttpServletRequest arg0, Object key) {

        return (Model)key; //因为key不是主键,而是完整的Model,直接返回

    }

   

    protected boolean isEnableCache(){

        return false;  //无需缓存,CartItem本身实际是在内存中。

    }

 

}

配置struts-config.xml

<form-beans>    

   <form-bean name="listForm" type="com.jdon.strutsutil.ModelListForm"/>

</form-beans>   

 <action-mappings>

    <action path="/shop/viewCart"

type="com.jdon.framework.samples.jpetstore.presentation.action.CartListAction"

      name="listForm" scope="request"

      validate="false">

      <forward name="success" path="/cart/Cart.jsp"/>

    </action>

    ……

  </action-mappings>

上面是购物车显示实现,只要调用/shop/viewCart.shtml就可以显示购物车了。

Cart.jsp页面插入下面标签:

<logic:iterate id="cartItem" name="listForm" property="list">

    ….

</logic:iterate>

分页显示标签如下:

<MultiPages:pager actionFormName="listForm" page="/shop/viewCart.shtml">

<MultiPages:prev name="<font color=green><B><< Prev</B></font>"/>

<MultiPages:index />

<MultiPages:next name="<font color=green><B>Next >></B></font>"/>

</MultiPages:pager>

 

 

购物车新增删除条目功能

前面完成了购物车显示功能,下面是设计购物车的新增和删除、修改功能。

参考Jdon框架的CRUD功能实现,ModelCartItem,配置jdonframework.xml使其完成新增删除功能:

                   <model key="workingItemId"

                            class="com.jdon.framework.samples.jpetstore.domain.CartItem">

                            <actionForm name="cartItemForm"/>

                            <handler>

                                     <service ref="cartService">

                                               <createMethod name="addCartItem"/>

                                               <deleteMethod name="deleteCartItem"/>

                                     </service>

                            </handler>

                   </model>

在这个配置中,只有新增和删除方法,修改方法没有,因为购物车修改主要是其中商品条目的数量修改,它不是逐条修改,而是一次性批量修改,这里的ModelCartItem,这是购物车里的一个条目,因此如果这里写修改,也只是CartItem一个条目的修改,不符合我们要求。下面专门章节实现这个修改。

表现层主要是配置,没有代码,代码都依靠cartService中的addCartItemdeleteCartItem实现:例如:

    public void addCartItem(EventModel em) {

        CartItem cartItem = (CartItem) em.getModel();

        String workingItemId = cartItem.getWorkingItemId();

        ……

    }

注意addCartItem中从EventModel实例中获取的ModelCartItem,这与我们在jdonframework.xml中上述定义的Model类型是统一的。

Struts-config.xml中定义是CRUD的标准定义,注意,这里只有ModelSaveAction无需ModelViewAction,因为将商品条目加入或删除购物车这个功能没有专门的显示页面:

    <action path="/shop/addItemToCart" type="com.jdon.strutsutil.ModelSaveAction"

      name="cartItemForm" scope="request"

      validate="false">

      <forward name="success" path="/cart/Cart.jsp"/>

    </action>

 

    <action path="/shop/removeItemFromCart" type="com.jdon.strutsutil.ModelSaveAction"

      name="cartItemForm" scope="request"

      validate="false">

      <forward name="success" path="/cart/Cart.jsp"/>

    </action>

注意,调用删除功能时,需要附加action参数:

/shop/removeItemFromCart.shtml?action=delete

/shop/addItemToCart.shtml是新增属性,缺省后面无需跟参数。

购物车条目批量修改功能

上面基本完成了购物车主要功能;购物车功能一个复杂性在于其显示功能和修改功能合并在一起,修改功能是指修改购物车里所有商品条目的数量。

既然有修改功能,而且这个修改功能比较特殊,我们需要设计一个独立的ActionForm,用来实现商品条目数量的批量修改。

首先设计一个ActionFormModelForm),该ModelForm主要用来实现购物车条目数量的更改,取名为CartItemsForm,其内容如下:

public class CartItemsForm extends ModelForm {

    private String[] itemId;

    private int[]quantity;

    private BigDecimal totalCost;

    …..

}

itemIdquantity设计成数组,这样,Jsp页面可以一次性提交多个itemIdquantity数值。

现在CartItemsForm已经包含前台jsp输入的数据,我们还是将其传递递交到服务层实现处理。因此建立一个Model,内容与CartItemsForm类似,这里的Model名为CartItems,实际是一个传输对象。

public class CartItems extends Model{

    private String[] itemId;

    private int[] quantity;

    private BigDecimal totalCost;

    ……

}

表现层在jdonframework.xml定义配置就无需编码,配置如下:

                   <model key=" "

                            class="com.jdon.framework.samples.jpetstore.domain.CartItems">

                            <actionForm name="cartItemsForm"/>

                            <handler>

                                     <service ref="cartService">

                                               <updateMethod name="updateCartItems"/>

                                     </service>

                            </handler>

                   </model>

上面配置中,ModelCartItemsActionFormcartItemsForm,这两个是专门为批量修改设立的。只有一个方法updateMethod。因为在这个更新功能中,没有根据主键从数据库查询Model的功能,因此,这里modelkey可以为空值。

服务层CartServiceImpupdateCartItems方法实现购物车条目数量更新:

    public void updateCartItems(EventModel em) {

        CartItems cartItems = (CartItems) em.getModel();

        try {

            String[] itemIds = cartItems.getItemId();

            int[] qtys = cartItems.getQuantity();

            int length = itemIds.length;

            for (int i = 0; i < length; i++) {

                updateCartItem(itemIds[i], qtys[i]);//逐条更新购物车中的数量

            }

        } catch (Exception ex) {

            logger.error(ex);

        }

    }

 

注意updateCartItems中从EventModel取出的是CartItems,和前面addCartItem方法中取出的是CartItem Model类型不一样,这是因为这里我们在jdonframework.xml中定义与updateCartItems相对应的ModelCartItems.

最后一步工作是Cat.jsp中加入CartItemsForm,能够在购物车显示页面有一个表单提交,客户按提交按钮,能够立即实现当前页面购物车数量的批量修改。Cat.jsp加入如下代码:

<html:form action="/shop/updateCartQuantities.shtml" method="post" >

<html:hidden property="action" value="edit" />

……

<input type="hidden" name="itemId" value="<bean:write name="cartItem" property="workingItemId"/>">

  <input type="text" size="3" name="quantity" value="<bean:write name="cartItem"

property="quantity"/>" />

…….

注意,一定要有action赋值edit这一行,这样提交给updateCartQuantities.shtml实际是ModelSaveAction时,框架才知道操作性质。

购物车总价显示功能

最后,还有一个功能需要完成,在购物车显示时,需要显示当前购物车的总价格,注意不是显示当前页面的总价格,所以无法在Cart.jsp直接实现,必须遍历购物车里所有CartItem计算总数。

该功能是购物车显示时一起实现,购物车显示是通过CartListAction实现的,这个CartListAction实际是生成一个ModelListForm,如果ModelListForm能够增加一个getTotalPrice方法就可以,因此有两种实现方式:继承ModelListForm加入自己的getTotalPrice方法;第二种无需再实现自己的ModelListFormModelListForm可以携带一个Model,通过setOneModel即可,这个方法是在ModelListAction的子类CartListAction可以override覆盖实现的,在CartListAction加入下列方法:

  protected Model setOneModel(HttpServletRequest request){

        CartService cartService = (CartService)WebAppUtil.getService("cartService", request);

        CartItems cartItems = new CartItems();

        cartItems.setTotalCost(cartService.getSubTotal());       

        return cartItems;

    }

我们使用空的CartItems作为携带价格总数的Model,然后在Cart.jsp中再取出来显示:

<bean:define id="cartItems " name="listForm" property="oneModel" />

<b>Sub Total: <bean:write name="cartItems" property="subTotal" format="$#,##0.00" />

将当前页面listForm中属性oneModel定义为cartItems,它实际是我们定义的CartItems

下一行取出总价即可。

用户喜欢商品列表功能

在显示购物车时,需要一起显示该用户喜欢的商品列表,很显然这是一个批量分页查询实现,但是它有些特殊,它首先显示的第一页不是由URL调用的,而是嵌入在购物车显示中,那么只能在购物车显示页面的ModellistForm中做文章。

在上节中,在CartListActionsetOneModel方法中,使用CartItems作为价格总数的载体,现在恐怕我们也要将之作为本功能实现载体。

还有一种实现载体,就是其他scopesessionActionFormAccountForm很适合做这样的载体,而且和本功能意义非常吻合,所以在AccountForm/Account中增加一个myList字段,在myList字段中,放置的是该用户喜欢的商品Product集合,注意不必放置Product的主键集合,因为我们只要显示用户喜欢商品的第一页,这一页是嵌入购物车显示页面中,所以第一页显示的个数是由程序员可事先在程序中定义。

这样在Account获得时,一起将myList集合值获得。

订单模块实现

我们还是从域模型开始,Order是订单模块的核心实体,其内容可以确定如下:

public class Order extends Model {

 

  /* Private Fields */

 

  private int orderId;

  private String username;

  private Date orderDate;

  private String shipAddress1;

  private String shipAddress2;

  …..

}

第二步,建立与Model对应的ModelForm,我们可以称之为边界模型,代码从Order拷贝过来即可。当然OrderForm还有一些特殊的字段以及初始化:

public class OrderForm extends ModelForm

private boolean shippingAddressRequired;

    private boolean confirmed;

    static {

        List cardList = new ArrayList();

        cardList.add("Visa");

        cardList.add("MasterCard");

        cardList.add("American Express");

        CARD_TYPE_LIST = Collections.unmodifiableList(cardList);

      }   

   

    public OrderForm(){

        this.shippingAddressRequired = false;

        this.confirmed = false;       

    }

   …..

}

第三步,建立Order Model的业务服务接口,如下:

public interface OrderService {

       void insertOrder(Order order);

       Order getOrder(int orderId);

       List getOrdersByUsername(String username);

}

第四步,实现OrderServicePOJO子类:OrderServiceImp

第五步,表现层实现,本步骤可和第四步同时进行。

OrderService中有订单的插入创建功能,我们使用Jdon框架的CRUDcreate配置实现,配置struts-config.xmljdonframework.xml

      <form-bean name="orderForm"

type="com.jdon.framework.samples.jpetstore.presentation.form.OrderForm"/>

 

         <model key="orderId"

                            class="com.jdon.framework.samples.jpetstore.domain.Order">

                            <actionForm name="orderForm"/>

                            <handler>

                                     <service ref="orderService">

                                               <createMethod name="insertOrder"/>

                                     </service>

                            </handler>

         </model>

第六步:根据逐个实现界面功能,订单的第一个功能创建一个新的订单,在新订单页面NewOrderForm.jsp推出之前,这个页面的ActionForm已经被初始化,是根据购物车等Cart其他Model数据初始化合成的。

新订单页面初始化

根据Jdon框架中CRUD功能实现,初始化一个ActionForm有两种方法:一继承ModelHandler实现initForm方法;第二通过jdonframework.xmlinitMethod方法配置。

这两个方案选择依据是根据用来初始化的数据来源什么地方。

订单表单初始化实际是来自购物车信息或用户账号信息,这两个都事先保存在HttpSession中,购物车信息是通过有态CartService实现的,所以这些数据来源是和request相关,那么我们选择第一个方案。

继承ModelHandler之前,我们可以考虑首先继承ModelHandler的子类XmlModelHandler,只要继承initForm一个方法即可,这样节省其他方法编写实现。

public class OrderHandler extends XmlModelHandler {

   

    public ModelForm initForm(HttpServletRequest request) throws

    Exception{

        HttpSession session = request.getSession();

        AccountForm accountForm = (AccountForm) session.getAttribute("accountForm");

        OrderForm orderForm = createOrderForm(accountForm);

        CartService cartService = (CartService)WebAppUtil.getService("cartService", request);

       

        orderForm.setTotalPrice(cartService.getSubTotal());

 

        //below can read from the user's creditCard service;

        orderForm.setCreditCard("999 9999 9999 9999");

        orderForm.setExpiryDate("12/03");

        orderForm.setCardType("Visa");

        orderForm.setCourier("UPS");

        orderForm.setLocale("CA");

        orderForm.setStatus("P");

 

        Iterator i = cartService.getAllCartItems().iterator();

        while (i.hasNext()) {

          CartItem cartItem = (CartItem) i.next();

          orderForm.addLineItem(cartItem);

        }

        return orderForm;        

    }

   

    private OrderForm createOrderForm(AccountForm account){

      ……

    }

}

ModelHandlerinitForm继承后,因为这使用了JdonCRUD功能实现,这里我们只使用到CRUD中的创建功能,因此,findModelBykey方法就无需实现,或者可以在jdonframework.xml中配置该方法实现。

考虑到在initForm执行后,需要推出一个NewOrderForm.jsp页面,这是一个新增性质的页面。所以在struts-config.xml

                  <action path="/shop/newOrderForm" type="com.jdon.strutsutil.ModelViewAction"

      name="orderForm" scope="request"      validate="false">

        <forward name="create" path="/order/NewOrderForm.jsp"/>

    </action>      

订单确认流程

新的订单页面推出后,用户需要经过两个流程才能确认保存,这两个流程是填写送货地址以及再次完整确认。这两个流程实现的动作非常简单,就是将OrderForm中的shippingAddressRequired字段和confirm字段赋值,相当于简单的开关,这是一个很简单的动作,可以有两种方案:直接在jsp表单中将这两个值赋值;直接使用strutsAction实现。后者需要编码,而且不是非有这个必要,只有第一个方案行不通时才被迫实现。

第一步:填写送货地址

使用Jdon框架的推出纯Jsp功能的Action配置struts-config.xml

    <action path="/shop/shippingForm" type="com.jdon.strutsutil.ForwardAction"

      name="orderForm" scope="session"      validate="false">

      <forward name="forward" path="/order/ShippingForm.jsp"/>         

    </action>

这是实现送货地址页面的填写,使用的还是OrderForm。更改前面流程NewOrderForm.jsp中的表单提交action值为本action path: shippingForm.shtml

<html:form action="/shop/shippingForm.shtml" styleId="orderForm" method="post" >

  ……

</html:form>

ShippingForm.jsp中增加将shippingAddressRequired赋值的字段:

<html:hidden name="orderForm"  property="shippingAddressRequired" value="false"/>

第二步:确认订单

类似上述步骤,配置struts-config.xml

   <action path="/shop/confirmOrderForm" type="com.jdon.strutsutil. ForwardAction"

      name="orderForm" scope="session"  validate="false">

      <forward name="forward" path="/order/ConfirmOrder.jsp"/>          

   </action>        

将上一步ShippingForm.jsp的表单action改为本actionpath: confirmOrderForm.shtml

<html:form action="/shop/confirmOrderForm.shtml" styleId="orderBean" method="post" >

修改ConfirmOrder.jsp中提交的表单为最后一步,保存订单newOrder.shtml

<html:link page="/shop/newOrder.shtml?confirmed=true"><img border="0" src="../images/button_continue.gif" /></html:link>

第三步:下面是创建数据保存功能实现:

    <action path="/shop/newOrder" type="com.jdon.strutsutil.ModelSaveAction"

      name="orderForm" scope="session"

      validate="true" input="/order/NewOrderForm.jsp">

      <forward name="success" path="/order/ViewOrder.jsp"/>

    </action>

ModelSaveAction是委托ModelHandler实现的,这里有两种方式:配置方式:在jdonframework.xml中配置了方法插入;第二种是实现代码,这里原本可以使用配置方式实现,但是因为在功能上有要求:在订单保存后,需要清除购物车数据,因此只能使用代码实现方式,在ModelHandler中实现子类方法serviceAction

public void serviceAction(EventModel em, HttpServletRequest request) throws java.lang.Exception {

   try {

      CartService cartService = (CartService) WebAppUtil.getService("cartService", request);

cartService.clear(); //清楚购物车数据

 

      OrderService orderService = (OrderService) WebAppUtil.getEJBService("orderService", request);

      switch (em.getActionType()) {

            case Event.CREATE:

                Order order = (Order) em.getModel();

                orderService.insertOrder(order);

                cartService.clear();

                break;

        case Event.EDIT:

                break;

        case Event.DELETE:

                break;

       }

   } catch (Exception ex) {

            throw new Exception(" serviceAction Error:" + ex);

   }

}

用户订单列表

用户查询自己的订单列表功能很明显可以使用Jdon框架的批量查询事先。

struts-config.xml中配置ModelListForm如下:

      <form-bean name="listForm" type="com.jdon.strutsutil.ModelListForm"/>

建立继承ModelListAction子类OrderListAction

public class OrderListAction extends ModelListAction {

 

    public PageIterator getPageIterator(HttpServletRequest request, int start, int count) {

        OrderService orderService = (OrderService) WebAppUtil.getService("orderService", request);

        HttpSession session = request.getSession();

        AccountForm accountForm = (AccountForm) session.getAttribute("accountForm");

        if (accountForm == null) return new PageIterator();

        return orderService.getOrdersByUsername(accountForm.getUsername(), start, count);

    }

 

    public Model findModelByKey(HttpServletRequest request, Object key) {

        OrderService orderService = (OrderService) WebAppUtil.getService("orderService", request);

        return orderService.getOrder((Integer)key);

    }

 

}

修改OrderService, 将获得Order集合方法改为:

public class OrderService{

 

PageIterator getOrdersByUsername(String username, int start, int count)

….

}

根据Jdon批量查询要求,使用iBatis实现返回ID集合以及符合条件的总数。

最后编写ListOrders.jsp,两个语法:logic:iterator MultiPages

配置jdon框架启动

目前我们有四个struts-config.xml,前面每个模块一个配置:

/WEB-INF/struts-config.xml 主配置

/WEB-INF/struts-config-catalog.xml  商品相关配置

/WEB-INF/struts-config-security.xml 用户相关配置

/WEB-INF/struts-config-cart.xml 购物车相关配置

/WEB-INF/struts-config-order.xml 订单相关配置

本项目只有一个jdonframework.xml,当然我们也可以创建多个jdonframework.xml,然后在其struts-config.xml中配置。

  <plug-in className="com.jdon.strutsutil.InitPlugIn">

    <set-property property="modelmapping-config"  value="jdonframework_iBATIS.xml" />

  </plug-in>

修改iBatisDAO配置

iBatis 4.0.5中原来的配置过于扩张(从持久层扩张到业务层),AccountDao每个实例获得都需要通过daoManager.getDao这样形式,而使用Jdon框架后,AccountDaoDAO实例获得无需特别语句,我们只要在AccountService直接引用AccountDao接口,至于AccountDao的具体实例,通过Ioc注射进入即可。

因此,在jdonframework.xml中有如下配置:

         <pojoService name="accountDao"

                   class="com.jdon.framework.samples.jpetstore.persistence.dao.sqlmapdao.AccountSqlMapDao"/>

         <pojoService name="accountService"

                            class="com.jdon.framework.samples.jpetstore.service.bo.AccountServiceImp"/>

         <pojoService name="productManager"

                            class="com.jdon.framework.samples.jpetstore.service.bo.ProductManagerImp"/>

AccountServiceImp代码如下:

public class AccountServiceImp implements AccountService, Poolable {

    private AccountDao accountDao;

    private ProductManager productManager;

   

    public AccountServiceImp(AccountDao accountDao,

                             ProductManager productManager){

        this.accountDao = accountDao;

        this.productManager = productManager;

    }

 

AccountServiceImp需要两个构造方法实例,这两个中有一个是AccountDao

按照iBatis原来的AccountDao子类AccountSqlMapDao有一个构造方法参数是DaoManager,但是我们无法生成自己的DaoManager实例,因为DaoManager是由dao.xml配置文件读取后生成的,这是一个动态实例;那只有更改AccountSqlMapDao构造方法了。

根源在于BaseSqlMapDao类,BaseSqlMapDao是一个类似JDBC模板类,每个Dao都继承它,现在我们修改BaseSqlMapDao如下:

public class BaseSqlMapDao extends DaoTemplate implements SqlMapExecutor{

    …..

}

BaseSqlMapDaoXML配置和JDBC模板的结合体,在这个类中,这两者搭配在一起,在其中实现SqlMapExecutor各个子方法。

我们再创建一个DaoManagerFactory,专门根据配置文件创建DaoManager实例:

主要方法如下:

Reader reader = Resources.getResourceAsReader(daoResource);

daoManager = DaoManagerBuilder.buildDaoManager(reader);

其中daoResourcedao.xml配置文件,这个配置是在jdonframework.xml中配置:

<pojoService name="daoManagerFactory"

                            class="com.jdon.framework.samples.jpetstore.persistence.dao.DaoManagerFactory">

                            <constructor 

value="com/jdon/framework/samples/jpetstore/persistence/dao/sqlmapdao/sql/dao.xml"/>

         </pojoService>

这样,我们可以通过改变jdonframework.xml配置改变dao.xml配置。

Dao.xml配置如下:

<daoConfig>

  <context>

    <transactionManager type="SQLMAP">

      <property name="SqlMapConfigResource"       

value="com/jdon/framework/samples/jpetstore/persistence/dao/sqlmapdao/sql/sql-map-config.xml"/>

    </transactionManager>

 

    <dao interface="com.ibatis.sqlmap.client.SqlMapExecutor"  

implementation="com.jdon.framework.samples.jpetstore.persistence.dao.sqlmapdao.BaseSqlMapDao"/>

 

  </context>

</daoConfig>

dao.xml中,我们只配置一个JDBC模板,而不是将所有的如AccountDao配置其中,因为我们需要iBatis只是它的JDBC模板,实现持久层方便的持久化,仅此而已!

DaoManagerFactory为我们生产了DaoManager实例,那么如何赋值到BaseSqlMapDao中,我们设计一个创建BaseSqlMapDao工厂如下:

 

public class SqlMapDaoTemplateFactory {

 

    private DaoManagerFactory daoManagerFactory;

 

    public SqlMapDaoTemplateFactory(DaoManagerFactory daoManagerFactory) {

        this.daoManagerFactory = daoManagerFactory;

    }

   

    public SqlMapExecutor getSqlMapDaoTemp(){

        DaoManager daoManager = daoManagerFactory.getDaomanager();

        return (SqlMapExecutor)daoManager.getDao(SqlMapExecutor.class);

    }

 

}

通过getSqlMapDaoTemp方法,由DaoManager.getDao方法获得BaseSqlMapDao实例,BaseSqlMapDao的接口是SqlMapExecutor,这样我们通过DaoManager获得一个JDBC模板SqlMapExecutor的实例。

这样,在AccountDao各个子类AccountSqlMapDao中,我们只要通过SqlMapDaoTemplateFactory获得SqlMapExecutor实例,委托SqlMapExecutor实现JDBC操作,如下:

public class AccountSqlMapDao implements AccountDao {

  //iBatis数据库操作模板

  private SqlMapExecutor sqlMapDaoTemplate;

    //构造方法

  public AccountSqlMapDao(SqlMapDaoTemplateFactory sqlMapDaoTemplateFactory) {

      sqlMapDaoTemplate = sqlMapDaoTemplateFactory.getSqlMapDaoTemp();

  }

 //查询数据库

  public Account getAccount(String username) throws SQLException{

    return (Account)sqlMapDaoTemplate.queryForObject("getAccountByUsername", username);

  }

 

 

部署调试

当在JBossTomcat控制台 或者日志文件中出现下面字样标识Jdon框架安装启动成功:

<========  Jdon Framework started successfully! =========>

Jdon框架启动成功后,以后出现的错误基本是粗心大意的问题,仔细分析会很快找到原因,相反,如果编程时仔细慢一点,则后面错误出现概率很小。

使用Jdon框架开发Jpetstore 一次性调试通过率高,一般问题都是存在数据库访问是否正常,一旦正常,主要页面就出来了,其中常见问题是jsp页面和ActionForm的字段不对应,如jsp页面显示如下错误:

No getter method available for property creditCardTypes for bean under name orderForm

表示在OrderForm中没有字段creditCardTypes,或者有此字段,但是大小写错误等粗心问题。

如果jsp页面或后台log记录显示:

System error! please call system Admin.java.lang.Exception

一般这是由于前面出错导致,根据记录向前搜索,搜索到第一个出错记录:

2005-07-07 11:55:16,671 [http-8080-Processor25] DEBUG com.jdon.container.pico.PicoContainerWrapper - getComponentClass: name=orderService

2005-07-07 11:55:16,671 [http-8080-Processor25] ERROR com.jdon.aop.reflection.MethodConstructor -  no this method name:insertOrder

第一个出错是在MethodConstructor报错,没有insertOrder方法,根据上面一行是orderService,那么检查orderService代码看看有无insertOrder

public interface OrderService {

       void insertOrder(Order order);

       Order getOrder(int orderId);

       List getOrdersByUsername(String username);

}

OrderService接口中是有insertOrder方法,那么为什么报错没有呢?仔细检查一下,是不是insertOrder的方法参数有问题,哦, 因为orderService的调用是通过jdonframework.xml下面配置进行的:

         <model key="orderId"

                            class="com.jdon.framework.samples.jpetstore.domain.Order">

                            <actionForm name="orderForm"/>

                            <handler>

                                     <service ref="orderService">

                                               <createMethod name="insertOrder"/>

                                     </service>

                            </handler>

                   </model>

而根据Jdon框架要求,使用配置实现ModelHandler,则要求OrderServiceinsertOrder方法参数必须是EventModel,更改OrderServiceinsertOrder方法如下:

public interface OrderService {

    void insertOrder(EventModel em);

}

同时,修改OrderService的子类代码OrderServiceImp

  public void insertOrder(EventModel em) {

        Order order = (Order)em.getModel();

        try{

            orderDao.insertOrder(order);

        }catch(Exception daoe){

                Debug.logError(" Dao error : " + daoe, module);

                em.setErrors("db.error");

        }

    }

 

注意em.setErrors方法,该方法可向struts页面显示出错信息,db.error是在本项目的application.properties中配置的。

关于本次页面出错问题,还有更深缘由,因为我们在jdonframework.xml中中定义了createMethod,而根据前面已经有ModelHandler子类代码OrderHandler实现,所以,这里不用配置实现,jdonframework.xml的配置应该如下:

                   <model key="orderId"

                            class="com.jdon.framework.samples.jpetstore.domain.Order">

                            <actionForm name="orderForm"/>

                            <handler class="com.jdon.framework.samples.jpetstore.presentation.action.OrderHandler"/>

                   </model>

直接定义了hanlderclassOrderHandler,在OrderHandler中的serviceAction我们使用代码调用了OrderServiceinsertOrder方法,如果使用这样代码调用,无需要求OrderServiceinsertOrder的参数是EventModel了。

 

 

总结

Jpetstore整个开发大部分基于Jdon框架开发,特别是表现层,很少直接接触使用struts原来功能,Jdon框架的表现层架构基本让程序员远离了struts的烦琐开发过程,又保证了strutsMVC实现。

Jpetstore中只有SignonAction这个类是直接继承strutsDispatchAction,这个功能如果使用基于J2EE容器的安全认证实现(见JdonNews),那么Jpetstore全部没有用到strutsAction,无需编写Action代码;ActionForm又都是Model的拷贝,ActionActionFormstruts编码的两个主要部分,这两个部分被Jdon框架节省后,整个J2EEWeb层开发方便快速,而且容易得多。

这说明Jdon框架确实是一款快速开发J2EE工具,而且是非常轻量的。

纵观Jpetstore系统,主要有三个层的配置文件组成,持久层由iBatisProduct.xml等配置文件组成;服务层由jdon框架的jdonframework.xml组成;表现层由strutsstruts-config.xmljdonframework.xml组成;剩余代码基本是Model之类实现。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多