本文的主要內(nèi)容是對(duì) Grails 1.1 中 PetClinic-MVC 實(shí)例的代碼分析。這個(gè)實(shí)例主要驗(yàn)證了如何在非 Grails
的項(xiàng)目中使用 GORM。此外也演示了 Spring MVC 2.5 的一些用法。本文假設(shè)大家對(duì) Spring、Spring
MVC、Groovy 和 Grails 有著初步的了解。OK, let's go!
Domain 層
目前 Grails 只提供了在普通 Spring 應(yīng)用中獨(dú)立使用 GORM 的方法(現(xiàn)在 Groovy 和 Grails 已是
SpringSource 旗下的產(chǎn)品了,自然要首先對(duì)自家產(chǎn)品提供支持)。不過(guò)相信以后在其它 Java EE 框架中也可獨(dú)立使用 GORM。
在 Grails 以外使用 GORM,只需在 Domain 類上使用 grails.persistence.Entity 注釋即可。其它的就像在 Grails 中的一樣。
在配置方面也很簡(jiǎn)單,只要使用 <gorm:sessionFactory ... /> 即可。詳細(xì)請(qǐng)見(jiàn) Grails 的 PetClinic,這里就不拗述了。
Controller 層
同 Domain 層一樣,Controller 層也全部使用 Groovy 實(shí)現(xiàn)。主要是因?yàn)?GORM 的大部分方法都是動(dòng)態(tài)的。
1. ClinicController: ClinicController 實(shí)現(xiàn)了 InitializingBean 接口。這個(gè)接口只有一個(gè)方法 afterPropertiesSet。這個(gè)方法會(huì)在 Bean 完成屬性設(shè)置之后執(zhí)行。PetClinic 用這個(gè)方式實(shí)現(xiàn)了類似 Grails 中 BootStrap 的部分功能。在這個(gè)方法中我們可以看到 GORM 中的 save() 方法和 addTo* 動(dòng)態(tài)方法都是可用的。
可能有人會(huì)問(wèn),afterPropertiesSet()
這個(gè)方法的作用就是增加一些預(yù)定義的數(shù)據(jù),為什么不使用構(gòu)造函數(shù)來(lái)實(shí)現(xiàn)。實(shí)際上,在本例中,afterPropertiesSet()
的作用的確可以用構(gòu)造函數(shù)來(lái)實(shí)現(xiàn)(經(jīng)過(guò)測(cè)試)。至于作者是出于什么目的而選擇了實(shí)現(xiàn) InitializingBean 接口我也說(shuō)不好。
ClinicController 的其它部分就是一些簡(jiǎn)單的不能再簡(jiǎn)單的 Http 請(qǐng)求處理函數(shù)了。需要說(shuō)明的一點(diǎn)是 @RequestMapping 中的值代表了請(qǐng)求的 URL,這個(gè)是不用帶后綴的,也就是說(shuō) ".do" 是多余的。
2. AddPetForm:
在 AddPetForm 類的級(jí)別上有三個(gè)注釋,分別是 @Controller, @RequestMapping 和
@SessionAttributes。@Controller 不用多說(shuō),作用是這個(gè)類聲明為一個(gè) Spring Controller,并添加到
Context 中。下面介紹一下 @RequestMapping 和 @SessionAttributes 的功能:
除此以外,AddPetForm 中還有兩個(gè)特殊的方法,一個(gè)是注釋了 @ModelAttribute 的方法;另一個(gè)是注釋了 @InitBinder 的方法。下面分別介紹一下:
Controller 層中兩個(gè)特殊的類:ClinicBindingInitializer 和 PetTypeEditor
配置
1. applicationContext.xml
applicationContext 中大部分都是常見(jiàn)的 Spring 配置。特殊一點(diǎn)的配置除了 <gorm:sessionFactory ... /> 以外,還有 Hibernate StatisticsService JMX 的配置。
2. dispatcher-servlet.xml
Spring MVC 配置文件(也是一個(gè) Spring 的配置文件)的默認(rèn)名是 dispatcher-servlet.xml。
從上至下,第一個(gè)配置表明哪個(gè) Package 下的 Class 使用了 Spring 的 @Controller
注釋。這樣 Spring 容器在啟動(dòng)的時(shí)候會(huì)自動(dòng)掃描其下的 Class,將加注了 @Controller 注釋的類加入到 Spring
的容器中。
第二個(gè)配置 DefaultAnnotationHandlerMapping 是 HandlerMapping 接口的一個(gè)最常用的實(shí)現(xiàn)類。用于映射 Handler 和請(qǐng)求路徑之間的關(guān)系。通過(guò)這樣的配置,Controller 中的 @RequestMapping 中的路徑值就可以真正地和 Http 請(qǐng)求對(duì)應(yīng)起來(lái)。HandlerMapping 的另外一個(gè)常用的實(shí)現(xiàn)類是 ControllerClassNameHandlerMapping 。
第三個(gè)配置 AnnotationMethodHandlerAdapter 的作用是使處理請(qǐng)求的 Handler 能夠真正的具有處理請(qǐng)求的能力而提供一些服務(wù),包括為請(qǐng)求參數(shù)綁定應(yīng)用相應(yīng)的屬性編輯器(通過(guò)注冊(cè) webBindingInitializer 實(shí)現(xiàn))。
==============================結(jié)束的分割線==============================
寫(xiě)到這里,PetClinic-MVC 中值得分析學(xué)習(xí)的代碼都提到了。PetClinic-MVC 是一個(gè)很小的應(yīng)用,但是其中演示了 GORM 在普通 Spring 中的應(yīng)用和 Spring MVC 2.5 的使用。細(xì)看的話還是能學(xué)到不少東西的。我所寫(xiě)的只是我的理解,難免會(huì)有一些不足和錯(cuò)誤,歡迎糾正。
本文將簡(jiǎn)單談?wù)勎覍?duì) EJB 3.0 的兩種 Persistence Context 和 Seam-managed Persistence Context 的不同點(diǎn)的理解、所要解決的問(wèn)題和我自己所疑惑的問(wèn)題。
EJB 3.0 (JPA) 的 Persistence Context
大家在使用 EJB 3.0 的時(shí)候會(huì)注意到 EJB 3.0 中的容器管理 Persistence Context
有兩種類型,一種是 Transaction,另一種是 Extended。這是一個(gè)較 Hibernate 的 Session
所沒(méi)有的概念,Session 沒(méi)有兩種不同的類型,而且最重要的是 Session 不是容器管理的,這里的容器指的是 App Server
容器。這里暫時(shí)不談?wù)?Persistence Context 與 Session 之間的異同,主要談?wù)剝煞N Persistence
Context 之間的不同。學(xué)過(guò) ORM 的同學(xué)都知道,當(dāng) Persistence Context 是打開(kāi)狀態(tài)的時(shí)候,Model
就處于被管理的狀態(tài)中;當(dāng) Persistence Context 關(guān)閉之后,Model 就處于了 Detached 狀態(tài)。
上面這些特性對(duì)于 Transaction 或 Extended 的 Persistence Context
都是一樣的,不同的地方在于 Persistence Context 何時(shí)被打開(kāi)關(guān)閉。由于絕大多數(shù)情況下 Persistence Context
是被容器管理的(如果你不嫌累也可以自己控制 Persistence Context),所以在 EJB 3.0 應(yīng)用中看不到打開(kāi)或關(guān)閉
Persistence Context 的代碼(Spring + Hibernate 的應(yīng)用也同樣如此,Hibernate Session
的管理工作可以交給 Spring 來(lái)做)。
其實(shí),Transaction 和 Extended Persistence Context
的不同之處也就在于容器何時(shí)打開(kāi)或關(guān)閉 Persistence Context。Transaction 類型的 Persistence
Context 的打開(kāi)和關(guān)閉是和事務(wù)的打開(kāi)和關(guān)閉是同步的。也就是說(shuō)在一個(gè)事務(wù)開(kāi)始之后,Persistence Context
才會(huì)開(kāi)始;在事務(wù)關(guān)閉的時(shí)候,相應(yīng)的 Persistence Context 也會(huì)被關(guān)閉。
Extended 類型的 Persistence Context 的打開(kāi)和關(guān)閉是和 Stateful Session Bean
的生命周期同步的,是跨越事務(wù)的。也就是說(shuō),從 SFSB 的初始化開(kāi)始,直到銷毀,Persistence Context
都是存在的。你可以在事務(wù)之外執(zhí)行寫(xiě)操作,但是這是并不會(huì)執(zhí)行真正的數(shù)據(jù)庫(kù)操作,寫(xiě)操作只是放入了隊(duì)列,直到下一個(gè)事務(wù),寫(xiě)操作才會(huì)真正地被執(zhí)行。兩者的
不同簡(jiǎn)單說(shuō)來(lái)就是 Extended Persistence Context 存在的時(shí)間更長(zhǎng)。那為什么要有兩種不同的 Persistence
Context 呢?
當(dāng)一個(gè) Web
請(qǐng)求到來(lái)時(shí),服務(wù)器會(huì)打開(kāi)一個(gè)線程,這個(gè)線程可能會(huì)調(diào)用一個(gè)事務(wù)方法,這是一個(gè)事務(wù)便開(kāi)始了,當(dāng)這個(gè)請(qǐng)求結(jié)束時(shí),線程關(guān)閉,事務(wù)也隨之結(jié)束。由于
Transaction 類型的 Persistence Context 的生存周期是在事務(wù)范圍之內(nèi)的,所以一個(gè) Web
請(qǐng)求的結(jié)束也意味著相應(yīng)的 Persistence Context 的關(guān)閉。由于多數(shù) Web 應(yīng)用在一次 Web
請(qǐng)求內(nèi)即可完成一個(gè)獨(dú)立的操作,所以大部分情況下 Transaction 的 Persistence Context
是適用的。但是對(duì)于一些復(fù)雜的應(yīng)用,一次操作需要跨越多次請(qǐng)求。這種情況下,如果依舊使用 Transcation 的 Persistence
Context,由于每次請(qǐng)求結(jié)束后,相應(yīng)的 Persistence Context 都被關(guān)閉,相應(yīng)的 Model 也就變?yōu)?Detached
狀態(tài)。如果接下來(lái)的請(qǐng)求仍然需要這些已經(jīng)變?yōu)?Detached 狀態(tài)的 Model 就需要重新 load,使用 merge()
方法來(lái)持久化。稍有不適就會(huì)產(chǎn)生 LazyInitializationException 和
NonUniqueObjectException。同時(shí),這也提高了操作的復(fù)雜程度。
如果使用 Extended Persistence Context 就能解決這些問(wèn)題。由于 Extended
Persistence Context 的生命周期是與 SFSB 的生命周期同步的,所以只要多次請(qǐng)求調(diào)用的都是同一個(gè) SFSB
中的方法,有多少次的請(qǐng)求,Persistence Context 總是同一個(gè),其中的 Model 也始終是被管理的。很好地解決了
Persistence Context 在線程之間傳遞的問(wèn)題,也不會(huì)有 LazyInitializationException 和
NonUniqueObjectException 問(wèn)題的發(fā)生。
Seam-managed Persistence Context
EJB 3.0 容器管理之下的 Persistence Context
很不錯(cuò),能解決很多問(wèn)題,但是還是有些問(wèn)題無(wú)法解決。Seam 很強(qiáng)大,如果有些問(wèn)題 EJB 容器解決不了了,沒(méi)關(guān)系,把 Persistence
Context 交由 Seam 來(lái)管理就 OK 了。那 Seam 都能解決哪些 EJB 不能解決的問(wèn)題呢?先考慮下面兩個(gè)問(wèn)題: