1. 程式人生 > 實用技巧 >最最最詳細SSH框架整合技術

最最最詳細SSH框架整合技術

Spring 與 Hibernate 整合

除了 JdbcTemplate 外,Spring 還可通過 Hibernate 來完成 Dao 的工作。即將 Spring 與Hibernate 進行整合。
舉例:專案 spring_hibernate(在 dao_jdbcTemplate 基礎上修改)

1、匯入 Jar 包

除了 Spring 的基本 Jar 包外,還需要以下幾種 Jar 包:
(1)Spring AOP 的兩個 Jar 包
在這裡插入圖片描述
在這裡插入圖片描述
(2)AspectJ 的兩個 Jar 包
在這裡插入圖片描述
在這裡插入圖片描述
(3)Spring 的 JDBC 的 Jar 包
在這裡插入圖片描述
(4)Spring 整合 ORM 的 Jar 包
Spring 整合 ORM 框架的 Jar 包,在 Spring 框架解壓目錄下的 libs 目錄中。

在這裡插入圖片描述
(5)Spring 的事務 Jar 包
Spring 的事務 Jar 包,在 Spring 框架解壓目錄下的 libs 目錄中。
在這裡插入圖片描述
(6)Hibernate 的基本 Jar 包
Hibernate 的基本 Jar 包中已經包含了 C3P0 資料來源的 Jar 包,且版本高於 Spring 中提供
的版本。所以直接使用 Hibernate 中的即可。
Hibernate 的基本 Jar 包中已經包含了 MySql 的驅動 Jar 包,所以不用再匯入了。
在這裡插入圖片描述
在這裡插入圖片描述

2、實體對映檔案的配置

在這裡插入圖片描述

3、註冊資料來源

Spring 配置檔案中資料來源與 jdbc 屬性檔案的註冊,與 template_jdbc 專案中的相同。

在這裡插入圖片描述

4、配置 SessionFactory

Spring 的精髓是,所有的 Bean 均由 Spring 容器統一管理,所以在 SPring 中若要使用Hibernate,就需要將 SessionFactory 交由 Spring 來管理。
配置 SessionFactory 的 Bean,將 hibernate.cfg.xml 檔案替換掉。使用的實現類為LocalSessionFactoryBean,注意,是 hibernate5 包下的。其用於設定的屬性主要有三個:資料來源,對映檔案,及 hibernate 特性。其設定內容,與 Hibernate 主配置檔案的基本相同。
在這裡插入圖片描述
(1)注意對映檔案的註冊方式

這裡直接指定的為映入檔案所在的包,其中可能包含多個對映檔案。也可寫為如下形式:
若只有一個對映檔案
在這裡插入圖片描述
對於有多個對映檔案,可使用< list/>列出:
在這裡插入圖片描述
當然,若有多個存放對映檔案的目錄,也可使用< list/>列出。
(2)注意當前 Session 上下文所使用的類
至於 Hibernate 特性的設定,除了 hibernate.current_session_context_class 外,其餘 key與 Hibernate 主配置檔案中的屬性名相同,值也相同。
屬性 hibernate.current_session_context_class,用於指定當前 Session 所執行的上下文環境。其值不再是 thread,而是 SpringSessionContext,表示 Session 將交由 Spring 容器來管理。
在這裡插入圖片描述

5、定義 Dao 實現類

由於 Dao 實現類要通過 Hibernate 來操作 DB,所以在該類中需要獲取到 Session 工廠物件 SessionFactory。當然,最終目的是獲取到 Hibernate 的 Session 物件。而 SessionFactory 物件的建立,也是由 Spring 容器來管理的,所以,需要在 Dao 實現類中新增 SessionFactory 屬性,以便 Spring 容器通過 setter 將 Session 工廠注入。
注意,Dao 實現類無需繼承自任何父類,只要將 SessionFactory 定義為 set 屬性即可。而 Hibernate 的 Session 物件,是通過 SessionFactory 的 getCurrentSession()方法獲取的。
在這裡插入圖片描述

6、註冊 Dao 與 Service 的 Bean

