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

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

如同struts的控制器都繼承于Action類一樣,spring的控制器都要實現controller接口

我們通過注入的方式傳入usersDAO進行數據操作(通常這個操作應該是放到Service層,這里為了方便講解沒有加入Service層),這個DAO是myeclipse自動生成的原封不動,這里不再貼出來了。
幾句話的意思很清楚,調用findByID方法把ID為1000的用戶實體查出來,這個時候按道理session已經關閉了,下面調用user.getNewses方法獲取這個用戶發布的新聞信息的一個set集合,打印條數,系統顯示:

沒有報session 已經關閉的錯誤,原因是spring-config中作了配置,在配置之前我們要對spring-mvc做一些初步的了解。
配合DispatcherServlet我們也要定義相關的映射,能夠將請求跳轉到對應的控制器,先看看usersDAO

然后是我們的控制器LoginSpring,這里沒有對跳轉作處理,因為我們的重點不是mvc而是解決延遲加載。

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

這樣配置了以后管理session的就不是我們的hibernatetemplate了而是我們配置的攔截器,他會保持session直到我們的控制器做完了所有的事情才關閉,也就是我們調用user.getNewses方法的時候是打開的,因此能得到滿意的結果。
2、openSessionInViewFilter
當我們的系統的控制層要使用struts的時候,我們就不能使用spring的攔截器了,因個這個攔截器是基于DispatcherServlet的,我們只有在web.xml中配置過濾器來改變session的流程。首先簡要介紹一下struts+spring集成,看看struts的配置文件:

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

這里不需要指定id,只需要指定別名,這個別名會自動匹配struts配置文件中對應的path, DelegatingActionProxy會自動從插件所配置的spring配置文件中尋找匹配的bean并實例化,當然也會完成注入的過程。知道怎么將兩個框架正和使用以后,我們來配置過濾器,見web.xml:

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

這樣的話當我們使用延遲加載的時候調用的就是HibernateTamplate的代理類,能夠讓Spring在請求開始的時候打開Session,響應結束前關閉Session,這樣就不會存在Session關閉的錯誤了。但是當我們增刪改的時候,又會出現下面的問題:

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

我們模擬一個實例就是兩個銀行之間的轉賬,MsBankDAO民生銀行DAO;ZsBankDAO招商銀行DAO。這兩個DAO都是用myeclipse生成,這里不作介紹,我們的業務邏輯對象BankBO的transferMoney方法來完成這個轉賬操作:

其中if語句中的兩個操作就是一個典型的粗粒度事務,我們使用聲明的方式來進行事務處理就無需在這里面加入任何關于事務的代碼。
既然是使用AOP代理BO,我們就得有一個BankBOImpl的代理類:


然后我們模擬一個事故:

將民生銀行的修改方法拋出一個錯誤
我們要傳入被代理類的接口以及實現,建一個主函數來測試一下:

注意這里要通過代理類boProxy來返回BankBO的一個臨時實現,運行結果:

在查看數據庫中的數據并沒有改變。
四、解決ReadOnly事務策略問題
繼續第二節我們的問題,Hibernate的事務策略是需要配置的,Spring為了能以AOP的方式來管理事務,就必須提供Hibernate事務策略和事務操作的封裝,也就是我們上面提供的PlatFormTransactionManager接口的Hibernate持久層實現HibernateTransactionManager,
Spring在處理事務的時候會將這個底層的封裝體傳到TransactionTemplate進行初始化事務處理流程和參數,我們在看HibernateTeamplat是我們所熟知的Hibernate的Session的API的封裝,他的父類HibernateAccessor中已經定義好了5種Hibernate的事務策略,其中就有Flush_NEVER策略和FLUSH_AUTO等策略,如果我們平時增刪改的時候沒有用到HibernateTamplate就會在操作的時候由Hibernate來管理事務,但這個時候由于Opensessioninview的緣故為了防止在延遲加載的時候改動持久層刷新策略已經被設定為Flush_Never,也就是說增刪改的時候也會以只讀的刷新模式來處理,這當然會報錯,按照異常所說的要把前者換成后者就行了,但這樣就沒辦法保證延遲加載的時候的安全性,因為很有可能這個PO對象會被傳到View層被隨意改動,如果能夠在讀的時候只讀,在寫的時候由能夠及時刷新就能解決問題了,如果是編程式事務處理的話,我們就必須分別在查詢和增刪改的時候更換刷新模式,這將會很麻煩,還好Spring提供了聲明式事務處理,提供了一種擴展性很高的解決方案。解決的方法很簡單,讓Spring來全權管理事務,在省事的同時也更好的劃分了層次的關系,避免了Service層涉及事務策略這類的系統功能,將AOP思想體現的淋漓盡致??纯磁渲茫?/span>
Find開頭的方法被設定為只讀,其他方法設定為auto(默認),讓所有Service結尾的類的方法都處在事務中(包括不需要事務的方法),雖然消耗了一定的系統資源,但卻讓我們不必再為事務的問題操心,把精力集中到業務邏輯中。
--
學海無涯