Struts和Hibernate之间搭起桥梁

 

StrutsHibernate之间搭起桥梁

 

1.        摘要

  Hibernatestruts是当前市面上几个最流行的开源的库之一。它们很有效率,是程序员在开发Java企业应用,挑选几个竞争的库的首选。虽然它们经常被一起应用,但是Hibernate的设计目标并不是和Struts一起使用,而StrutsHibernate诞生好多年之前就发布了。为了让它们在一起工作,仍然有很多挑战。这篇文章点明了StrutsHibernate之间的一些鸿沟,尤其关系到面向对象建模方面。文章也描述了如何在两者间搭起桥梁,给出了一个基于扩展Struts的解决方案。所有的基于StrutsHibernate构建的Web应用都能从这个通用的扩展中获益。

  在Hibernate in ActionManning2004十月)这本书里,作者Christian BauerGavin King揭示了面向对象世界的模型和关系数据模型,两个世界的范例是不一致的。Hibernate非常成功地在存储层(persistence Layer)将两者粘合在一起。但是领域模型(domain model)(也就是Model-View-Controllermodel layer)和HTML页面(MVCView Layer)仍然存在不一致。在这篇文章中,我们将检查这种不一致,并且探索解决的方案。

 

2.        范例不一致的再发现

  让我们先看一个经典的parent-child关系例子(看下面的代码):productcategory.Category类定义了一个类型为long的标示符id和一个类型为String的属性name.Product类也有一个类型为long的标示符id和一个类型为Category的属性category,表示了多对一的关系(也就是说很多product可以属于一个Category

/*** @hibernate.class table="CATEGORY"*/

public class Category {

private Long id  

private String name  

/**    * @hibernate.id generator-class="native" column="CATEGORY_ID"    */

public Long getId() {

      return id;

}  

public void setIdLong id

     this.id = id  

}  

/**    * @hibernate.property column="NAME"    */  

public String getName() {

      return name  

}

public void setNameLong name {

      this.name = name  

}

}

/*** @hibernate.class table="PRODUCT"*/

public class Product {  

private Long id

 private Category category  

/**    * @hibernate.id generator-class="native" column="PRODUCT_ID"    */  

public Long getId() {

      return id  

}  

public void setIdLong id {

      this.id = id  

}  

 

/**   

* @hibernate.many-to-one   

* column="CATEGORY_ID"   

* class="Category"   

* cascade="none"   

* not-null="false"    */  

public Category getCategory() {

      return category  

}  

public void setCategoryCategory category {

      this.category = category  

}

}

 

我们希望一个product可以被更改category,所以我们的HTML提供了一个下拉框列出所有Category.

   <select name="categoryId"> 

        <option value="">No Category</option>

<option value="1">Category 1</option>

<option value="2">Category 2</option>  

<option value="3">Category 3</option>

</select>

   这里我们看出了两者的不一致:在Product领域对象里,category属性是Category类型,但是ProductForm只有一个类型为longcategoryId.这种不匹配不但增加了不一致,而且导致了不必要的代码进行primitive type的标示符和对应的对象之间的转换。

  这种不一致部分是由于HTML Form自己引起的:它只代表了一种关系模型,不能代表面向对象的模型。面向对象和关系模型的不一致在存储层由对象关系映射(O/RM)解决。但是类似的问题在表示层(view layer)仍然存在。解决的关键是让他们一起无缝地工作。

 

3.        Struts的功能和局限

  幸运的是,Struts能够生成和解释内嵌的对象属性。Category下拉框可以用Struts page-constructionhtml tag library

<htmlselect property="category.id"> 

<option value="">No Category</option>  

<htmloptions collection="categories" property="id" labelProperty="name"/>

</htmlselect>

  我们假设categoriesCategory对象的一个list.所以现在我们要修改ProductForm,让它变得更加面向对象,我们要修改ProductFormcategoryId,改成类型为Categorycategory.这种改变会导致在ProductProductForm之间复制属性的工作更加繁琐,因为两者有相同的属性。

public class ProductForm extends ActionForm {    

private Long id    

private Category category     ……

}

  当我们完成剩余的Struts Action configuration validator jsp hibernate层后,开始测试,我们马上在访问ProductForm.category.id时遇到了NullPointerException.这是预料中的!因为ProductForm.category还没有被设置,同时,Hibernate也会将多对一所联系的对象引用设为空(如果database field为空指)(译者:这里指Hiberateproduct.categoryNull,如果该Product没有联系到任何category)。Struts要求所有的对象在显示(生成HTML Form)和传播(提交HTML FORM)之前被建立。

  让我们看看如何用ActionForm.reset()来架起桥梁。

  (并非如此)臭名昭著的Struts ActionForm

  在我第一个星期接触Struts的时候,我最大的一个疑问就是:为什么我必须为Properties getter方法,setter方法保持几乎完全相同的两份copy,一份在ActionForm Bean,一份在DomainObject.这个繁琐的步骤成了Struts社区最主要的抱怨之一。