在 Spring 配置檔案中註冊 dao 與 service 物件,注意需將 sessionFactory 注入給 Dao。
在這裡插入圖片描述

7、配置事務管理器

由於 Hibernate 的 Session 要求必須在事務環境下才能執行,所以在 Spring 中使用Hibernate,必須要配置事務管理器,以開啟事務環境。此時使用的事務管理器為HibernateTransactionManager。需要注意的是,使用 Jdbc 的事務管理器,需要注入一個數據源 dataSource,而使用 Hibernate 的事務管理器,則需要注入一個 sessionFactory 屬性。
在這裡插入圖片描述

8、配置事務通知及顧問

在這裡插入圖片描述

9、在 Spring 中一般不使用 Hibernate 模板物件(瞭解)

在 SSH 中,Dao 獲取 Hibernate 的 Session,還有一種方式,通過 HibernateTemplate 獲取。但一般不使用 Hibernate 模板,而是通過在 Dao 中定義 SessionFactory,由容器向其注入。在Dao 中,通過 SessionFactory 的方法 getCurrentSession()獲取 Session。
為什麼不使用 Hibernate 模板呢?檢視 HibernateTemplate 原始碼,發現 Hibernate 模板物件中方法的執行所使用的 Session,就是通過 getCurrentSession()獲取的。使用模板物件後,遇到複雜查詢,還需要使用其 execute()方法,通過回撥介面 HibernateCallback 在獲取到Session 後,再執行 HQL 語句。轉了一大圈後,又繞到了獲取當前 Session,而且獲取的這個Session 也是通過 getCurrentSession()獲得的。
所以,最終不如放棄 Hibernate 模板,直接通過 getCurrentSession()獲得 Session 物件。
下面以模板物件的 save()方法的原始碼為例,來檢視其 Session 的獲取情況。
在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述

Spring 在 Web 專案中的使用

在 Web 專案中使用 Spring 框架,首先要解決在 Servlet 中(暫時不使用 Struts2)獲取到Spring 容器的問題。只要在 View 層獲取到了 Spring 容器,便可從容器中獲取到 Service 物件。

1、不使用 Spring 的 Web 外掛

舉例:springweb 專案(在 dao_hibernate 基礎上修改)
Step1:新建一個 Web Project
Step2:定義 index 頁面
在這裡插入圖片描述
Step3:定義 LoginServlet(重點程式碼)
在這裡插入圖片描述Step4:定義 success 頁面
在這裡插入圖片描述
Step5:複製 dao_hibernate 中內容
將 dao_hibernate 專案中以下內容複製到當前專案中:
(1)Service 層、Dao 層全部程式碼
(2)配置檔案 applicationContext.xml 及 jdbc.properties
(3)所有 Jar 包
Step6:直接釋出執行
Step7:執行結果分析
當表單提交,跳轉到 success.jsp 後,多重新整理幾次頁面,檢視後臺輸出,發現每重新整理一次頁面,就 new 出一個新的 Spring 容器。即,每提交一次請求,就會建立一個新的 Spring 容器。對於一個應用來說,只需要一個 Spring 容器即可。所以,將 Spring 容器的建立語句放在 Servlet 的 doGet()或 doPost()方法中是有問題的。
在這裡插入圖片描述
此時,可以考慮,將 Spring 容器的建立放在 Servlet 進行初始化時進行,即執行 init()方法時執行。並且,Servlet 還是單例多執行緒的,即一個業務只有一個 Servlet 例項,所有執行該業務的使用者執行的都是這一個 Servlet 例項。這樣,Spring 容器就具有了唯一性了。
但是,Servlet 是一個業務一個 Servlet 例項,即 LoginServlet 只有一個,但還會有StudentServlet、TeacherServlet 等。每個業務都會有一個 Servlet,都會執行自己的 init()方法,也就都會建立一個 Spring 容器了。這樣一來,Spring 容器就又不唯一了。

2、使用 Spring 的 Web 外掛

