| 作者:Ted He;alilo(作者的blog:http://blog./page/alilo) 摘要 /** 我們希望一個(gè)product可以被更改category,所以我們的HTML提供了一個(gè)下拉框列出所有Category。 <select name="categoryId"> 這里我們看出了兩者的不一致:在Product領(lǐng)域?qū)ο罄?,category屬性是Category類(lèi)型,但是ProductForm只有一個(gè)類(lèi)型為long的categoryId。這種不匹配不但增加了不一致,而且導(dǎo)致了不必要的代碼進(jìn)行primitive type的標(biāo)示符和對(duì)應(yīng)的對(duì)象之間的轉(zhuǎn)換。 這種不一致部分是由于HTML Form自己引起的:它只代表了一種關(guān)系模型,不能代表面向?qū)ο蟮哪P?。面向?qū)ο蠛完P(guān)系模型的不一致在存儲(chǔ)層由對(duì)象關(guān)系映射(O/RM)解決。但是類(lèi)似的問(wèn)題在表示層(view layer)仍然存在。解決的關(guān)鍵是讓他們一起無(wú)縫地工作。 Struts的功能和局限 幸運(yùn)的是,Struts能夠生成和解釋內(nèi)嵌的對(duì)象屬性。Category下拉框可以用Struts page-construction(html) tag library: <html:select property="category.id"> 我們假設(shè)categories是Category對(duì)象的一個(gè)list。所以現(xiàn)在我們要修改ProductForm,讓它變得更加“面向?qū)ο蟆保覀円薷腜roductForm的categoryId,改成類(lèi)型為Category的category。這種改變會(huì)導(dǎo)致在Product和ProductForm之間復(fù)制屬性的工作更加繁瑣,因?yàn)閮烧哂邢嗤膶傩浴?br> public class ProductForm extends ActionForm {當(dāng)我們完成剩余的Struts Action, configuration, validator, jsp, hibernate層后,開(kāi)始測(cè)試,我們馬上在訪問(wèn)ProductForm.category.id時(shí)遇到了NullPointerException。這是預(yù)料中的!因?yàn)镻roductForm.category還沒(méi)有被設(shè)置,同時(shí),Hibernate也會(huì)將多對(duì)一所聯(lián)系的對(duì)象引用設(shè)為空(如果database field為空指)(譯者:這里指Hiberate將product.category為Null,如果該P(yáng)roduct沒(méi)有聯(lián)系到任何category)。Struts要求所有的對(duì)象在顯示(生成HTML Form)和傳播(提交HTML FORM)之前被建立。 讓我們看看如何用ActionForm.reset()來(lái)架起橋梁。 (并非如此)臭名昭著的Struts ActionForm 在我第一個(gè)星期接觸Struts的時(shí)候,我最大的一個(gè)疑問(wèn)就是:為什么我必須為Properties, getter方法, setter方法保持幾乎完全相同的兩份copy, 一份在ActionForm Bean, 一份在DomainObject。這個(gè)繁瑣的步驟成了Struts社區(qū)最主要的抱怨之一。 以我的觀點(diǎn),ActionForm存在有原因的。首先,它們可以區(qū)別于Domain Object因?yàn)樗麄兊?dāng)了不同的角色。在MVC模式下,Domain Object是Model層的一個(gè)部分,ActionForm是View層的。因?yàn)閃ebpage的Field和Database的Field可能不一樣,某些特制的轉(zhuǎn)換是常見(jiàn)的。第二,ActionForm.validate()方法可以定義非常好用的驗(yàn)證規(guī)則。第三,可能有其他的,特定的View行為,但是又不想在domain layer實(shí)現(xiàn),特別當(dāng)persistence framework來(lái)管理domain object的時(shí)候。 提交Form 讓我們來(lái)用ActionForm內(nèi)有的方法-reset()-來(lái)解決view和model之間的不一致。這個(gè)reset()方法是在ActionForm在被Struts Controller Servlet處理request時(shí)候復(fù)制ActionForm屬性之前調(diào)用的。這個(gè)方法最常見(jiàn)的使用是:checkbox必須被顯式地設(shè)為false,讓沒(méi)有被選中的checkbox被正確識(shí)別。Reset()也是一個(gè)初始化用于view rending對(duì)象的合適地方。代碼看起來(lái)是這樣的: public class ProductForm extends ActionForm {Struts在使用用戶(hù)提交的值填寫(xiě)ProductForm之前,Struts會(huì)調(diào)用reset(),這樣category屬性將會(huì)被初始化。請(qǐng)注意,你必須檢查category看它是不是null,后面我們會(huì)討論這個(gè)。 編輯Form 到目前為止,我們已經(jīng)解決了form提交時(shí)候的問(wèn)題。但是當(dāng)我們?cè)谏蒮orm頁(yè)面的時(shí)候呢?Html:select tag也希望有一個(gè)非空的引用,所以我們將在form生成頁(yè)面之前調(diào)用reset()。我們?cè)赼ction類(lèi)里加入了一行: public class EditProductAction extends Action {我假設(shè)讀者已經(jīng)對(duì)action類(lèi)和Jakarta commons Beanutils包非常熟悉了。CreateOrLoadProduct()建立了一個(gè)新的Product實(shí)例或者從數(shù)據(jù)庫(kù)里載入一個(gè)已有的實(shí)例,具體取決于這個(gè)action是建立或者修改Product的。ProductForm被賦值后(譯者:也就是調(diào)用PropertyUtils.copyProperties后),productForm.category已經(jīng)從Product.category復(fù)制過(guò)來(lái)了(譯者:實(shí)際上只是復(fù)制了category對(duì)象引用,并沒(méi)有開(kāi)銷(xiāo)),然后,ProductForm就能用來(lái)生成頁(yè)面了。我們同時(shí)也必須保證:不覆蓋已經(jīng)被Hibernate載入的對(duì)象,所以我們必須檢查(category)是不是為null。 因?yàn)閞eset()方法是在ActionForm中定義的,我們可以把上述代碼放入一個(gè)superclass,比如CommonEditAction,來(lái)處理這些事情: Product product = createOrLoadProduct(); 如果你需要一個(gè)只讀的Form, 你有兩個(gè)選擇: 第一檢查所聯(lián)系的jsp對(duì)象是不是null, 第二復(fù)制domain對(duì)象到ActionForm之后調(diào)用Reset() 保存domain對(duì)象 我們解決了提交Form和生成Form頁(yè)面的問(wèn)題, 所以Struts可以滿(mǎn)足了。但是Hibernate呢?當(dāng)用戶(hù)選擇了一個(gè)null ID option – 在我們的例子中“no category”option- 并且提交form, productForm.category指向一個(gè)新建立的hibernate對(duì)象,id為null。當(dāng)category屬性從ProductForm復(fù)制到Hibernate控制的Product對(duì)象并且存儲(chǔ)時(shí),Hibernate會(huì)抱怨product.category是一個(gè)臨時(shí)對(duì)象,需要在Product存儲(chǔ)前先被存儲(chǔ)。當(dāng)然,我們知道它是Null,并且不需要被存儲(chǔ)。所以我們需要將product.category置為Null,然后Hibernate就能存儲(chǔ)Product了(譯者:在這種情況下,數(shù)據(jù)庫(kù)product.category被設(shè)成空值)。我們也不希望改變Hibernate的工作方式,所以我們選擇在復(fù)制到Domain對(duì)象之前清理這些臨時(shí)對(duì)象,我們?cè)赑roductForm中加了一個(gè)方法: public class ProductForm extends ActionForm {我們?cè)赾opyProperties之前清理掉這些臨時(shí)對(duì)象,所以如果ProductForm.category只是用來(lái)放Null的,則將ProductForm.category置為Null。然后Domain對(duì)象的category也會(huì)被設(shè)成null: public class SaveProductAction extends Action {一對(duì)多關(guān)系 我還沒(méi)有解決Category到Product的一對(duì)多關(guān)系。我們把它加入到Category的Metadata中: public class Category {注意:Hibernate的cascade屬性為all-delete-orphan表明:Hibernate需要在存儲(chǔ)包含的Category對(duì)象時(shí)候,自動(dòng)存儲(chǔ)Product對(duì)象。和parent對(duì)象一起存儲(chǔ)child對(duì)象的情況并不常見(jiàn),常見(jiàn)的是:分別控制child的存儲(chǔ)和parent的存儲(chǔ)。在我們的例子中,我們可以容易地做到這一點(diǎn),如果我們?cè)试S用戶(hù)在同一個(gè)html page編輯Category和ProductS。用set表示Products是非常直觀的: public class CategoryForm extends ActionForm {更進(jìn)一步 我們已經(jīng)可以察看,編輯,提交forms,并且存儲(chǔ)相關(guān)的objects,但是為所有的ActionForm類(lèi)定義CleanupEmptyObjects()和reset()方法是個(gè)累贅。我們將用一個(gè)抽象的ActionForm來(lái)完成協(xié)助完成這些工作。 作為通用的實(shí)現(xiàn),我們必須遍歷所有的Hibernate管理的domain對(duì)象,發(fā)現(xiàn)他們的identifier,并且測(cè)試id值。幸運(yùn)的是:org.hibernate.metadata包已經(jīng)有兩個(gè)Utility類(lèi)能取出domain對(duì)象的元數(shù)據(jù)。我們用ClassMetadata類(lèi)檢查這個(gè)object是不是Hibernate管理的。如果是:我們把它們的id Value取出來(lái)。我們用了Jakarta Commons Beanutils包來(lái)協(xié)助JavaBean元數(shù)據(jù)的操作。 import java.beans.PropertyDescriptor; 為了讓代碼可讀,我們省略了Exception的處理代碼。 我們的新AbstractForm類(lèi)從Struts的ActionForm類(lèi)繼承,并且提供了通用行為:reset和cleanup多對(duì)一關(guān)聯(lián)對(duì)象。當(dāng)這個(gè)關(guān)系是相反的話(huà)(也就是一對(duì)多關(guān)系),那么每個(gè)例子將會(huì)有所不同,類(lèi)似在Abstract類(lèi)里實(shí)現(xiàn)是比較好的辦法。 總結(jié) Struts和Hibernate是非常流行和強(qiáng)大的框架,他們可以有效地相互合作,并且彌補(bǔ)domain模型和MVC視圖(view)之間的差別。這篇文章討論一個(gè)解決Struts/Hibernate Project的通用的方案,并且不需要大量修改已經(jīng)有的代碼。 | 
|  | 
來(lái)自: bluecrystal > 《hibernate》