欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費電子書(shū)等14項超值服

開(kāi)通VIP
在Struts和Hibernate之間搭起橋梁

作者:Ted He;alilo(作者的blog:http://blog.matrix.org.cn/page/alilo)

摘要

Hibernate和struts是當前市面上幾個(gè)最流行的開(kāi)源的庫之一。它們很有效率,是程序員在開(kāi)發(fā)Java企業(yè)應用,挑選幾個(gè)競爭的庫的首選。雖然它們經(jīng)常被一起應用,但是Hibernate的設計目標并不是和Struts一起使用,而Struts在Hibernate誕生好多年之前就發(fā)布了。為了讓它們在一起工作,仍然有很多挑戰。這篇文章點(diǎn)明了Struts和Hibernate之間的一些鴻溝,尤其關(guān)系到面向對象建模方面。文章也描述了如何在兩者間搭起橋梁,給出了一個(gè)基于擴展Struts的解決方案。所有的基于Struts和Hibernate構建的Web應用都能從這個(gè)通用的擴展中獲益。

在Hibernate in Action(Manning,2004十月)這本書(shū)里,作者Christian Bauer和Gavin King揭示了面向對象世界的模型和關(guān)系數據模型,兩個(gè)世界的范例是不一致的。Hibernate非常成功地在存儲層(persistence Layer)將兩者粘合在一起。但是領(lǐng)域模型(domain model)(也就是Model-View-Controller的model layer)和HTML頁(yè)面(MVC的View Layer)仍然存在不一致。在這篇文章中,我們將檢查這種不一致,并且探索解決的方案。

范例不一致的再發(fā)現

讓我們先看一個(gè)經(jīng)典的parent-child關(guān)系例子(看下面的代碼):product和category。Category類(lèi)定義了一個(gè)類(lèi)型為long的標示符id和一個(gè)類(lèi)型為String的屬性name。Product類(lèi)也有一個(gè)類(lèi)型為long的標示符id和一個(gè)類(lèi)型為Category的屬性category,表示了多對一的關(guān)系(也就是說(shuō)很多product可以屬于一個(gè)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;
   }
}



我們希望一個(gè)product可以被更改category,所以我們的HTML提供了一個(gè)下拉框列出所有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領(lǐng)域對象里,category屬性是Category類(lèi)型,但是ProductForm只有一個(gè)類(lèi)型為long的categoryId。這種不匹配不但增加了不一致,而且導致了不必要的代碼進(jìn)行primitive type的標示符和對應的對象之間的轉換。

這種不一致部分是由于HTML Form自己引起的:它只代表了一種關(guān)系模型,不能代表面向對象的模型。面向對象和關(guān)系模型的不一致在存儲層由對象關(guān)系映射(O/RM)解決。但是類(lèi)似的問(wèn)題在表示層(view layer)仍然存在。解決的關(guān)鍵是讓他們一起無(wú)縫地工作。

Struts的功能和局限

幸運的是,Struts能夠生成和解釋內嵌的對象屬性。Category下拉框可以用Struts page-construction(html) tag library:

<html:select property="category.id">
   <option value="">No Category</option>
   <html:options collection="categories" property="id" labelProperty="name"/>
</html:select>


我們假設categories是Category對象的一個(gè)list。所以現在我們要修改ProductForm,讓它變得更加“面向對象”,我們要修改ProductForm的categoryId,改成類(lèi)型為Category的category。這種改變會(huì )導致在Product和ProductForm之間復制屬性的工作更加繁瑣,因為兩者有相同的屬性。

public class ProductForm extends ActionForm {
     private Long id;
     private Category category;
     ...
}



當我們完成剩余的Struts Action, configuration, validator, jsp, hibernate層后,開(kāi)始測試,我們馬上在訪(fǎng)問(wèn)ProductForm.category.id時(shí)遇到了NullPointerException。這是預料中的!因為ProductForm.category還沒(méi)有被設置,同時(shí),Hibernate也會(huì )將多對一所聯(lián)系的對象引用設為空(如果database field為空指)(譯者:這里指Hiberate將product.category為Null,如果該Product沒(méi)有聯(lián)系到任何category)。Struts要求所有的對象在顯示(生成HTML Form)和傳播(提交HTML FORM)之前被建立。