舉例:springweb2 專案 (在 springweb 專案基礎上修改)
對於 Web 應用來說,ServletContext 物件是唯一的,一個 Web 應用,只有一個ServletContext 物件。該物件是在 Web 應用裝載時初始化的,即在 Web 應用裝載時會自動執行介面 ServletContext 的初始化方法。該初始化方法在整個應用中只會執行一次。若將 Spring
容器的建立語句放到 ServletContext 的初始化方法中執行,並將建立好的 Spring 容器作為ServletContext 的屬性放入其中。以後再需要 Spring 容器,直接讀取該屬性值即可。
ServletContext 物件生命週期與 Web 應用的相同。即放在其中的屬性為全域性屬性。所以,放入 ServletContext 中的 Spring 容器,在整個應用的生命週期中,均可被訪問。這樣就可以保證 Spring 容器在 Web 應用中的唯一性了。
上述的這些工作,已經被封裝在瞭如下的 Spring 的 Jar 包的相關 API 中:spring-web-4.2.1.RELEASE.jar
(1)匯入 Jar 包
在 Web 項 目 中 使 用 Spring , 需 要 導 入 Spring 對 Web 的 支 持 包 :
spring-web-4.2.1.RELEASE.jar。
該包在 Spring 框架的解壓目錄下的 libs 目錄中。
在這裡插入圖片描述
(2)註冊監聽器 ContextLoaderListener
若要在 ServletContext 初始化 時 創 建 Spring 容 器 , 就 需 要 使 用 監 聽 器 接 口ServletContextListener 對 ServletContext 進行監聽。Spring 為該監聽器介面定義了一個實現類
ContextLoaderListener,專門用於在 ServletContext 初始化時建立 Spring 容器。檢視原始碼:
在這裡插入圖片描述
在這裡插入圖片描述
注意,監聽器是需要在 web.xml 中註冊的。
在這裡插入圖片描述
(3)指定 Spring 配置檔案的位置
ContextLoaderListener 在對 Spring 容器進行建立時,需要載入 Spring 配置檔案。其預設的 Spring 配置檔案位置與名稱為:WEB-INF/applicationContext.xml。但,一般會將該配置檔案放置於專案的 classpath 下,即 src 下,所以需要在 web.xml 中對 Spring 配置檔案的位置及名稱進行指定。
在這裡插入圖片描述
從監聽器 ContextLoaderListener 的父類 ContextLoader 的原始碼中可以看到其要讀取的配置檔案位置引數名稱 contextConfigLocation。
在這裡插入圖片描述
(4)修改 Spring 配置檔案中對映檔案路徑的寫法
匯入的 Jar 包 spring-web 中程式碼要求,Spring 配置檔案中對映檔案的路徑前必須新增classpath:,以表示其在類路徑下。
在這裡插入圖片描述
(5)通過 WebApplicationContextUtils 獲取 Spring 容器
工具類 WebApplicationContextUtils 有一個方法專門用於從 ServletContext 中獲取 Spring容器物件:getRequiredWebApplicationContext(ServletContext sc)
查其原始碼,看其呼叫關係,就可看到其是從 ServletContext 中讀取的屬性值,即 Spring容器
在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述
重新整理 success 頁面後,可看到程式碼中使用的 Spring 容器為同一個物件。
在這裡插入圖片描述

Spring 與 Struts2 整合

Spring 與 Struts2 整合的目的有兩個:
(1)在 Struts2 的 Action 中,即 View 層,獲取到 Service。然後就實現了在 Struts2 項
目中 View 層、Service 層、Dao 層的聯通。
(2)將 Action 例項交由 Spring 容器管理。
為了更為清楚的講解這兩個目的的實現,下面分兩步逐步來完成這兩個目的。
(1)Action 例項由 Struts2 自己建立,Service 由容器注入給 Action。 (2)Action 例項由 Spring 容器建立。
當然,以上兩步均需要匯入 Struts2 的基本 Jar 包。

1、Action 例項由 Struts2 自己建立,Servcie 由容器注入給 Action

