一、spring+hibernate時(shí)使用hibernate的延遲加載功能時(shí)存在的問(wèn)題:
spring是一個(gè)設(shè)計(jì)層面的框架,他解決的是業(yè)務(wù)邏輯層和其他各層的松耦合問(wèn)題,因此它將面向接口的編程思想貫穿整個(gè)系統(tǒng)應(yīng)用。Spring的orm框架用來(lái)集成其他持久層框架,比如hibernate,spring-orm框架對(duì)hibernate的session進(jìn)行了封裝,我們可以很方便的通過(guò)繼承這個(gè)封裝類(lèi)HibernateDAOSupport并注入hibernate的sessionFactory完成初始化,并調(diào)用其內(nèi)置對(duì)象HibernateTemplate的封裝方法來(lái)調(diào)用session的API而不用考慮到session的初始化和關(guān)閉以及事務(wù)處理等系統(tǒng)操作,這也是AOP思想的一種體現(xiàn)。Hibernate的延遲加載功能是指獲取某個(gè)實(shí)體對(duì)象時(shí)并不從數(shù)據(jù)庫(kù)中加載他的關(guān)聯(lián)對(duì)象,而在實(shí)際獲取關(guān)聯(lián)對(duì)象的時(shí)候才從數(shù)據(jù)庫(kù)中加載,這樣做很好的節(jié)省了數(shù)據(jù)庫(kù)資源但是前提是必須保持session處于打開(kāi)狀態(tài),在所有操作完成后再關(guān)閉。Spring封裝了session操作后很自然的要做到在方法調(diào)用的前后打開(kāi)和關(guān)閉session,這樣我們?cè)谕ㄟ^(guò)HibernateTemplate的方法來(lái)獲取實(shí)體對(duì)象以后session就已經(jīng)關(guān)閉了,而這時(shí)候在調(diào)用獲取關(guān)聯(lián)對(duì)象的方法的時(shí)候就會(huì)拋出異常:

二、如何解決
1、openSessionInViewInterceptor
通過(guò)spring的控制層框架spring-mvc來(lái)處理控制層并通過(guò)攔截器openSessionInViewInterceptor改變spring調(diào)用session的流程。這里介紹最簡(jiǎn)單的spring-mvc的使用方法,首先和struts一樣要定義一個(gè)HttpServlet來(lái)總體控制請(qǐng)求的處理類(lèi)以及容器的初始化,struts是ActionServlet,Spring是DispatcherServlet,如圖:

如同struts的控制器都繼承于Action類(lèi)一樣,spring的控制器都要實(shí)現(xiàn)controller接口

我們通過(guò)注入的方式傳入usersDAO進(jìn)行數(shù)據(jù)操作(通常這個(gè)操作應(yīng)該是放到Service層,這里為了方便講解沒(méi)有加入Service層),這個(gè)DAO是myeclipse自動(dòng)生成的原封不動(dòng),這里不再貼出來(lái)了。
幾句話(huà)的意思很清楚,調(diào)用findByID方法把ID為1000的用戶(hù)實(shí)體查出來(lái),這個(gè)時(shí)候按道理session已經(jīng)關(guān)閉了,下面調(diào)用user.getNewses方法獲取這個(gè)用戶(hù)發(fā)布的新聞信息的一個(gè)set集合,打印條數(shù),系統(tǒng)顯示:

沒(méi)有報(bào)session 已經(jīng)關(guān)閉的錯(cuò)誤,原因是spring-config中作了配置,在配置之前我們要對(duì)spring-mvc做一些初步的了解。
配合DispatcherServlet我們也要定義相關(guān)的映射,能夠?qū)⒄?qǐng)求跳轉(zhuǎn)到對(duì)應(yīng)的控制器,先看看usersDAO

然后是我們的控制器LoginSpring,這里沒(méi)有對(duì)跳轉(zhuǎn)作處理,因?yàn)槲覀兊闹攸c(diǎn)不是mvc而是解決延遲加載。

最后是我們的映射和針對(duì)這些控制器所配置的攔截器opensessioninviewinterceptor

