选择显示字体大小

在struts和hibernate之间搭起桥梁

struts扩展到面向对象的hibernate

译者按
在看这篇文章之前: 如果你还不清楚hibernate的or/m工作机制, hibernate注释,以及一对多,多对一的机制,请先看hibernate文档
如果你不清楚struts的mvc,请先看struts文档,
你也要大概了解javabean和jakarta commons beanutil是干什么的)

版权声明:任何获得matrix授权的网站,转载时请务必保留以下作者信息和链接
作者:ted he;alilo(作者的blog:http://blog.matrix.org.cn/page/alilo)
原文:http://www.matrix.org.cn/resource/article/44/44391_struts+hibernate.html
关键字:struts;hibernate

摘要

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

hibernate in action(manning,2004十月)这本书里,作者christian bauer和gavin king揭示了面向对象世界的模型和关系数据模型,两个世界的范例是不一致的。hibernate非常成功地在存储层(persistence layer)将两者粘合在一起。但是领域模型(domain model)(也就是model-view-controller的model layer)和html页面(mvc的view layer)仍然存在不一致。在这篇文章中,我们将检查这种不一致,并且探索解决的方案。

范例不一致的再发现

让我们先看一个经典的parent-child关系例子(看下面的代码):product和category。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 setid(long id) {
      this.id = id;
   }

   /**
    * @hibernate.property column="name"
    */
   public string getname() {
      return name;
   }

   public void setname(long 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 setid(long 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 setcategory(category category) {
      this.category = category;
   }
}



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

<select name=&quot;categoryid&quot;>
   <option value=&quot;&quot;>no category</option>
   <option value=&quot;1&quot;>category 1</option>
   <option value=&quot;2&quot;>category 2</option>
   <option value=&quot;3&quot;>category 3</option>
</select>



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

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

struts的功能和局限

幸运的是,struts能够生成和解释内嵌的对象属性。category下拉框可以用struts page-construction(html) tag library:

<html:select property=&quot;category.id&quot;>
   <option value=&quot;&quot;>no category</option>
   <html:options collection=&quot;categories&quot; property=&quot;id&quot; labelproperty=&quot;name&quot;/>
</html:select>


我们假设categories是category对象的一个list。所以现在我们要修改productform,让它变得更加&ldquo;面向对象&rdquo;,我们要修改productform的categoryid,改成类型为category的category。这种改变会导致在product和productform之间复制属性的工作更加繁琐,因为两者有相同的属性。

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为空指)(译者:这里指hiberate将product.category为null,如果该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 object是model层的一个部分,actionform是view层的。因为webpage的field和database的field可能不一样,某些特制的转换是常见的。第二,actionform.validate()方法可以定义非常好用的验证规则。第三,可能有其他的,特定的view行为,但是又不想在domain layer实现,特别当persistence framework来管理domain object的时候。

提交form

让我们来用actionform内有的方法-reset()-来解决view和model之间的不一致。这个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 reset(actionmapping mapping, httpservletrequest request)
     {
        super.reset( mapping, request );
        if ( category == null ) { category = new category(); }
     }
}



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

编辑form

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

public class editproductaction extends action {
     public final actionforward execute( actionmapping mapping, actionform form,
        httpservletrequest request, httpservletresponse response ) throws exception
     {
        ...
        product product = createorloadproduct();
        productform productform = (productform)form;
        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()

保存domain对象

我们解决了提交form和生成form页面的问题, 所以struts可以满足了。但是hibernate呢?当用户选择了一个null id option &ndash; 在我们的例子中&ldquo;no category&rdquo;option- 并且提交form, productform.category指向一个新建立的hibernate对象,id为null。当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 reset(actionmapping 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 execute( actionmapping mapping, actionform form,
        httpservletrequest request, httpservletresponse response ) throws exception
     {
        ...
        product product = new product();
        ((productform)form).cleanupemptyobjects();
        propertyutils.copyproperties( product, form );
        saveproduct( product );
        ...
     }
}



一对多关系

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

public class category {
     ...
     private set products;
     ...

     /**
      * @hibernate.set
      * table=&quot;product&quot;
      * lazy=&quot;true&quot;
      * outer-join=&quot;auto&quot;
      * inverse=&quot;true&quot;
      * cascade=&quot;all-delete-orphan&quot;
      *
      * @hibernate.collection-key
      * column=&quot;category_id&quot;
      *
      * @hibernate.collection-one-to-many
      * class=&quot;product&quot;
      */
     public set getproducts() {
        return products;
     }

     public void setproducts(set products) {
        this.products = products;
     }
}


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

public class categoryform extends actionform {
     private set productforms;
     ...
     public void reset(actionmapping 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 reset(actionmapping mapping, httpservletrequest request) {
      super.reset( mapping, request );

      // get propertydescriptor of all bean properties
      propertydescriptor descriptors&#91;&#93; =
         propertyutils.getpropertydescriptors( this );

      for ( int i = 0; i < descriptors.length; i++ ) {
         class propclass = descriptors&#91;i&#93;.getpropertytype();

         classmetadata classmetadata = hibernateutil.getsessionfactory()
            .getclassmetadata( propclass );

         if ( classmetadata != null ) {   // this is a hibernate object
            string propname = descriptors&#91;i&#93;.getname();
            object propvalue = propertyutils.getproperty( this, propname );

            // evaluate property, create new instance if it is null
            if ( propvalue == null ) {
               propertyutils.setproperty( this, propname, propclass.newinstance() );
            }
         }
      }
   }

   public void cleanupemptyobjects() {
      // get propertydescriptor of all bean properties
      propertydescriptor descriptors&#91;&#93; =
      propertyutils.getpropertydescriptors( this );

      for ( int i = 0; i < descriptors.length; i++ ) {
         class propclass = descriptors&#91;i&#93;.getpropertytype();
         classmetadata classmetadata = hibernateutil.getsessionfactory()
            .getclassmetadata( propclass );

         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&#91;i&#93;.getname();
               propertyutils.setproperty( this, propname, null );
            }


         }
      }
   }
}


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

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

总结

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

感谢jason stell和barbara warren-sica对于本文的审查和修订。

参考资料
struts论坛:http://www.matrix.org.cn/topic.shtml?forumid=22
hibernate论坛:http://www.matrix.org.cn/topic.shtml?forumid=23
javaworld:http://www.javaworld.com
hibernate in action, christian bauer and gavin king (manning, october 2004; isbn: 193239415x):
http://www.manning.com/books/bauer
an excerpt from hibernate in action, &quot;get started with hibernate&quot; (javaworld, october 2004):
http://www.javaworld.com/javaworld/jw-10-2004/jw-1018-hibernate.html
for more information about struts:
http://struts.apache.org
for more information about hibernate:
http://www.hibernate.org
struts: the complete reference (mcgraw-hill osborne media, april 2004; isbn: 0072231319):
http://books.mcgraw-hill.com/getbook.php?isbn=0072231319&template=osborne
learn how to use hibernate, struts, spring, and axis to design an soa framework in &quot;design a simple service-oriented j2ee application framework,&quot; fangjian wu (javaworld, october 2004):
http://www.javaworld.com/javaworld/jw-10-2004/jw-1004-soa.html
for more articles on development frameworks, browse the web development frameworks section of javaworld's topical index:
http://www.javaworld.com/channel_content/jw-webdev-index.shtml
for more articles on tools for accessing data, browse the data access tools section of javaworld's topical index:
http://www.javaworld.com/channel_content/jw-dataaccesstools-index.shtml
also browse the persistence section of javaworld's topical index:
http://www.javaworld.com/channel_content/jw-persistence-index.shtml
for more articles on object-oriented programming, browse the object-oriented design and programming section of javaworld's topical index:
http://www.javaworld.com/channel_content/jw-oop-index.shtml


 


关键字 本文所属关键字

相关 与本文相关文章

分类 所有文章关键字导航

源码编程相关

Java   Asp   PHP   .Net   XML   C/C++   CGI   VB   Jsp   J2ee   J2se   J2me   EJB   Servlet   Tomcat   Resin   Struts   Weblogic   Eclipse   ANT   GUI   JMS   Web servise   IDEA   Webphere   Hibernate   Spring   Jboss   Applet   Swing   Socket   Javamail   Perl   Ajax   P2P   安全   模式   框架   测试   开源   游戏

SQL数据库相关

My-SQL   Ms-SQL   Access   DB2   Oracle   Sybase   SQLserver   索引   存储过程   加密   数据库   分页   视图  

手机无线相关

3G   Wap   CDMA   GRPS   GSM   IVR   彩信   短信   无线   增值业务

网页设计制作相关

HTML   CSS   网页配色   网页特效   Javascript   VBscript   Dreamweaver   Frontpage   JS   Web   网站设计

网站建设推广相关

建站经验   网站优化   网站排名   推广   Alexa

操作系统/服务器相关

Windows XP   Windows 2000   Windows 2003   Windows Me   Windows 9.x   Linux   UNIX   注册表   操作系统   服务器   应用服务器

图形图像多媒体相关

Photoshop   Fireworks   Flash   Coreldraw   Illustrator   Freehand   Photoimpact   多媒体   图形图像

标准 网站致力的规范

Valid CSS!

无不良内容,无不良广告,无恶意代码

Valid XHTML 1.0 Transitional

creativecommons