<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    隨筆-88  評論-77  文章-48  trackbacks-0

    摘要

    Hibernate和struts是當(dāng)前市面上幾個最流行的開源的庫之一。它們很有效率,是程序員在開發(fā)Java企業(yè)應(yīng)用,挑選幾個競爭的庫的首選。雖然它們經(jīng)常被一起應(yīng)用,但是Hibernate的設(shè)計目標(biāo)并不是和Struts一起使用,而Struts在Hibernate誕生好多年之前就發(fā)布了。為了讓它們在一起工作,仍然有很多挑戰(zhàn)。這篇文章點明了Struts和Hibernate之間的一些鴻溝,尤其關(guān)系到面向?qū)ο蠼7矫妗N恼乱裁枋隽巳绾卧趦烧唛g搭起橋梁,給出了一個基于擴展Struts的解決方案。所有的基于Struts和Hibernate構(gòu)建的Web應(yīng)用都能從這個通用的擴展中獲益。

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

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

    讓我們先看一個經(jīng)典的parent-child關(guān)系例子(看下面的代碼):product和category。Category類定義了一個類型為long的標(biāo)示符id和一個類型為String的屬性name。Product類也有一個類型為long的標(biāo)示符id和一個類型為Category的屬性category,表示了多對一的關(guān)系(也就是說很多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="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)域?qū)ο罄铮琧ategory屬性是Category類型,但是ProductForm只有一個類型為long的categoryId。這種不匹配不但增加了不一致,而且導(dǎo)致了不必要的代碼進(jìn)行primitive type的標(biāo)示符和對應(yīng)的對象之間的轉(zhuǎn)換。

    這種不一致部分是由于HTML Form自己引起的:它只代表了一種關(guān)系模型,不能代表面向?qū)ο蟮哪P汀C嫦驅(qū)ο蠛完P(guān)系模型的不一致在存儲層由對象關(guān)系映射(O/RM)解決。但是類似的問題在表示層(view layer)仍然存在。解決的關(guān)鍵是讓他們一起無縫地工作。

    Struts的功能和局限

    幸運的是,Struts能夠生成和解釋內(nèi)嵌的對象屬性。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>



    我們假設(shè)categories是Category對象的一個list。所以現(xiàn)在我們要修改ProductForm,讓它變得更加“面向?qū)ο蟆保覀円薷腜roductForm的categoryId,改成類型為Category的category。這種改變會導(dǎo)致在Product和ProductForm之間復(fù)制屬性的工作更加繁瑣,因為兩者有相同的屬性。

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




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

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

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

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

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

    提交Form

    讓我們來用ActionForm內(nèi)有的方法-reset()-來解決view和model之間的不一致。這個reset()方法是在ActionForm在被Struts Controller Servlet處理request時候復(fù)制ActionForm屬性之前調(diào)用的。這個方法最常見的使用是:checkbox必須被顯式地設(shè)為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會調(diào)用reset(),這樣category屬性將會被初始化。請注意,你必須檢查category看它是不是null,后面我們會討論這個。

    編輯Form

    到目前為止,我們已經(jīng)解決了form提交時候的問題。但是當(dāng)我們在生成form頁面的時候呢?Html:select tag也希望有一個非空的引用,所以我們將在form生成頁面之前調(diào)用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 );
    ????????...
    ???? }
    }




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

    因為reset()方法是在ActionForm中定義的,我們可以把上述代碼放入一個superclass,比如CommonEditAction,來處理這些事情:
    ????

    				
    				
    				
    						??????Product product = createOrLoadProduct();
    ????????PropertyUtils.copyProperties( form, product );
    ????????form.reset( mapping, request );



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

    保存domain對象

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

    我還沒有解決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對象時候,自動存儲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();
    ????????}
    ???? }
    }




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

    作為通用的實現(xiàn),我們必須遍歷所有的Hibernate管理的domain對象,發(fā)現(xiàn)他們的identifier,并且測試id值。幸運的是:org.hibernate.metadata包已經(jīng)有兩個Utility類能取出domain對象的元數(shù)據(jù)。我們用ClassMetadata類檢查這個object是不是Hibernate管理的。如果是:我們把它們的id Value取出來。我們用了Jakarta Commons Beanutils包來協(xié)助JavaBean元數(shù)據(jù)的操作。

    				
    				
    				
    						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類從Struts的ActionForm類繼承,并且提供了通用行為:reset和cleanup多對一關(guān)聯(lián)對象。當(dāng)這個關(guān)系是相反的話(也就是一對多關(guān)系),那么每個例子將會有所不同,類似在Abstract類里實現(xiàn)是比較好的辦法。

    總結(jié)

    Struts和Hibernate是非常流行和強大的框架,他們可以有效地相互合作,并且彌補domain模型和MVC視圖(view)之間的差別。這篇文章討論一個解決Struts/Hibernate Project的通用的方案,并且不需要大量修改已經(jīng)有的代碼。

    posted on 2006-04-30 09:01 崛起的程序員 閱讀(283) 評論(0)  編輯  收藏 所屬分類: 載選文章
    主站蜘蛛池模板: 亚洲国产精品久久久久久| 亚洲youwu永久无码精品| 亚洲三级高清免费| 青青青亚洲精品国产| 久久久久亚洲AV综合波多野结衣 | 亚洲AV无码一区二区三区牛牛| 日韩视频在线免费| 黄色一级毛片免费看| 亚洲天堂男人天堂| 国产网站在线免费观看| 一个人看的www免费视频在线观看 一个人免费视频观看在线www | 中文字幕亚洲色图| 国产免费人成在线视频| 99精品视频免费在线观看| 亚洲成AV人片高潮喷水| 无码乱人伦一区二区亚洲| 国产真人无遮挡作爱免费视频| 在线免费观看h片| 亚洲欧洲av综合色无码| 亚洲国产精品成人精品无码区在线| 免费无码AV电影在线观看| 久久精品国产免费一区| 精品久久亚洲一级α| 亚洲成人黄色网址| 亚洲日韩精品一区二区三区无码| 大陆一级毛片免费视频观看| 免费91麻豆精品国产自产在线观看 | 最近中文字幕免费2019| 一区二区三区免费在线观看| 亚洲男人的天堂久久精品| 亚洲AV一宅男色影视| 亚洲国产精品一区二区三区久久| 免费黄色福利视频| 久艹视频在线免费观看| 一本到卡二卡三卡免费高 | 男人j进女人p免费视频| 亚洲性无码AV中文字幕| 亚洲美女自拍视频| 亚洲av永久无码精品漫画| 亚洲日韩一页精品发布| 亚洲精品无码AV中文字幕电影网站|