這樣配置了以后管理session的就不是我們的hibernatetemplate了而是我們配置的攔截器,他會(huì)保持session直到我們的控制器做完了所有的事情才關(guān)閉,也就是我們調(diào)用user.getNewses方法的時(shí)候是打開(kāi)的,因此能得到滿(mǎn)意的結(jié)果。
2、openSessionInViewFilter
當(dāng)我們的系統(tǒng)的控制層要使用struts的時(shí)候,我們就不能使用spring的攔截器了,因個(gè)這個(gè)攔截器是基于DispatcherServlet的,我們只有在web.xml中配置過(guò)濾器來(lái)改變session的流程。首先簡(jiǎn)要介紹一下struts+spring集成,看看struts的配置文件:

每個(gè)Action的type都設(shè)定為org.springframework.web.struts.DelegatingActionProxy,并在最下面配置spring的配置文件的路徑,當(dāng)我們要使用過(guò)濾器的時(shí)候這個(gè)配置文件必須只存放Action的實(shí)際映射,下面是spring-action.xml,里面只有一個(gè)action:

這里不需要指定id,只需要指定別名,這個(gè)別名會(huì)自動(dòng)匹配struts配置文件中對(duì)應(yīng)的path, DelegatingActionProxy會(huì)自動(dòng)從插件所配置的spring配置文件中尋找匹配的bean并實(shí)例化,當(dāng)然也會(huì)完成注入的過(guò)程。知道怎么將兩個(gè)框架正和使用以后,我們來(lái)配置過(guò)濾器,見(jiàn)web.xml:

這里使用過(guò)濾器的時(shí)候一定要附上所有spring配置文件并在web容器(tomcat)啟動(dòng)時(shí)加載和初始化。這里spring-action.xml存放的Struts的Action的映射,spring-config.xml中存放其他的bean。
最后再看看這個(gè)Action,和上面的一樣的代碼。

這樣的話(huà)當(dāng)我們使用延遲加載的時(shí)候調(diào)用的就是HibernateTamplate的代理類(lèi),能夠讓Spring在請(qǐng)求開(kāi)始的時(shí)候打開(kāi)Session,響應(yīng)結(jié)束前關(guān)閉Session,這樣就不會(huì)存在Session關(guān)閉的錯(cuò)誤了。但是當(dāng)我們?cè)鰟h改的時(shí)候,又會(huì)出現(xiàn)下面的問(wèn)題:

為什么會(huì)有這樣的問(wèn)題呢,因?yàn)?/span>Hibernate有自己的事務(wù)策略,我們?cè)?/span>Spring的OpenSessionInView中打開(kāi)Session是以只讀的方式來(lái)管理事務(wù),這樣進(jìn)行增刪改的時(shí)候就會(huì)出現(xiàn)錯(cuò)誤。如何解決,先看看Spring的事務(wù)處理。
三、聲明式事務(wù)處理
聲明式事務(wù)處理是springAOP思想的一個(gè)擴(kuò)展,事務(wù)處理是一個(gè)典型的系統(tǒng)功能,因此通過(guò)將事務(wù)處理封裝在一個(gè)切面中進(jìn)行處理以分離具體業(yè)務(wù)操作和系統(tǒng)功能的方式是最好的設(shè)計(jì)層面的選擇。
首先我們要配置一個(gè)PlatformTransactionManager接口的一個(gè)實(shí)現(xiàn)用來(lái)控制事務(wù)流程(commit和rollback),如果我們是spring+hibernate,框架集成的話(huà),我們就要配置hibernate專(zhuān)用的PlatformTransactionManager實(shí)現(xiàn):

我們模擬一個(gè)實(shí)例就是兩個(gè)銀行之間的轉(zhuǎn)賬,MsBankDAO民生銀行DAO;ZsBankDAO招商銀行DAO。這兩個(gè)DAO都是用myeclipse生成,這里不作介紹,我們的業(yè)務(wù)邏輯對(duì)象BankBO的transferMoney方法來(lái)完成這個(gè)轉(zhuǎn)賬操作:

其中if語(yǔ)句中的兩個(gè)操作就是一個(gè)典型的粗粒度事務(wù),我們使用聲明的方式來(lái)進(jìn)行事務(wù)處理就無(wú)需在這里面加入任何關(guān)于事務(wù)的代碼。
既然是使用AOP代理BO,我們就得有一個(gè)BankBOImpl的代理類(lèi):


然后我們模擬一個(gè)事故:

將民生銀行的修改方法拋出一個(gè)錯(cuò)誤
我們要傳入被代理類(lèi)的接口以及實(shí)現(xiàn),建一個(gè)主函數(shù)來(lái)測(cè)試一下:

注意這里要通過(guò)代理類(lèi)boProxy來(lái)返回BankBO的一個(gè)臨時(shí)實(shí)現(xiàn),運(yùn)行結(jié)果:

在查看數(shù)據(jù)庫(kù)中的數(shù)據(jù)并沒(méi)有改變。
四、解決ReadOnly事務(wù)策略問(wèn)題
繼續(xù)第二節(jié)我們的問(wèn)題,Hibernate的事務(wù)策略是需要配置的,Spring為了能以AOP的方式來(lái)管理事務(wù),就必須提供Hibernate事務(wù)策略和事務(wù)操作的封裝,也就是我們上面提供的PlatFormTransactionManager接口的Hibernate持久層實(shí)現(xiàn)HibernateTransactionManager,
Spring在處理事務(wù)的時(shí)候會(huì)將這個(gè)底層的封裝體傳到TransactionTemplate進(jìn)行初始化事務(wù)處理流程和參數(shù),我們?cè)诳碒ibernateTeamplat是我們所熟知的Hibernate的Session的API的封裝,他的父類(lèi)HibernateAccessor中已經(jīng)定義好了5種Hibernate的事務(wù)策略,其中就有Flush_NEVER策略和FLUSH_AUTO等策略,如果我們平時(shí)增刪改的時(shí)候沒(méi)有用到HibernateTamplate就會(huì)在操作的時(shí)候由Hibernate來(lái)管理事務(wù),但這個(gè)時(shí)候由于Opensessioninview的緣故為了防止在延遲加載的時(shí)候改動(dòng)持久層刷新策略已經(jīng)被設(shè)定為Flush_Never,也就是說(shuō)增刪改的時(shí)候也會(huì)以只讀的刷新模式來(lái)處理,這當(dāng)然會(huì)報(bào)錯(cuò),按照異常所說(shuō)的要把前者換成后者就行了,但這樣就沒(méi)辦法保證延遲加載的時(shí)候的安全性,因?yàn)楹苡锌赡苓@個(gè)PO對(duì)象會(huì)被傳到View層被隨意改動(dòng),如果能夠在讀的時(shí)候只讀,在寫(xiě)的時(shí)候由能夠及時(shí)刷新就能解決問(wèn)題了,如果是編程式事務(wù)處理的話(huà),我們就必須分別在查詢(xún)和增刪改的時(shí)候更換刷新模式,這將會(huì)很麻煩,還好Spring提供了聲明式事務(wù)處理,提供了一種擴(kuò)展性很高的解決方案。解決的方法很簡(jiǎn)單,讓Spring來(lái)全權(quán)管理事務(wù),在省事的同時(shí)也更好的劃分了層次的關(guān)系,避免了Service層涉及事務(wù)策略這類(lèi)的系統(tǒng)功能,將AOP思想體現(xiàn)的淋漓盡致。看看配置:
Find開(kāi)頭的方法被設(shè)定為只讀,其他方法設(shè)定為auto(默認(rèn)),讓所有Service結(jié)尾的類(lèi)的方法都處在事務(wù)中(包括不需要事務(wù)的方法),雖然消耗了一定的系統(tǒng)資源,但卻讓我們不必再為事務(wù)的問(wèn)題操心,把精力集中到業(yè)務(wù)邏輯中。
--
學(xué)海無(wú)涯