以我的观点,ActionForm存在有原因的。首先,它们可以区别于Domain Object因为他们但当了不同的角色。在MVC模式下,Domain ObjectModel层的一个部分,ActionFormView层的。因为WebpageFieldDatabaseField可能不一样,某些特制的转换是常见的。第二,ActionForm.validate()方法可以定义非常好用的验证规则。第三,可能有其他的,特定的View行为,但是又不想在domain layer实现,特别当persistence framework来管理domain object的时候。

 

4.        提交Form

  让我们来用ActionForm内有的方法-reset()-来解决viewmodel之间的不一致。这个reset()方法是在ActionForm在被Struts Controller Servlet处理request时候复制ActionForm属性之前调用的。这个方法最常见的使用是:checkbox必须被显式地设为false,让没有被选中的checkbox被正确识别。Reset()也是一个初始化用于view rending对象的合适地方。代码看起来是这样的:

public class ProductForm extends ActionForm {    

private Long id    

private Category category    

……    

public void resetActionMapping mapping HttpServletRequest request      {

        super.reset mapping request );

        if category == null {

 category = new Category();

}

}

}

Struts在使用用户提交的值填写ProductForm之前,Struts会调用reset(),这样category属性将会被初始化。请注意,你必须检查category看它是不是null,后面我们会讨论这个。

 

 

5.     编辑Form

  到目前为止,我们已经解决了form提交时候的问题。但是当我们在生成form页面的时候呢?Htmlselect tag也希望有一个非空的引用,所以我们将在form生成页面之前调用reset()。我们在action类里加入了一行:

 

public class EditProductAction extends Action {

     public final ActionForward execute(ActionMapping mappingActionForm form

HttpServletRequest requestHttpServletResponse response) throws Exception {

             ……        Product product = createOrLoadProduct(); 

ProductForm productForm = ProductFormform       

PropertyUtils.copyProperties productForm product );

productForm.reset mapping request );        ……    

}

}

 

  我假设读者已经对action类和Jakarta commons Beanutils包非常熟悉了。CreateOrLoadProduct()建立了一个新的Product实例或者从数据库里载入一个已有的实例,具体取决于这个action是建立或者修改Product的。ProductForm被赋值后(译者:也就是调用PropertyUtils.copyProperties后),productForm.category已经从Product.category复制过来了(译者:实际上只是复制了category对象引用,并没有开销),然后,ProductForm就能用来生成页面了。我们同时也必须保证:不覆盖已经被Hibernate载入的对象,所以我们必须检查(category)是不是为null.

  因为reset()方法是在ActionForm中定义的,我们可以把上述代码放入一个superclass,比如CommonEditAction,来处理这些事情:

Product product = createOrLoadProduct();       

PropertyUtils.copyProperties form product );       

form.reset mapping request );

  如果你需要一个只读的Form 你有两个选择: 第一检查所联系的jsp对象是不是null 第二复制domain对象到ActionForm之后调用Reset()

 

6. 保存domain对象

我们解决了提交Form和生成Form页面的问题, 所以Struts可以满足了。但是Hibernate呢?当用户选择了一个null ID option ?C 在我们的例子中“no category”option- 并且提交form productForm.category指向一个新建立的hibernate对象,idnull.category属性从ProductForm复制到Hibernate控制的Product对象并且存储时,Hibernate会抱怨product.category是一个临时对象,需要在Product存储前先被存储。当然,我们知道它是Null,并且不需要被存储。所以我们需要将product.category置为Null,然后Hibernate就能存储Product了(译者:在这种情况下,数据库product.category被设成空值)。我们也不希望改变Hibernate的工作方式,所以我们选择在复制到Domain对象之前清理这些临时对象,我们在ProductForm中加了一个方法:

public class ProductForm extends ActionForm {

     private Long id    

private Category category

   ……    

public void resetActionMapping mapping HttpServletRequest request

       super.reset mapping request );       

if category == null {

category = new Category();

}    

}    

public void cleanupEmptyObjects() {

        if category.getId() == null {

             category = null

}    

}

}

  

我们在copyProperties之前清理掉这些临时对象,所以如果ProductForm.category只是用来放Null的,则将ProductForm.category置为Null.然后Domain对象的category也会被设成null

public class SaveProductAction extends Action {

     public final ActionForward executeActionMapping mappingActionForm form

HttpServletRequest requestHttpServletResponse response) throws Exception  {

             ……       

Product product = new Product();      

             ((ProductFormform.cleanupEmptyObjects();

       PropertyUtils.copyProperties product form );     

SaveProduct product );       

……    

}

}

 

7.        一对多关系

我还没有解决CategoryProduct的一对多关系。我们把它加入到CategoryMetadata中:

public class Category { 

    ……   

