第三部分:建立框架代碼
工程結(jié)構(gòu)

大家可以看到,本例一共分為:dao、entity、service、web四層。另外在這些層次下,還以業(yè)務(wù)功能再進(jìn)行分包,這樣做是為了方便在以后的功能擴(kuò)展中,能更好的管理和維護(hù)代碼。如果將所有類都直接集中在這4個(gè)包下面,隨著類的增加,會(huì)越來(lái)越難以維護(hù),而且查找起來(lái)也很費(fèi)勁。
HibernateDao
在本例中,我是通過(guò)繼承Spring提供的HibernateDaoSupport來(lái)實(shí)現(xiàn)持久層的基類。同時(shí)引入泛型參數(shù),封裝了一些基本操作方法。

這是HibernateDao的部分代碼,引入的這個(gè)泛型參數(shù),其實(shí)就是實(shí)體類(User、Role)。通過(guò)傳遞這個(gè)實(shí)體類,在構(gòu)造方法中利用反射特性將它從JVM中取出來(lái)。

這里的getClass()方法是獲得繼承HibernateDao的類(UserDao、RoleDao)
getGenericSuperclass()方法就是通過(guò)這些繼承了HibernateDao的類,來(lái)得到父類(父類就是HibernateDao)的泛型。注意這個(gè)方法的返回值為Type,這是一個(gè)類型接口。請(qǐng)參考API。
因?yàn)樵诶^承HibernateDao 的時(shí)候,會(huì)給它加一個(gè)泛型參數(shù)。比如,User、Role實(shí)體類。因此超類是參數(shù)化類型,所以返回的 Type 對(duì)象包含所使用的實(shí)際類型參數(shù)。這里返回的Type對(duì)象是ParameterizedType接口的實(shí)現(xiàn)類ParameterizedTypeImpl,所以要將返回類型轉(zhuǎn)型為ParameterizedType。
getActualTypeArguments()方法是ParameterizedType接口中的,它的作用就是獲得實(shí)際類型參數(shù) Type 對(duì)象的數(shù)組,因?yàn)槲覀冞@里只定義了一個(gè)泛型參數(shù),數(shù)組里面也只有一個(gè)值,所以需要在數(shù)組下標(biāo)處填0。然后最后一步轉(zhuǎn)型千萬(wàn)別忘記了,因?yàn)檫@個(gè)方法返回的可是一個(gè)Type數(shù)組喔。
如果對(duì)于這部分的說(shuō)明還有點(diǎn)不理解的話,請(qǐng)到時(shí)候有了代碼,設(shè)個(gè)斷點(diǎn)跟蹤一下,就會(huì)全部清楚了。關(guān)于java反射,它已經(jīng)超出本文的范圍。大象只對(duì)本例中用到的部分進(jìn)行講解。
使用這種寫法,是方便我們進(jìn)行類型轉(zhuǎn)換與類型檢查。另外還可以簡(jiǎn)化某些方法的寫法。比如:createCriteria(Criterion... criterions)這個(gè)方法。參數(shù)是Criterion類型的可變參數(shù),它是用來(lái)設(shè)置查詢條件。如果要進(jìn)行對(duì)象化查詢,那么最簡(jiǎn)單的寫法就可以直接寫成createCriteria()。另外還有重載的方法,可以根據(jù)傳入class類型來(lái)創(chuàng)建自定義查詢。
dao
持久層的Dao類是根據(jù)實(shí)體類定義,一般是一個(gè)實(shí)體類就會(huì)有一個(gè)對(duì)應(yīng)的Dao類。當(dāng)然這要跟業(yè)務(wù)需求來(lái)設(shè)計(jì),不是絕對(duì)的。另外你也可以為了簡(jiǎn)便而去掉dao層,將持久化操作與業(yè)務(wù)邏輯全部寫在service層。