讓我們看看如何用ActionForm.reset()來(lái)架起橋梁。

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

在我第一個(gè)星期接觸Struts的時(shí)候,我最大的一個(gè)疑問(wèn)就是:為什么我必須為Properties, getter方法, setter方法保持幾乎完全相同的兩份copy, 一份在A(yíng)ctionForm Bean, 一份在DomainObject。這個(gè)繁瑣的步驟成了Struts社區最主要的抱怨之一。

以我的觀(guān)點(diǎn),ActionForm存在有原因的。首先,它們可以區別于Domain Object因為他們但當了不同的角色。在MVC模式下,Domain Object是Model層的一個(gè)部分,ActionForm是View層的。因為Webpage的Field和Database的Field可能不一樣,某些特制的轉換是常見(jiàn)的。第二,ActionForm.validate()方法可以定義非常好用的驗證規則。第三,可能有其他的,特定的View行為,但是又不想在domain layer實(shí)現,特別當persistence framework來(lái)管理domain object的時(shí)候。

提交Form

讓我們來(lái)用ActionForm內有的方法-reset()-來(lái)解決view和model之間的不一致。這個(gè)reset()方法是在A(yíng)ctionForm在被Struts Controller Servlet處理request時(shí)候復制ActionForm屬性之前調用的。這個(gè)方法最常見(jiàn)的使用是:checkbox必須被顯式地設為false,讓沒(méi)有被選中的checkbox被正確識別。Reset()也是一個(gè)初始化用于view rending對象的合適地方。代碼看起來(lái)是這樣的:

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在使用用戶(hù)提交的值填寫(xiě)ProductForm之前,Struts會(huì )調用reset(),這樣category屬性將會(huì )被初始化。請注意,你必須檢查category看它是不是null,后面我們會(huì )討論這個(gè)。

編輯Form

到目前為止,我們已經(jīng)解決了form提交時(shí)候的問(wèn)題。但是當我們在生成form頁(yè)面的時(shí)候呢?Html:select tag也希望有一個(gè)非空的引用,所以我們將在form生成頁(yè)面之前調用reset()。我們在action類(lèi)里加入了一行:

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 );
        ...
     }
}



我假設讀者已經(jīng)對action類(lèi)和Jakarta commons Beanutils包非常熟悉了。CreateOrLoadProduct()建立了一個(gè)新的Product實(shí)例或者從數據庫里載入一個(gè)已有的實(shí)例,具體取決于這個(gè)action是建立或者修改Product的。ProductForm被賦值后(譯者:也就是調用PropertyUtils.copyProperties后),productForm.category已經(jīng)從Product.category復制過(guò)來(lái)了(譯者:實(shí)際上只是復制了category對象引用,并沒(méi)有開(kāi)銷(xiāo)),然后,ProductForm就能用來(lái)生成頁(yè)面了。我們同時(shí)也必須保證:不覆蓋已經(jīng)被Hibernate載入的對象,所以我們必須檢查(category)是不是為null。

因為reset()方法是在A(yíng)ctionForm中定義的,我們可以把上述代碼放入一個(gè)superclass,比如CommonEditAction,來(lái)處理這些事情:
    
      Product product = createOrLoadProduct();
        PropertyUtils.copyProperties( form, product );
        form.reset( mapping, request );


如果你需要一個(gè)只讀的Form, 你有兩個(gè)選擇: 第一檢查所聯(lián)系的jsp對象是不是null, 第二復制domain對象到ActionForm之后調用Reset()

保存domain對象