舉例:專案 spring_struts2(在 springweb2 專案基礎上修改)
(1)匯入 Jar 包
此方式除了要匯入 Spring 與 Struts 的 Jar 包外,還需要匯入 Strut2 與 Spring 連線的外掛Jar 包 struts2-spring-plugin。該 Jar 包在 Struts2 框架解壓目錄的 lib 目錄中。
在這裡插入圖片描述
(2)將 Service 宣告為 Action 的屬性
程式碼中,只需將 Servcie 定義為 Action 的屬性即可。需要注意,預設情況下,要使該屬
性名稱與 Spring 配置檔案中的 Service 的 id 相同。
在這裡插入圖片描述
在這裡插入圖片描述
從 Struts2 角度來說,Struts2 要與 Spring 整合,要求開啟一個常量 struts.objectFactory,用於將 Action 交由 Spring 容器來管理。其在 Struts2 中預設是關閉的。
在這裡插入圖片描述
struts.objectFactory 的 開 啟 會 使 另 一 常 量 , 按 名 稱 自 動 裝 配 的 常 量Beanstruts.objectFactory.spring.autoWire 自動起作用。該常量預設是開啟的。在 default.properties 中可以看到。
在這裡插入圖片描述
由於匯入了 struts2-spring-plugin 的 Jar 包,開啟它,可看到一個檔案 struts-plugin.xml。開啟這個檔案,可看到
在這裡插入圖片描述
開啟 struts-plugin.xml 檔案,可看到其已開啟了 strus.objectFactory 常量。
在這裡插入圖片描述
所以,無需任何配置,直接將 Service 作為 Action 的屬性,並使屬性名與 Spring 中的ServiceBean 的 id 名相同,即可完成將 Service 注入到 Action。

2、Action 例項由 Spring 容器建立

舉例:專案 spring_struts2_hibernate(在 spring_struts2 基礎上修改)
上面的程式完成了 Service 由容器注入給 Action 的目的。但不足是,Action 是由 Struts2自己建立維護的。而 SSH 的精髓是,所有的 Bean 均由 Spring 容器統一管理。所以這一步要實現的目的是將 Action 交由 Spring 來管理。
方式二的用法:
(1)將 Service 宣告為 Action 的屬性
此時仍需將 Service 宣告為 Action 的屬性,只不過,對屬性名沒有要求。無需與 Spring配置檔案中 Service 的 Bean 的 id 相同。
(2)在 Spring 配置檔案中註冊 Action 的 Bean
在 Spring 的配置檔案中將 Action 作為一個普通 Bean 進行定義,並在配置檔案中完成Service 的注入。
(3)設定 Action 的 Bean 的 scope 為 prototype
Action 是多例的,但由於 Spring 的預設建立的為 singleton,即單例的。所以,需要設定 scope=“prototype”。
(4)Struts2 配置檔案中的 class 使用偽類名
Struts2 配置檔案中的標籤的 class 屬性值不再是 Action 的全類名了,而是個偽類名:Spring 配置檔案中該 Bean 的 id 名。
在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述

SSH 整合 Jar 包總結

1、Struts2 中的 Jar 包 (16 個)

(1)Struts2 的基本 Jar 包 (13 個)
Struts2框架的解壓目錄下apps/ struts2-blank.war中解壓後WEB-INF/lib下的13個Jar包。
(2)Struts2 與 Spring 整合外掛 Jar 包 在 Struts2 框架的解壓目錄下/lib 目錄下:
struts2-spring-plugin-2.3.24.jar
(3)Struts2 與 JSon 整合外掛 Jar 包
若專案使用了 Ajax,則需要整合了 JSon,此時還需匯入整合 JSon 的 Jar 包。在 Struts2框架的解壓目錄下/lib 目錄下:
struts2-json-plugin-2.3.24.jar
(4)Struts2 註解開發 Jar 包
若專案中使用了 Struts2 的註解開發,則需要匯入以下的包。在在 Struts2 框架的解壓目錄下/lib 目錄下:struts2-convention-plugin-2.3.24.jar

2、Spring 中的 Jar 包 (14 個)