這些定義的方法是供service層調(diào)用,在業(yè)務(wù)層,將不會(huì)看到一行與持久層有關(guān)的代碼,降低藕合性是這樣做的目的。@Repository注解的作用就是標(biāo)注這個(gè)UserDao是一個(gè)持久層組件。還記得前一章講到的掃描器嗎?component-scan 它就是用來(lái)將標(biāo)有@Repository,@Service這樣的注解類掃描到Spring的容器里,并且同時(shí)對(duì)標(biāo)有@Autowired注解的Bean啟用了自動(dòng)注入功能。這就是Spring從2.5開(kāi)始提供的新特性。我們使用注解的方法,就可以告別那繁瑣的配置文件定義。
entity
關(guān)于實(shí)體的定義就是使用JPA注解,關(guān)于這部分,我以前寫過(guò)一篇文章專門講這個(gè),如果有不清楚的朋友可以先參考一下。學(xué)習(xí)JPA——Hibernate Annotation使用實(shí)例
本例中,我有兩點(diǎn)要講下。
第一、管理主鍵的表generator_table去掉原來(lái)單獨(dú)定義的那個(gè)ID主鍵,把g_key設(shè)為主鍵,整個(gè)表將只有兩個(gè)字段,g_key和g_value。
第二、在User實(shí)體中,我將角色ID(role_id)與角色實(shí)體(Role)做了一個(gè)多對(duì)一關(guān)聯(lián)。這一點(diǎn)是原來(lái)文章中沒(méi)有講過(guò)的。

請(qǐng)一定注意role_id是user表的字段。我在本例中設(shè)定的是一個(gè)角色可以對(duì)應(yīng)多個(gè)人員,所以這個(gè)role_id存的就是role表id的值。fetch = FetchType.LAZY指定采用延遲檢索,如果當(dāng)你取得了User對(duì)象,但又不想取Role中的信息,這時(shí),User對(duì)象中的role屬性是代理狀態(tài)。Role對(duì)象中的值都是空的。只有當(dāng)你使用role.id或role.name進(jìn)行取值的時(shí)候,hibernate才會(huì)去數(shù)據(jù)庫(kù)中查找對(duì)應(yīng)的記錄,因此在一定程度上降低了資源消耗。不過(guò)這里有點(diǎn)要注意,采用延遲檢索的時(shí)候,需要加上前一篇講到的OpenSessionInViewFilter過(guò)濾器。否則會(huì)遇到session關(guān)閉的異常。
service

@Service表示這是業(yè)務(wù)層組件。在業(yè)務(wù)層需要對(duì)UserDao加上@Autowired注解,大象在這里將業(yè)務(wù)層的方法名與持久層的方法名定義為一樣的,是我的一種習(xí)慣,大家可以按自己的想法來(lái)做。
測(cè)試
既然有了這么多代碼,那就來(lái)測(cè)試一下吧,看看有沒(méi)有問(wèn)題。

好吧,為了照顧那些堅(jiān)定的JUnit擁護(hù)者,再寫一個(gè)JUnit測(cè)試。本例使用junit-4.4.jar


@BeforeClass注解的方法表示在類實(shí)例化之前,也就是在類的構(gòu)造方法執(zhí)行之前就會(huì)執(zhí)行。而且使用這個(gè)注解的方法必須是static void
@Test標(biāo)明這是測(cè)試方法,方法名不用像以前那樣必須按照規(guī)則進(jìn)行命名,可以自由定義。
上圖顯示大象使用JUnit方式測(cè)試也通過(guò)了(如果不會(huì)通過(guò)我寫它干嘛?嘿嘿)。
假如我將張三改成張四,再來(lái)看看測(cè)試結(jié)果。

這個(gè)截圖可以很明顯的說(shuō)明所有東西。
這一篇是給大家講怎么用代碼來(lái)實(shí)現(xiàn)除web層之外的全注解步驟。當(dāng)然,我主要是講思路,其實(shí)思路比代碼重要得多。這一個(gè)系列的最后,我會(huì)放上所有源碼供大家下載。現(xiàn)在這樣慢慢分析,是想給大家講道理。我們應(yīng)該努力提升自己的境界與層次,而不要只把眼光放在代碼上面。下一章將會(huì)著重介紹web層,以及struts2的注解插件struts2-convention。那么,我們下次繼續(xù)。
本文為菠蘿大象原創(chuàng),如要轉(zhuǎn)載請(qǐng)注明出處。http://www.aygfsteel.com/bolo