   private Set products    

……    

/**     

* @hibernate.set     

* table="PRODUCT"     

* lazy="true"     

* outer-join="auto"     

* inverse="true"     

* cascade="all-delete-orphan"     

* @hibernate.collection-key     

* column="CATEGORY_ID"      *      

* @hibernate.collection-one-to-many      * class="Product"     

*/    

public Set getProducts() {

        return products    

}    

public void setProductsSet products {

        this.products = products    

}

}

  注意:Hibernatecascade属性为all-delete-orphan表明:Hibernate需要在存储包含的Category对象时候,自动存储Product对象。和parent对象一起存储child对象的情况并不常见,常见的是:分别控制child的存储和parent的存储。在我们的例子中,我们可以容易地做到这一点,如果我们允许用户在同一个html page编辑CategoryProductS.set表示Products是非常直观的:

public class CategoryForm extends ActionForm {

     private Set productForms

     ……    

public void resetActionMapping mapping HttpServletRequest request {

        super.reset mapping request );       

for int i = 0 i < MAX_PRODUCT_NUM_ON_PAGE i++ {

           ProductForm productForm = new ProductForm();          

productForm.reset mapping request );

    productForms.add productForm );       

}    

}    

 

public void cleanupEmptyObjects() {

        for Iterator i = productForms.iterator();i.hasNext(); {

             ProductForm productForm = ProductForm i.next();

productForm.cleanupEmptyObjects();

}    

}

}

  更进一步我们已经可以察看,编辑,提交forms,并且存储相关的objects,但是为所有的ActionForm类定义CleanupEmptyObjects()和reset()方法是个累赘。我们将用一个抽象的ActionForm来完成协助完成这些工作。

  作为通用的实现,我们必须遍历所有的Hibernate管理的domain对象,发现他们的identifier,并且测试id值。幸运的是:org.hibernate.metadata包已经有两个Utility类能取出domain对象的元数据。我们用ClassMetadata类检查这个object是不是Hibernate管理的。如果是:我们把它们的id Value取出来。我们用了Jakarta Commons Beanutils包来协助JavaBean元数据的操作。

import java.beans.PropertyDescriptor

import org.apache.commons.beanutils.PropertyUtils

import org.hibernate.metadata.ClassMetadata

 

public abstract class AbstractForm extends ActionForm {  

public void resetActionMapping mapping HttpServletRequest request

 

     super.reset mapping request );

    // Get PropertyDescriptor of all bean properties  

 

PropertyDescriptor descriptors[] =PropertyUtils.getPropertyDescriptors this );     

for int i = 0 i < descriptors.length i++ {        

Class propClass = descriptors[i].getPropertyType();

ClassMetadata classMetadata = HibernateUtil.getSessionFactory().getClassMetadata propClass );

 

if classMetadata = null {  

// This is a Hibernate object           

String propName = descriptors[i].getName();

 

 Object propValue = PropertyUtils.getProperty this propName );           

// Evaluate property create new instance if it is null   

 

if (propValue == null){

             PropertyUtils.setPropertythispropNamepropClass.newInstance());

}

}

}

}  

 

public void cleanupEmptyObjects() {

                    // Get PropertyDescriptor of all bean properties     

PropertyDescriptor descriptors[] =  PropertyUtils.getPropertyDescriptors this );     

for int i = 0 i < descriptors.length i++ {

              Class propClass = descriptors[i].getPropertyType();        

ClassMetadata classMetadata = HibernateUtil.getSessionFactory().getClassMetadatapropClass );

if classMetadata = null {  

// This is a Hibernate object           

Serializable id = classMetadata.getIdentifier this EntityMode.POJO );           

 

// If the object id has not been set release the object.           

// Define application specific rules of not-set id here           

// e.g. id == null id == 0 etc.           

if id == null {

                    String propName = descriptors[i].getName();

       PropertyUtils.setProperty this propName null );           

}

}

      }

}

}

  为了让代码可读,我们省略了Exception的处理代码。

我们的新AbstractForm类从StrutsActionForm类继承,并且提供了通用行为:resetcleanup多对一关联对象。当这个关系是相反的话(也就是一对多关系),那么每个例子将会有所不同,类似在Abstract类里实现是比较好的办法。

 

8.        总结

  StrutsHibernate是非常流行和强大的框架,他们可以有效地相互合作,并且弥补domain模型和MVC视图(view)之间的差别。这篇文章讨论一个解决Struts/Hibernate Project的通用的方案,并且不需要大量修改已经有的代码。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
资源包主要包含以下内容: ASP项目源码:每个资源包中都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目中都附带了完整的数据库设计文件。这些文件通常包括数据库结构图、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程图、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包中都包含项目的演示文件和使用指南。演示文件通常以视频或图文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
资源包主要包含以下内容: ASP项目源码:每个资源包中都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目中都附带了完整的数据库设计文件。这些文件通常包括数据库结构图、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程图、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包中都包含项目的演示文件和使用指南。演示文件通常以视频或图文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值