(1)Spring 的基本 Jar 包
在這裡插入圖片描述
(2)AOP 開發需要的 Jar 包
spring-aop-4.2.1.RELEASE.jar
spring-aspects-4.2.1.RELEASE.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
(3)Spring 整合 ORM 所需 Jar 包
spring-orm-4.2.1.RELEASE.jar
(4)Spring JDBC 開發需要的 Jar 包
spring-jdbc-4.2.1.RELEASE.jar
spring-tx-4.2.1.RELEASE.jar
(5)Spring 事務管理需要的 Jar 包
spring-tx-4.2.1.RELEASE.jar(與上面的是同一個包)
(6)Spring 在 WEB 專案中使用所需 Jar 包
spring-web-4.2.1.RELEASE.jar

3、Hibernate 中的 Jar 包(19)

(1)reqired 目錄下所有 Jar (9 個)
在這裡插入圖片描述
(2)optional/ehcache/slf4j-api-1.6.1.jar
(3)optional/c3p0 目錄下所有 Jar (3 個)
在這裡插入圖片描述
(4)jpa/hibernate-entitymanager-5.0.1.Final.jar
(5)jpa-metamodel-generator 目錄下的 Jar(1 個)
在這裡插入圖片描述
(6)若用到了 ehcache 二級快取,則需匯入 optional/ehcache 中的所有包。在這裡插入圖片描述
(7)junit-4.9.jar
(8)slf4j-log4j12-1.7.12.jar
(9)mysql 驅動

4、包衝突

在不同的框架中,若存在相同的 Jar 包,即使版本號不同,也將會引發包衝突問題,導致程式執行出錯。一般是捨棄低版本,保留高版本。
(1)javassist 的 Jar 包衝突
在 Hibernate 的 Jar 包中有一個 javassist 的 Jar 包(3.18.1 版本),而在 Struts2 中也有一個 javassist 的 Jar 包(3.11.0 版本)。保留 3.18.1 版本。
在這裡插入圖片描述
在這裡插入圖片描述
(2)C3P0 的 Jar 包衝突
在 Hibernate 框架的基本 Jar 包中曾引入了 C3P0 的 Jar 包(0.9.2.1 版本),但在 Spring中,也曾引入過 C3P0 的 Jar 包(0.9.1.2 版本)。保留 0.9.2.1 版本。
在這裡插入圖片描述
在這裡插入圖片描述
(4)log4j2 的 Jar 包衝突
在 Struts2 的基本 Jar 包引入了 log4j2 的兩個 Jar 包(2.2 版本),而在 Hibernate 的基本Jar 包中也引入了 log4j2 的兩個 Jar 包(2.3 版本)。保留 2.3 版本。
在這裡插入圖片描述
在這裡插入圖片描述

OpenSessionInView

1、問題的引出

當將事務織入到 Service 層後,在 Dao 層通過 load()等具有延遲載入功能的方法載入實體時,會出現異常。
舉例:專案 open_session_in_view
Step1:定義 index.jsp
在這裡插入圖片描述
Step2:定義 Action
在這裡插入圖片描述
Step3:定義 Service 介面
在這裡插入圖片描述
Step4:定義 Service 實現類
在這裡插入圖片描述
Step5:定義 Dao 介面
在這裡插入圖片描述
Step6:定義 Dao 實現類
在這裡插入圖片描述
Step7:修改 struts.xml
在這裡插入圖片描述
Step8:修改 Spring 配置檔案
在這裡插入圖片描述

2、原因分析

View 層要讀取一個物件的某屬性值,所以在 View 層呼叫了 Service 層的物件獲取方法。
此時 Service 層就會去呼叫 Dao 層,欲從 DB 中讀取資料。但由於 Service 層並非是真正需要資料的一方,即 Servcie 層並非真正需要資料的詳情。又由於 Dao 層使用了延遲載入技術,所以返回給 Service 層一個數據為空的代理物件。當 Service 獲取到了這個物件後,馬上將這個物件返回給了 View 層。Service 層認為任務完成,所以將事務進行了關閉。由於 Session必須在事務環境下執行,且事務在提交或回滾後,會首先將 Session 關閉,而後關閉事務。
所以事務關閉前,Session 就已經消失。當 View 層收到 Service 層傳送來的物件,並使用其詳情時,發現為空。此時 View 層向 Service 層發出進行真正查詢請求時,Service 層的 Session
已經不存在,所以,會報出 Session 為空的異常。
在這裡插入圖片描述
當執行完 View 層的 user = service.find(5); 語句時,只是將一個無資料的 user 的位元組碼增強代理物件返回給了 user。但此時 Service 層的 Session 已經消失。
當執行 View 層的 int age = user.getAge(); 語句時,發現 user 無真正的值,所以要引發一個真正的 DB 查詢。但此時 Session 已經不存在了。
丟擲異常:org.hibernate.LazyInitializationException: could not initialize proxy - no Session