我們解決了提交Form和生成Form頁(yè)面的問(wèn)題, 所以Struts可以滿(mǎn)足了。但是Hibernate呢?當用戶(hù)選擇了一個(gè)null ID option – 在我們的例子中“no category”option- 并且提交form, productForm.category指向一個(gè)新建立的hibernate對象,id為null。當category屬性從ProductForm復制到Hibernate控制的Product對象并且存儲時(shí),Hibernate會(huì )抱怨product.category是一個(gè)臨時(shí)對象,需要在Product存儲前先被存儲。當然,我們知道它是Null,并且不需要被存儲。所以我們需要將product.category置為Null,然后Hibernate就能存儲Product了(譯者:在這種情況下,數據庫product.category被設成空值)。我們也不希望改變Hibernate的工作方式,所以我們選擇在復制到Domain對象之前清理這些臨時(shí)對象,我們在ProductForm中加了一個(gè)方法:

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之前清理掉這些臨時(shí)對象,所以如果ProductForm.category只是用來(lái)放Null的,則將ProductForm.category置為Null。然后Domain對象的category也會(huì )被設成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 );
        ...
     }
}



一對多關(guān)系

我還沒(méi)有解決Category到Product的一對多關(guān)系。我們把它加入到Category的Metadata中:

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 setProducts(Set products) {
        this.products = products;
     }
}


注意:Hibernate的cascade屬性為all-delete-orphan表明:Hibernate需要在存儲包含的Category對象時(shí)候,自動(dòng)存儲Product對象。和parent對象一起存儲child對象的情況并不常見(jiàn),常見(jiàn)的是:分別控制child的存儲和parent的存儲。在我們的例子中,我們可以容易地做到這一點(diǎn),如果我們允許用戶(hù)在同一個(gè)html page編輯Category和ProductS。用set表示Products是非常直觀(guān)的:

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();
        }
     }
}



更進(jìn)一步
我們已經(jīng)可以察看,編輯,提交forms,并且存儲相關(guān)的objects,但是為所有的ActionForm類(lèi)定義CleanupEmptyObjects()和reset()方法是個(gè)累贅。我們將用一個(gè)抽象的ActionForm來(lái)完成協(xié)助完成這些工作。

作為通用的實(shí)現,我們必須遍歷所有的Hibernate管理的domain對象,發(fā)現他們的identifier,并且測試id值。幸運的是:org.hibernate.metadata包已經(jīng)有兩個(gè)Utility類(lèi)能取出domain對象的元數據。我們用ClassMetadata類(lèi)檢查這個(gè)object是不是Hibernate管理的。如果是:我們把它們的id Value取出來(lái)。我們用了Jakarta Commons Beanutils包來(lái)協(xié)助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[] =
         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.setProperty( this, propName, propClass.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()
            .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[i].getName();
               PropertyUtils.setProperty( this, propName, null );
            }


         }
      }
   }
}


為了讓代碼可讀,我們省略了Exception的處理代碼。

我們的新AbstractForm類(lèi)從Struts的ActionForm類(lèi)繼承,并且提供了通用行為:reset和cleanup多對一關(guān)聯(lián)對象。當這個(gè)關(guān)系是相反的話(huà)(也就是一對多關(guān)系),那么每個(gè)例子將會(huì )有所不同,類(lèi)似在A(yíng)bstract類(lèi)里實(shí)現是比較好的辦法。

總結

Struts和Hibernate是非常流行和強大的框架,他們可以有效地相互合作,并且彌補domain模型和MVC視圖(view)之間的差別。這篇文章討論一個(gè)解決Struts/Hibernate Project的通用的方案,并且不需要大量修改已經(jīng)有的代碼。
本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請點(diǎn)擊舉報。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
Spring、Hibernate、Struts1整合的方式
Struts,Hibernate,Spring經(jīng)典面試題 - xp1204的專(zhuān)欄 - CS...
我的Struts分頁(yè)方法
SSH實(shí)現的增刪改查實(shí)例
Struts1.x系列教程(1):用MyEclipse開(kāi)發(fā)第一個(gè)Struts程序 - 哈佛...
實(shí)例介紹Struts+Spring+Hibernate開(kāi)發(fā)
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導長(cháng)圖 關(guān)注 下載文章
綁定賬號成功
后續可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服

欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久