3、解決辦法

問題的主要原因是事務應用在業務層,而延遲載入使得對 Hibernate 的 Session 的連續使用放在了 View 層。若要 Session 不進行自動關閉,必須使其所在的事務環境不能在 Service層就結束,所以可以將 Session 的開啟放在 View 層。
只需在 web.xml 中註冊一個過濾器 OpenSessionInViewFilter(開啟 Session 在 View),只要使用者提交請求,就會先執行該過濾器,將 Session 開啟。
註冊過濾器時需要注意:
(1)該過濾器會自動從 Spring 配置檔案中查詢名稱為 sessionFactory 的 Bean。
檢視 OpenSessionInViewFilter 的原始碼:
在這裡插入圖片描述
若定義的不是這個名稱,則需通過初始化引數進行指定。
在這裡插入圖片描述
(2)該過濾器應註冊在 StrutsPrepareAndExecuteFilter 的前面,即在進入 Struts2 之前先執行過濾,否則還會出現同樣的問題。其註冊順序可以從 Struts2 的流程圖中看出。
在這裡插入圖片描述
過濾器的執行都是通過過濾器鏈串起來執行的,即當前的 Filter 通過在自己的 doFilter()方法中執行 chain.doFilter()來呼叫執行下一下過濾器。
檢視 StrutsPrepareAndExecuteFilter 的原始碼可以看出,只有當沒有 Action 要執行時,才可能會執行其它的過濾器,即呼叫 chain.doFilter()。否則,只要有 Action 能夠匹配上執行,那麼就沒有 chain.doFilter()方法要執行了,即 Struts2 的 StrutsPrepareAndExecuteFilter 將成為最後一個被執行的 Filter。
所以,只有將 OpenSessionInViewFilter 放到 Struts2 的過濾器
StrutsPrepareAndExecuteFilter 的前面才會被執行到。

SSH 全註解開發

在專案 spring_struts2_hibernate 基礎上修改。

1、新增 Struts2 與 Spring 註解

使用了 Struts2 註解後,無需再使用 struts.xml 了。然而 Strut2 通過在中使用偽類,在 Spring 中定義該 Action 的 Bean 的方式,將 Service 物件注入給了 Action。一旦刪除了struts.xml,在 Spring 中定義的 Action 的 Bean,將與 Struts2 無關。而這個關係,通過 Spring的註解可以再次建立。所以,不能在 SSH 中單獨演示 Struts2 的註解。
Step1:先將資料庫及表建好
在這裡插入圖片描述
Step2:修改 Dao 的實現類 UserDaoImpl
在這裡插入圖片描述
Step3:修改 Service 的實現類 UserServiceImpl
在這裡插入圖片描述
Step4:修改 LoginAction
在這裡插入圖片描述
Step5:在 Spring 配置檔案中註冊元件掃描的基本包
Step6:在 Spring 配置檔案中開啟 Spring 事務註解驅動
在這裡插入圖片描述
Step7:在 Spring 配置檔案中刪除 dao、service、action 的 Bean 定義
Step8:刪除 struts.xml
完成 Struts2 與 Spring 的註解。

2、新增 Hibernate 的註解

Step1:修改實體類 User
在這裡插入圖片描述
Step2:刪除對映檔案
Step3 : 在 Spring 配 置 文 件 的 SessionFactory 定 義 中 , 刪 除 映 射 文 件 的 相 關 配 置mappingResources
在這裡插入圖片描述
Step4:在 Spring 配置檔案的 SessionFactory 定義中,新增實體包掃描器。SSH 全註解完成。
在這裡插入圖片描述