免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
Spring 2 和 JPA 簡(jiǎn)介

開始之前

近十年來,構(gòu)建健壯、可維護(hù)的服務(wù)器端 Java 應(yīng)用程序的 “正確” 方式一直是 Java 2 企業(yè)版(J2EE)平臺(tái)的專屬領(lǐng)域。J2EE 應(yīng)用程序用企業(yè) JavaBean(EJB)技術(shù)構(gòu)建,運(yùn)行在方便部署并提供豐富的容器服務(wù)(例如數(shù)據(jù)庫連接和池的管理)的服務(wù)器之上。這些服務(wù)器還通過對(duì)重要特性(例如安全性和事務(wù))提供部署時(shí)聲明式控制帶來了附加價(jià)值。雖然功能豐富,J2EE 開發(fā)過程包含了許多煩瑣和重復(fù)的任務(wù),還要?jiǎng)?chuàng)建和維護(hù)大量的源代碼文件。

許多輕量級(jí) Java 框架聲稱簡(jiǎn)化了服務(wù)器應(yīng)用程序開發(fā),但論及成熟和流行的程度,Spring 框架無可匹敵(請(qǐng)參閱 參考資料)。Spring 目前的版本是 Spring 2,從第 1 天起,其設(shè)計(jì)目的就在于簡(jiǎn)化服務(wù)器應(yīng)用程序的構(gòu)建過程。Spring 的開發(fā)沒有采用一體化的容器角度,而是為應(yīng)用程序的需求提供恰到好處的支持,因此不會(huì)因包羅萬象的容器環(huán)境造成負(fù)擔(dān)。Spring 消除了代碼膨脹:完全可以在容器之外編寫和測(cè)試業(yè)務(wù)對(duì)象,從而讓業(yè)務(wù)對(duì)象代碼保持簡(jiǎn)單、可測(cè)試、可維護(hù)和可重用。

隨著 Java EE 5 和 EJB 3.0 的出現(xiàn),J2EE 社區(qū)準(zhǔn)備好了迎接 Spring 開發(fā)人員社區(qū)。EJB 3.0 支持輕量級(jí) POJO(Plain Old Java Objects,老式普通 Java 對(duì)象)作為 EJB 組件的概念,并引入了 Java 持久性 API (JPA),JPA 是可以在容器外部運(yùn)行的持久性機(jī)制。這種持久性機(jī)制自動(dòng)實(shí)現(xiàn)業(yè)務(wù)對(duì)象和外部關(guān)系數(shù)據(jù)庫之間的信息移動(dòng)。Spring 框架的版本 2 繼續(xù)了自己的發(fā)展,也利用 JPA 作為持久性機(jī)制。

在這份教程中,您將使用 Spring 2 和 JPA 持久性。將用 Spring 2 框架創(chuàng)建一個(gè)服務(wù)器應(yīng)用程序,完成時(shí)可以訪問 DB2 Express-C 數(shù)據(jù)庫。Eclipse IDE 可以方便 Java 應(yīng)用程序的開發(fā)并促進(jìn)對(duì) Spring 2 框架的研究。

關(guān)于本教程

本教程采用按編碼學(xué)習(xí)這種純粹、簡(jiǎn)單的方式,目的是在盡可能短的時(shí)間內(nèi),指導(dǎo)您了解 Spring 2 框架的使用和應(yīng)用程序。您將在 Spring 2 框架的協(xié)助下,從頭開始逐步構(gòu)建一個(gè) Web 應(yīng)用程序。

這份教程并不想試圖覆蓋 Spring 2 的全部特性和選項(xiàng)。而是重點(diǎn)關(guān)注了使用 Spring 開發(fā)服務(wù)器應(yīng)用程序的一種切實(shí)有效的方式。鼓勵(lì)您參考其他 Spring 2 資源獲得與這個(gè)框架有關(guān)的更高級(jí)的應(yīng)用程序和技術(shù)(請(qǐng)參閱 參考資料)。

您將經(jīng)歷一個(gè)完整的 “從概念到應(yīng)用程序” 的周期,包括:

  • 執(zhí)行域分析
  • 為業(yè)務(wù)對(duì)象和服務(wù)編寫代碼
  • 對(duì)業(yè)務(wù)對(duì)象進(jìn)行單元測(cè)試
  • 使用 Sprint JPA 在不帶來過高成本的情況下為業(yè)務(wù)對(duì)象添加數(shù)據(jù)訪問代碼
  • 用 Spring DAO(數(shù)據(jù)訪問對(duì)象)實(shí)現(xiàn)服務(wù)
  • 針對(duì) DB2® Express-C 為服務(wù)編寫集成測(cè)試代碼
  • 為基于 Spring 模型-視圖-控制器(MVC)的用戶界面創(chuàng)建控制器
  • 為用戶界面設(shè)計(jì)視圖
  • 創(chuàng)建應(yīng)用程序的可部署 WAR 文件
  • 在 Apache Tomcat 服務(wù)器上配置和部署應(yīng)用程序

學(xué)完本教程后,您應(yīng)能夠理解 Spring 2 框架的工作原理以及它能為創(chuàng)建高度組件化、可維護(hù)的 Web 應(yīng)用程序帶來怎樣的幫助。在構(gòu)建這樣一個(gè)應(yīng)用程序的過程中,您將獲得實(shí)踐經(jīng)驗(yàn),還能把這里學(xué)到許多技術(shù)應(yīng)用到日常的開發(fā)任務(wù)中。





回頁首


先決條件

您應(yīng)當(dāng)熟悉基本的面向?qū)ο笤O(shè)計(jì)概念和使用 Java SE 5 的 Java 開發(fā),包括泛型。您還應(yīng)當(dāng)熟悉關(guān)系數(shù)據(jù)庫概念,對(duì)于如何在 DB2 Express-C 中設(shè)置新數(shù)據(jù)庫也應(yīng)當(dāng)有基本的了解。

您還應(yīng)熟悉測(cè)試術(shù)語,包括單元測(cè)試和集成測(cè)試。最好有測(cè)試框架(例如 JUnit)方面的實(shí)際經(jīng)驗(yàn),但并非必需。

您應(yīng)當(dāng)有 Eclipse 方面的實(shí)際經(jīng)驗(yàn),能夠在 Eclipse 內(nèi)創(chuàng)建新 Java 項(xiàng)目、編譯 Java 代碼和調(diào)試項(xiàng)目。





回頁首


系統(tǒng)需求

要試驗(yàn)這份教程中的工具和示例,硬件配置需求為:至少帶有 512MB 內(nèi)存(推薦 1GB)的系統(tǒng)。

需要安裝以下軟件:

Spring 2 框架操作概述

這一節(jié)介紹 Spring 2 框架,簡(jiǎn)要說明了它在常規(guī)服務(wù)器應(yīng)用程序開發(fā)中的優(yōu)勢(shì)。

用 Spring 構(gòu)建應(yīng)用程序

從傳統(tǒng) API 的意義上來說,Spring 并不是編程框架。多數(shù)情況下,框架由 API 和可以在應(yīng)用程序中使用的代碼主干的集合組成。

Spring 2 被設(shè)計(jì)成非侵入的。實(shí)際上,它允許您編寫對(duì)象和業(yè)務(wù)邏輯,就像 Spring 不存在一樣。在編寫和測(cè)試這些對(duì)象之后,可以添加 Spring 2 支持特性。在某些情況下,添加這些特性時(shí)甚至不需要重新編譯源代碼。

例如,可以先創(chuàng)建和測(cè)試一個(gè) Java employee 對(duì)象,然后添加 Spring 2 支持,把對(duì)象的實(shí)例保存到關(guān)系數(shù)據(jù)庫中。也可以先編寫更新銀行賬戶的代碼,然后應(yīng)用 Spring 2 的事務(wù)功能來確保數(shù)據(jù)的完整性。

圖 1 顯示了典型的基于 Web 的服務(wù)器應(yīng)用程序。用戶通過用戶界面與應(yīng)用程序交互。應(yīng)用程序邏輯作為一組業(yè)務(wù)對(duì)象上的操作來執(zhí)行,構(gòu)成應(yīng)用程序的域模型。業(yè)務(wù)對(duì)象通常由保存在關(guān)系數(shù)據(jù)庫中的數(shù)據(jù)支持。


圖 1. 典型的基于 Web 的應(yīng)用程序的架構(gòu)

如果要從頭開發(fā)這個(gè)應(yīng)用程序,需要構(gòu)建圖 1 中的每個(gè)組件,并編寫定制代碼來實(shí)現(xiàn)對(duì)數(shù)據(jù)庫的訪問。

在使用 Spring 構(gòu)建應(yīng)用程序時(shí),可首先關(guān)注完善域模型??梢杂煤?jiǎn)單的 Java POJO 為系統(tǒng)中的對(duì)象建模,并把系統(tǒng)中的服務(wù)定義成標(biāo)準(zhǔn)的 Java 接口。這樣的設(shè)計(jì)使您可以獨(dú)立于 Spring 或者其他框架/庫來創(chuàng)建和測(cè)試域模型。

然后即可把額外的 Spring 特性應(yīng)用到應(yīng)用程序,以測(cè)試過的域模型為基礎(chǔ)。例如,可以用 Spring 的 JPA 支持添加對(duì)象持久性 —— 把數(shù)據(jù)保存到關(guān)系數(shù)據(jù)庫和從關(guān)系數(shù)據(jù)庫檢索數(shù)據(jù)的能力。





回頁首


裝入時(shí)增強(qiáng)

Spring 框架通過在類裝入的時(shí)候提供附加價(jià)值來保證非侵入性。在正常運(yùn)行 Java 應(yīng)用程序時(shí),JVM 只在需要類時(shí)才通過一組類裝入器裝入類。這是非常透明的,通常在不知覺的情況下發(fā)生。對(duì)于一定復(fù)雜的軟件,如 Eclipse IDE,可以這樣裝入數(shù)千個(gè)類。使用 Spring 框架,通過告訴 Spring 引擎(也叫作 Spring 容器)您的類如何搭配在一起、要給這些類添加什么特性,對(duì)框架進(jìn)行編程。Spring 引擎根據(jù)您的調(diào)配來構(gòu)建類。圖 2 以圖表的方式顯示了這個(gè)過程:


圖 2. Spring 框架操作

圖 2 顯示出:因?yàn)?Spring 引擎擁有對(duì)類的低級(jí)訪問,所以可以根據(jù)特定的配置,用額外特性增強(qiáng)它們。圖 2 還顯示了一種可以向 Spring 引擎提供指令的機(jī)制。典型情況下,這些指令以 XML bean 描述符文件的形式存在,在某些情況下,也可能以 Java 5 注釋的形式存在于源代碼內(nèi)。

例如,可以創(chuàng)建代表員工的簡(jiǎn)單 Java 對(duì)象,然后讓 Spring 引擎增強(qiáng)這些類,使得這些對(duì)象可以動(dòng)態(tài)地保存到關(guān)系數(shù)據(jù)庫表中,或從關(guān)系數(shù)據(jù)庫表檢索這些對(duì)象。本教程將介紹如何實(shí)現(xiàn)這些目的。





回頁首


經(jīng)典 API 支持

除了類裝入時(shí)增強(qiáng),Spring 2 還提供了經(jīng)典 API 支持,以封裝復(fù)雜和煩瑣的操作。圖 3 顯示了這份教程的示例應(yīng)用程序使用的 Spring 支持庫。請(qǐng)對(duì)比圖 3 和 圖 1


圖 3. Spring 2 增加的價(jià)值

可以把 Spring 2 的 JPA 支持與 Spring DAO API 結(jié)合使用,簡(jiǎn)化對(duì)關(guān)系數(shù)據(jù)庫的訪問。將用 Spring MVC 輕松地為應(yīng)用程序添加基于 Web 的用戶界面。
 

準(zhǔn)備 Spring

在這一節(jié),要開始構(gòu)建一個(gè)利用 Spring 2 框架的示例員工信息應(yīng)用程序。要確定和編碼應(yīng)用程序的業(yè)務(wù)對(duì)象、生成 setter 和 getter、編寫服務(wù)接口、并對(duì)類進(jìn)行單元測(cè)試。應(yīng)用程序開發(fā)周期的這個(gè)階段的執(zhí)行獨(dú)立于 Spring 框架。

在域模型分析中確定 POJO

這份教程的應(yīng)用程序設(shè)計(jì)方式的第一步就是 域模型分析 —— 通常叫作 “確定 POJO”。在這種情況下,POJO 代表 Plain Old Java Object。它們也代表應(yīng)用程序中的 業(yè)務(wù)對(duì)象,這份教程交替使用這兩個(gè)術(shù)語。

確定應(yīng)用程序域的業(yè)務(wù)對(duì)象,與初學(xué)面向?qū)ο笤O(shè)計(jì)時(shí)所做的練習(xí)相同。目的是確定要?jiǎng)?chuàng)建的系統(tǒng)中的對(duì)象和它們之間的交互關(guān)系。更具體地講,就要是發(fā)現(xiàn):

  • 維護(hù)狀態(tài)的對(duì)象(和需要維護(hù)的狀態(tài))
  • 這些對(duì)象之間的關(guān)系
  • 這些對(duì)象之間的交互作用(如果有的話)
  • 在應(yīng)用程序中需要在這些對(duì)象上執(zhí)行的操作

以在這份教程中要?jiǎng)?chuàng)建的簡(jiǎn)單服務(wù)器應(yīng)用程序?yàn)槔?。這是個(gè)顯示員工信息的系統(tǒng)。顧名思義,這個(gè)系統(tǒng)中有一個(gè)對(duì)象是公司的員工。員工信息可以很豐富,但在這個(gè)簡(jiǎn)單的應(yīng)用程序中,只需要知道以下信息:

  • 員工編號(hào)
  • 姓名
  • 中間名縮寫
  • 姓氏
  • 分機(jī)號(hào)碼
  • 職務(wù)
  • 教育程度
  • 性別
  • 工資
  • 獎(jiǎng)金
  • 提成
  • 地址
  • 錄用日期
  • 出生日期

在這個(gè)域中能夠確定的另一個(gè)業(yè)務(wù)對(duì)象就是代表地址的對(duì)象。地址之所以從員工信息中分離出來,是因?yàn)橄到y(tǒng)中的其他實(shí)體(如果想擴(kuò)展系統(tǒng)的話)可能也有地址,這樣就可以把所有地址放在一起了。為簡(jiǎn)單起見,假設(shè)地址對(duì)象只包含街道號(hào)碼和街道名稱。

在這個(gè)簡(jiǎn)單的系統(tǒng)中,每個(gè)員工都有一個(gè)地址,每個(gè)地址也只屬于一個(gè)員工。這叫作 1 對(duì) 1 關(guān)系。其他可能的關(guān)系包括 1 對(duì)多(例如,項(xiàng)目與員工的關(guān)系)、多對(duì) 1 (例如員工與部門的關(guān)系)和 多對(duì)多(例如員工與 HR 福利的關(guān)系)。其他這些關(guān)系模型在 Spring 2 應(yīng)用程序中都可以建立,但是超出了這份初級(jí)教程的范圍。

在這個(gè)系統(tǒng)中,在員工和地址對(duì)象上要有以下操作:

  • 創(chuàng)建新員工(和新地址對(duì)象)
  • 刪除員工(和相關(guān)的地址對(duì)象)
  • 更新員工信息
  • 查找公司的全部員工
  • 根據(jù)員工編號(hào)查找公司的特定員工
  • 根據(jù)姓氏查找公司員工
  • 根據(jù)街道查找公司員工
  • 查找工資超過指定數(shù)額的員工
  • 查找提成超過指定數(shù)額的員工

典型情況下,可以通過考慮系統(tǒng)需要的用戶界面和系統(tǒng)中需要實(shí)現(xiàn)的業(yè)務(wù)邏輯來確定這些操作。

這個(gè)示例中的操作特意被保持簡(jiǎn)單,以便使教程重點(diǎn)突出。在典型的生產(chǎn)場(chǎng)景中,很可能會(huì)有跨多個(gè)確定的對(duì)象集執(zhí)行的更復(fù)雜的操作,還可能會(huì)存在對(duì)象之間的直接交互。

確定了業(yè)務(wù)對(duì)象、交互和操作之后,就可以編碼和測(cè)試了。





為業(yè)務(wù)對(duì)象編寫代碼

編寫的第一組對(duì)象是構(gòu)成系統(tǒng)的 POJO。可以用自己喜歡的開發(fā)編輯器或 IDE 創(chuàng)建這些對(duì)象。本教程假設(shè)您采用的是 Eclipse。

請(qǐng)?jiān)?Eclipse 中創(chuàng)建名為 Spring2Tutorial 的新 Java 項(xiàng)目,然后創(chuàng)建名為 com.ibm.dw.spring2.Employee 的類,如清單 1 所示:

            package com.ibm.dw.spring2;
            public class Employee {
            private long empid;
            private String empno;
            private String firstName;
            private String midInitial;
            private String lastName;
            private String phoneNumber;
            private String job;
            private int educationLevel;
            private char sex;
            private double salary;
            private double bonus;
            private double commission;
            private Address addr;
            private Date hiredate;
            private Date birthdate;
            }
            

清單 1 定義了在 Employee 對(duì)象實(shí)例中保存的全部信息。這些字段全被定義成 private,所以需要?jiǎng)?chuàng)建一些 getter 和 setter 方法,以便支持對(duì)這些信息的外部訪問。





用 Eclipse 生成 getter、setter 和構(gòu)造函數(shù)

在 Eclipse 中生成 getter 和 setter 方法。請(qǐng)?jiān)诰庉嬈髦杏覔簦⑦x擇 Source->Generate Getter and Setters...。在圖 4 所示的彈出對(duì)話框中,單擊 Select All 然后再單擊 OK

最后,必須為對(duì)象創(chuàng)建一些構(gòu)造函數(shù)。在 Eclipse 中,只需右擊并選擇 Source-> Generate Constructor。然后需要編輯生成的構(gòu)造函數(shù)。請(qǐng)看清單 2:

            public Employee(String empno, String firstName, String midInitial, String lastName,
            String phoneNumber, String job, int educationLevel, char sex, double salary,
            double bonus, double commission, Address addr, Date hiredate, Date birthdate) {
            this.empno = empno;
            this.firstName = firstName;
            this.midInitial = midInitial;
            this.lastName = lastName;
            this.phoneNumber = phoneNumber;
            this.job = job;
            this.educationLevel = educationLevel;
            this.sex = sex;
            this.salary = salary;
            this.bonus = bonus;
            this.commission = commission;
            this.addr = addr;
            this.hiredate = hiredate;
            this.birthdate = birthdate;
            }
            public Employee() {}
            

需要添加清單 2 所示的這個(gè)小小的構(gòu)造函數(shù),因?yàn)槿蘸笤趩卧獪y(cè)試階段要使用它。這就完成了 Employee 業(yè)務(wù)對(duì)象的編碼工作。

遺漏的 empid 字段

您可能注意到清單 2 的構(gòu)造函數(shù)中遺漏了 empid。后面要用這個(gè)字段來包含 JPA 生成的與 Employee 實(shí)例關(guān)聯(lián)的主鍵。這個(gè)鍵由 JPA 管理,不應(yīng)當(dāng)被應(yīng)用程序修改。現(xiàn)在應(yīng)當(dāng)從源代碼刪除 setter 方法 setEmpid()。





編寫 Address POJO 的代碼

每個(gè) Employee 對(duì)象都引用一個(gè) Address 對(duì)象。只要 Employee 對(duì)象 保存到數(shù)據(jù)庫,就需要保存 Address 對(duì)象。

Address 對(duì)象的代碼如清單 3 所示,其中也包含 getter、setter 和構(gòu)造函數(shù):

            package com.ibm.dw.spring2;
            public class Address {
            private long id;
            private int number;
            private String street;
            public long getId() {
            return id;
            }
            public int getNumber() {
            return number;
            }
            public void setNumber(int number) {
            this.number = number;
            }
            public String getStreet() {
            return street;
            }
            public void setStreet(String street) {
            this.street = street;
            }
            public Address( int number, String street) {
            this.number = number;
            this.street = street;
            }
            public Address() {}
            }
            

現(xiàn)在就完成了域模型中 POJO 的編碼,接下來需要從服務(wù)接口的角度實(shí)現(xiàn)程序程序的 POJO 上的操作。





創(chuàng)建服務(wù)接口

根據(jù)前面執(zhí)行的域分析,應(yīng)用程序要求一組在業(yè)務(wù)對(duì)象上的操作。

把需求集合轉(zhuǎn)換成代碼 —— 更準(zhǔn)確地說,是轉(zhuǎn)換成 Java 接口,結(jié)果將類似于清單 4。Employee 對(duì)象上的每個(gè)操作都成為 EmployeeService 接口中的一個(gè)方法。

            package com.ibm.dw.spring2;
            import java.util.List;
            public interface EmployeeService {
            // create a new employee
            public Employee save(Employee emp);
            // removing an employee
            public void delete(Employee emp);
            // update the information on an employee
            public Employee update(Employee emp);
            // find all the employees in the company
            public List<Employee> findAll();
            //  find an employee by the employee number
            public List<Employee> findByEmployeeNumber(String empno);
            // find an employee by his name
            public List<Employee> findByEmployeeLastName(String lastName);
            // find an employees living on a street
            public List<Employee> findByAddressStreetName(String streetName);
            // find an employee by the internal unique id
            public Employee findById(long id);
            // find employee over a certain salary
            public List<Employee> findEmployeeWithSalaryOver(double sal);
            // find employee with a certain commission income
            public List<Employee> findEmployeeWithCommissionOver(double comm);
            }
            

請(qǐng)注意在清單 4 中,代碼是對(duì)域分析得到操作列表的直接轉(zhuǎn)化。這個(gè)接口不包含任何特定于 Spring 的編碼。在這個(gè)接口中,目前只知道需要在對(duì)象上進(jìn)行 的操作,對(duì)于如何做還一無所知。很快您就會(huì)看到如何實(shí)現(xiàn)這個(gè)接口中的方法,但是首先要進(jìn)行 POJO 單元測(cè)試。





在容器外測(cè)試業(yè)務(wù)對(duì)象

在編碼完域模型中的獨(dú)立 POJO 和服務(wù)之后,即可為 POJO 編寫單元測(cè)試。至此為止,仍然不需要執(zhí)行任何特定于 Spring 的步驟。實(shí)際上,您可能會(huì)不加修改地使用您在其他應(yīng)用程序(或同一應(yīng)用程序的其他部分)中得到、經(jīng)過測(cè)試的 POJO。

清單 5 顯示了 POJOUnitTest.java。這個(gè) JUnit 測(cè)試用例測(cè)試 EmployeeAddress POJO。

            package com.ibm.dw.spring2;
            import java.text.SimpleDateFormat;
            import java.util.Date;
            import junit.framework.TestCase;
            public class POJOUnitTest extends TestCase {
            private Employee  emp1, emp2;
            private Address   addr1, addr2;
            protected void setUp() throws Exception {
            addr1 = new Address(10, "Walker Street");
            addr2 = new Address();
            addr2.setNumber(20);
            addr2.setStreet("Walker Street");
            emp1 = new Employee("0001", "Joe", "R","Smith",
            "4853", "Engineer", 3, ‘M‘,
            20000.00, 0.00, 0.00,
            addr1
            ,makeDate("08/08/2006") , makeDate("02/04/1972"));
            emp2 = new Employee();
            emp2.setEmpno("0002");
            emp2.setFirstName("John");
            emp2.setMidInitial("T");
            emp2.setLastName("Lockheed");
            emp2.setPhoneNumber("4333");
            emp2.setAddr(addr2);
            emp2.setJob("Sales");
            emp2.setHiredate(makeDate("01/01/2005"));
            emp2.setBirthdate(makeDate("10/8/1966"));
            }
            public void testEmployee() throws java.text.ParseException  {
            assertEquals("0001", emp1.getEmpno());
            assertEquals("Joe", emp1.getFirstName());
            assertEquals("R", emp1.getMidInitial());
            assertEquals("Smith", emp1.getLastName());
            assertEquals(10, emp1.getAddr()。getNumber());
            assertEquals("Walker Street", emp1.getAddr()。getStreet());
            assertEquals (makeDate("08/08/2006"),emp1.getHiredate());
            assertEquals("0002", emp2.getEmpno());
            assertEquals("John", emp2.getFirstName());
            assertEquals("T", emp2.getMidInitial());
            assertEquals("Lockheed", emp2.getLastName());
            assertEquals(20, emp2.getAddr()。getNumber());
            assertEquals("Walker Street", emp2.getAddr()。getStreet());
            assertEquals (makeDate("01/01/2005"),emp2.getHiredate());
            }
            public void testAddress()  {
            assertEquals(10, addr1.getNumber());
            assertEquals("Walker Street", addr1.getStreet());
            assertEquals(20, addr2.getNumber());
            assertEquals("Walker Street", addr2.getStreet());
            }
            private Date makeDate(String dateString) throws java.text.ParseException {
            return (new SimpleDateFormat("MM/dd/yyyy"))。parse(dateString);
            }
            }
            

側(cè)重測(cè)試的設(shè)計(jì)文化

在研讀已有的 Spring 文獻(xiàn)時(shí),您會(huì)發(fā)現(xiàn)大量適合基于 Spring 的系統(tǒng)開發(fā)的方法論和哲學(xué)。這些方法論之間的一個(gè)公共元素,就是以測(cè)試為中心的文化。因?yàn)?Spring 允許在容器之外迅速地測(cè)試域模型 POJO,所以頻繁的單元測(cè)試(和集成測(cè)試)可以成為設(shè)計(jì)過程的基石。

清單 5 中的測(cè)試代碼非常易于理解。這個(gè)測(cè)試根據(jù)先構(gòu)造函數(shù)、后逐個(gè)字段的方式設(shè)置 EmployeeAddress 的字段,最后對(duì)值進(jìn)行檢驗(yàn)。這些測(cè)試并未面面俱到;沒有測(cè)試每個(gè)字段,但是清單 5 確實(shí)表現(xiàn)了如何在麻煩的容器之外對(duì)模型進(jìn)行單元測(cè)試。

要在 Eclipse 內(nèi)運(yùn)行單元測(cè)試,請(qǐng)?jiān)趯?dǎo)航器視圖中右擊 POJOUnitTest.java 文件,然后選擇 Run as... -> JUnit Test。

Spring 中以 POJO 為中心的開發(fā)允許在容器之外獨(dú)立測(cè)試業(yè)務(wù)對(duì)象。與其他方式不同,采用以 POJO 為中心的設(shè)計(jì),可以非常迅速地執(zhí)行單元測(cè)試,所以,可以更頻繁地執(zhí)行此類測(cè)試(例如,作為構(gòu)建過程的一部分)。




 

通過 Spring 2 的 JPA 支持獲得數(shù)據(jù)訪問

在這一節(jié),我們將利用 Spring 2 對(duì) Java 持久性 API(JPA)的支持把數(shù)據(jù)庫訪問添加到 Employee 和 Address。

通過 JPA ORM 的對(duì)象持久性

為提供前面定義的服務(wù)接口的實(shí)現(xiàn),需要利用 Spring 2 的部分特性和支持庫。當(dāng)然,存在替代方法。可以用標(biāo)準(zhǔn)的 Java 數(shù)據(jù)庫訪問技術(shù),例如 JDBC,一個(gè)方法一個(gè)方法地開始實(shí)現(xiàn)接口。但是,看到 Spring 2 如何用 JPA 實(shí)現(xiàn)這一任務(wù)之后,您就會(huì)認(rèn)識(shí)到,把這項(xiàng)工作委托給 Spring 實(shí)際上要更容易。

在 Spring 2 中,集成了來自 EJB 3.0 和 Java EE 5 規(guī)范的 JPA 持久性棧(請(qǐng)參閱 參考資料),使之成為 Spring 支持?jǐn)?shù)據(jù)庫訪問的最簡(jiǎn)單而且也是標(biāo)準(zhǔn)的方式。

Spring 框架一直通過其他對(duì)象到關(guān)系的映射(ORM)技術(shù)支持持久性,但是這類映射任務(wù)要求對(duì)第三方非標(biāo)準(zhǔn)的技術(shù)性庫有相當(dāng)精妙和深入的了解。隨著 JPA 的到來,大批供應(yīng)商開始支持 JPA 標(biāo)準(zhǔn),因而對(duì)非標(biāo)準(zhǔn)的第三方持久性庫的支持的重要性有所降低。

Spring 2 支持 JPA,這使得為關(guān)系數(shù)據(jù)庫編寫、讀取、搜索、更新和刪除對(duì)象(POJO)的煩瑣工作變得透明??梢岳^續(xù)使用 Java 語言面向?qū)ο蟮恼Z法處理 POJO,JPA ORM 層負(fù)責(zé)數(shù)據(jù)庫表的創(chuàng)建、查詢、更新代碼和刪除代碼。

除了透明的數(shù)據(jù)庫操作,Spring 2 的 JPA 支持還把各種五花八門的特定于數(shù)據(jù)庫廠商的異常轉(zhuǎn)換成一套定義良好的異常,使得異常處理代碼大為簡(jiǎn)化。圖 5 演示了 Spring 2 的 JPA 支持:

與 Java SE 5 注釋一道,JPA 還支持通過外部 XML 文件的映射線索規(guī)范。如果您使用的不是 Java SE 5,那么這項(xiàng)技術(shù)是必要的,但相關(guān)內(nèi)容已超出了本教程的討論范圍。

在圖 5 中,您將自己的對(duì)象反饋給 Spring 引擎,同時(shí)還有一些關(guān)于您希望如何把它們映射到關(guān)系數(shù)據(jù)庫表的線索(元數(shù)據(jù))。Spring JPA 支持負(fù)責(zé)處理剩下的工作??梢杂?Java 5 注釋的形式或外部 XML 定義文件(為了與 JDK 1.4 兼容)的形式向 JPA 引擎提供映射線索。

因?yàn)楦鞣N ORM 產(chǎn)品和數(shù)據(jù)庫都存在 JPA 實(shí)現(xiàn),所以您的實(shí)現(xiàn)代碼是可以在不同廠商的解決方案之間移植的(如果必要)。

對(duì)映射對(duì)象的操作通過 JPA 實(shí)體管理器來執(zhí)行。例如,用叫作 em 的實(shí)體管理器把一個(gè)相關(guān)對(duì)象樹寫入關(guān)系數(shù)據(jù)庫,代碼應(yīng)當(dāng)是:

em.persists(myObjectTree);
            

JPA 實(shí)體管理器然后檢查您所提供的映射線索,并通過 myObjectTree 把對(duì)象樹的所有映射字段保存到關(guān)系數(shù)據(jù)庫。

您很快就會(huì)看到(在 用 Spring DAO 實(shí)現(xiàn)域服務(wù) 一節(jié)中),Spring 走得更遠(yuǎn),它還簡(jiǎn)化了使用 JPA 實(shí)體管理器的任務(wù)。





提供 JPA ORM 映射元數(shù)據(jù)

要提供如何把 Employee 對(duì)象保存到數(shù)據(jù)庫的提示,可以向 Employee.java 源代碼添加 Java SE 5 注釋。這些線索通常叫作元數(shù)據(jù),因?yàn)樗鼈兪敲枋鰯?shù)據(jù)的數(shù)據(jù)。

清單 6 顯示了注釋版本的 Employee 對(duì)象,其中的注釋以粗體突出顯示:

            package com.ibm.dw.spring2;
            import java.util.Date;
            import javax.persistence.CascadeType;
            ...
            import javax.persistence.TemporalType;
            @Entity
            public class Employee {
            @Id
            @GeneratedValue(strategy = GenerationType.TABLE)
            private long empid;
            @Column(length = 6)
            private String empno;
            @Column(name = "FIRSTNME")
            private String firstName;
            @Column(name = "MIDINIT")
            private String midInitial;
            private String lastName;
            @Column(name = "PHONENO")
            private String phoneNumber;
            @Column(length = 8)
            private String job;
            @Column(name = "EDLEVEL")
            private int educationLevel;
            @Column(length = 1)
            private char sex;
            @Column(precision=12, scale=2)
            private double salary;
            @Column(precision=12, scale=2)
            private double bonus;
            @Column(name = "COMM", precision=12, scale=2)
            private double commission;
            @OneToOne(cascade = CascadeType.ALL)
            private Address addr;
            @Temporal(TemporalType.DATE)
            private Date hiredate;
            @Temporal(TemporalType.DATE)
            private Date birthdate;
            ...
            

這個(gè)示例中的所有注釋都在字段級(jí)。這是 JPA 注釋的最常見用法。也可以給與字段對(duì)應(yīng)的 getter 方法加注釋。如果想要保存到數(shù)據(jù)庫中的值是計(jì)算得來的,而不是對(duì)象的字段時(shí),這可能是必要的。





Employee POJO 的 JPA 注釋

表 1 描述了 清單 6 中每個(gè)字段的注釋,以及提供給 Spring 2 引擎的持久性線索:

字段/元素 注釋 說明
Employee @Entity 指出這是要保存到數(shù)據(jù)庫的類。默認(rèn)情況下類名稱被用作表名。
empid @Id 指出這是表的主關(guān)鍵字段。
empid @GeneratedValue(strategy = GenerationType.TABLE) 指定持久性引擎用來分配唯一主關(guān)鍵 ID 的策略。GenerationType.TABLE 指出應(yīng)當(dāng)使用可移植的基于表的 ID 序列。其他特定于數(shù)據(jù)庫的選項(xiàng)也可使用,但可能無法跨多個(gè)數(shù)據(jù)庫工作。
empno @Column(length = 6) 這個(gè)字段包含員工編號(hào),由公司分配。請(qǐng)注意這不是主鍵。在這個(gè)應(yīng)用程序中,主鍵是由引擎生成和管理的。@Column() 標(biāo)記指定字段應(yīng)當(dāng)是六個(gè)字符長(zhǎng)。指定合適的字段長(zhǎng)度會(huì)有助于限制生成的表的大小。
firstName @Column(name = "FIRSTNME") 指定這個(gè)字段在數(shù)據(jù)庫表中應(yīng)當(dāng)使用的字段名。
midInitial @Column(name = "MIDINIT") 指定這個(gè)字段在數(shù)據(jù)庫表中應(yīng)當(dāng)使用的字段名。請(qǐng)注意,它與 Java 字段名不同。
lastName 沒有注釋,所以將使用字段名 "LASTNAME",與 Java 字段名匹配。
phoneNumber @Column(name = "PHONENO") 指定在數(shù)據(jù)庫表中使用的字段名。
job @Column(length = 8) 指定數(shù)據(jù)庫字段的長(zhǎng)度。
educationLevel @Column(name = "EDLEVEL") 指定數(shù)據(jù)庫字段名。
sex @Column(length = 1) 指定數(shù)據(jù)庫字段的長(zhǎng)度。
salary @Column(precision=12, scale=2) 指定浮點(diǎn)數(shù)據(jù)庫字段的算術(shù)精度。
bonus @Column(precision=12, scale=2) 指定浮點(diǎn)數(shù)據(jù)庫字段的算術(shù)精度。
commission @Column(precision=12, scale=2) 指定浮點(diǎn)數(shù)據(jù)庫字段的算術(shù)精度。
addr @OneToOne(cascade = CascadeType.ALL) 指定這個(gè)表和另一個(gè)映射表中的 Address 對(duì)象之間的關(guān)系。cascade=ALL 指明添加、修改、刪除和刷新應(yīng)當(dāng)全部級(jí)聯(lián)到關(guān)聯(lián)表。
hiredate @Temporal(TemporalType.DATE) 指定字段是日期字段(而不是時(shí)間或時(shí)間戳字段)。
birthdate @Temporal(TemporalType.DATE) 指定字段是日期字段(而不是時(shí)間或時(shí)間戳字段).

請(qǐng)注意 EmployeeAddress 實(shí)例是一對(duì)一的關(guān)聯(lián)(由 @OneToOne(casecade=CascadeType.ALL) 注釋指定)。這個(gè)注釋指定 Employee 對(duì)象上的所有實(shí)體管理器操作應(yīng)級(jí)聯(lián)到關(guān)聯(lián)的 Address 對(duì)象。這意味著新增任何 Employee 記錄都會(huì)在 RDBMS 中創(chuàng)建一個(gè)對(duì)應(yīng)的 Address 記錄。它還意味著對(duì) Employee 記錄所做的任何更新或刪除,都會(huì)級(jí)聯(lián)到關(guān)聯(lián)的 Address 記錄。這是經(jīng)常在 RDBMS 中發(fā)現(xiàn)的級(jí)聯(lián)刪除完整性約束的擴(kuò)展。在實(shí)踐中,會(huì)發(fā)現(xiàn)在執(zhí)行級(jí)聯(lián)選項(xiàng)時(shí),Java 代碼被極大地簡(jiǎn)化了:不再需要協(xié)調(diào)跨越多個(gè)表的操作。

有注釋的 Employee 源代碼是 JPA 實(shí)體管理器管理 EmployeeAddress 對(duì)象的持久性實(shí)例的藍(lán)本。您應(yīng)發(fā)現(xiàn),與容易出錯(cuò)的編寫創(chuàng)建和操作實(shí)際 RDBMS 表的實(shí)際代碼相比,這樣的注釋要簡(jiǎn)單得多。。

Employee 對(duì)象的表可以選擇性地基于上述注釋生成,其模式類似于清單 7:

            CREATE TABLE EMPLOYEE (
            EMPID INTEGER NOT NULL,
            EDLEVEL INTEGER,
            SEX CHAR(1),
            FIRSTNME VARCHAR(255),
            SALARY DOUBLE,
            LASTNAME VARCHAR(255),
            BONUS DOUBLE,
            JOB VARCHAR(8),
            COMM DOUBLE,
            MIDINIT VARCHAR(255),
            HIREDATE DATE,
            EMPNO VARCHAR(6),
            BIRTHDATE DATE,
            PHONENO VARCHAR(255),
            ADDR_ID INTEGER,
            PRIMARY KEY (EMPID),
            FOREIGN KEY (ADDR_ID)
            )
            ;
            

基于字符串的字段的長(zhǎng)度

請(qǐng)注意,任何沒有用 @Column(length=?) 注釋的 String 字段默認(rèn)是 VARCHAR(255)。這可以表示短字段浪費(fèi)的存儲(chǔ)空間(每行)。在生產(chǎn)場(chǎng)景中,可能想更嚴(yán)格地控制底層管理的表的空間分配。

請(qǐng)比較清單 7 與 清單 6 中注釋的 Employee,查看注釋在對(duì) Spring 引擎創(chuàng)建的表上的效果。

如果想得到所有可用的 JPA 注釋的詳細(xì)解釋和說明,請(qǐng)參考 JSR 220,它是企業(yè) JavaBeans 3.0 規(guī)范的最終發(fā)行文檔(請(qǐng)參閱 參考資料 )。





Address 對(duì)象的 JPA 注釋

Address POJO 以相似的方式進(jìn)行注釋,如清單 8 所示:

            package com.ibm.dw.spring2;
            import javax.persistence.Column;
            ...
            @Entity
            public class Address {
            @Id
            @GeneratedValue(strategy = GenerationType.TABLE)
            private long id;
            @Column(name = "NUM")
            private int number;
            @Column(name = "STNAME", length=25)
            private String street;
            ...
            

迄今為止,所有注釋對(duì)您來說都有著某些意義。毫不奇怪,清單 8 中的注釋生成的表具有如清單 9 所示的模式:

            CREATE TABLE ADDRESS (
            ID INTEGER NOT NULL,
            NUM INTEGER,
            STNAME VARCHAR(25),
            PRIMARY KEY (ID)
            )
            ;
            





Spring 2 和 Java EE 5 的關(guān)系

JPA 持久性是 EJB 3.0 的一部分,后者又是 Java EE 5 規(guī)范的一部分,這意味著所有兼容的 Java EE 5 服務(wù)器(商業(yè)的、開放源碼的或其他)都有符合規(guī)范的實(shí)現(xiàn)。這實(shí)際上就保證了在不久的將來,將有健壯、高質(zhì)量的 JPA 實(shí)現(xiàn)可以使用。

請(qǐng)注意,雖然 Spring 2 利用了 EJB 3.0 規(guī)范的 JPA 持久性,但未強(qiáng)迫 Spring 2 的用戶利用 EJB 3.0 或 Java EE 5 規(guī)范的其他元素。

從最初起,JPA 就被設(shè)計(jì)成可以在傳統(tǒng) EJB 容器之外獨(dú)立使用。作為一個(gè)具體的示例,本教程中的應(yīng)用程序受益于 JPA ,但絕對(duì)不是 EJB 3.0 或 Java EE 5 應(yīng)用程序。
 

用 Spring DAO 實(shí)現(xiàn)域服務(wù)

在這一節(jié),您將用 Spring DAO(數(shù)據(jù)訪問對(duì)象)API 實(shí)現(xiàn)員工信息應(yīng)用程序的服務(wù)接口。

實(shí)現(xiàn) EmployeeService 接口

一旦 Spring 2 引擎知道了如何保持 EmployeeAddress 對(duì)象的實(shí)例,實(shí)現(xiàn) EmployeeService 接口的任務(wù)就變得非常簡(jiǎn)單。

可以在服務(wù)實(shí)現(xiàn)中利用 Spring DAO API。Spring DAO 實(shí)現(xiàn)了著名的 DAO 設(shè)計(jì)模式(請(qǐng)參閱 參考資料)。在這個(gè)模式中,DAO 提供了一致的數(shù)據(jù)訪問外觀。通過傳輸對(duì)象執(zhí)行數(shù)據(jù)提取和修改。DAO 封裝了實(shí)際的數(shù)據(jù)源,并提供了操作傳輸對(duì)象的方法。

從架構(gòu)上說,DAO API 隱藏了操作實(shí)際數(shù)據(jù)持久性 API 調(diào)用的復(fù)雜性。(除了 JPA 之外,Spring 還支持其他 ORM 技術(shù),例如 JDO、Hibernate、iBATIS SQL Maps 和 Apache OJB。)。使用 Spring 的 DAO,可以編寫能夠輕松適應(yīng)這些持久性 API 的數(shù)據(jù)訪問代碼。

除了對(duì)數(shù)據(jù)持久性 API 的抽象,Spring 的 DAO 支持把各種特定于廠商的數(shù)據(jù)訪問異常映射到一套歸檔良好的 Spring 數(shù)據(jù)訪問異常。

Spring DAO API 還提供了便于擴(kuò)展的支持類。通過擴(kuò)展它們,您可不必再編寫煩瑣而易于出錯(cuò)的 ORM 數(shù)據(jù)訪問代碼。所需的全部編碼都封裝在基類和支持庫中,而且經(jīng)過全面測(cè)試。這些類封裝了通常與應(yīng)用程序邏輯混雜在一起的連接和事務(wù)管理代碼。在 JPA 支持類的情況下,對(duì) JPA 實(shí)體管理器的使用完全封裝在支持類中,因此您不必考慮實(shí)體管理器和實(shí)體管理器工廠的處理。

一些真實(shí)的代碼可以為您展現(xiàn) Spring DAO API 的多功能性。清單 10 是 EmployeeService 接口的實(shí)現(xiàn),稱為 EmployeeDAO,它使用了 Spring 2 的 JpaDaoSupport 類:

            import java.util.List;
            import org.springframework.orm.jpa.support.JpaDaoSupport;
            public class EmployeeDAO extends JpaDaoSupport implements EmployeeService {
            public Employee findById(long id) {
            return getJpaTemplate()。find(Employee.class, id);
            }
            public List<Employee> findAll() {
            return getJpaTemplate()。find("select e from Employee e");
            }
            public List<Employee> findByEmployeeNumber(String empno) {
            return getJpaTemplate()。find(
            "select e from Employee e where e.empno = ?1", empno);
            }
            public List<Employee> findByAddressStreetName(String street) {
            return getJpaTemplate()。find(
            "select e from Employee e where e.addr.street = ?1", street);
            }
            public List<Employee> findByEmployeeLastName(String lastName) {
            return getJpaTemplate()。find(
            "select e from Employee e where e.lastName = ?1", lastName);
            }
            public List<Employee> findEmployeeWithSalaryOver(double sal) {
            return getJpaTemplate()。find(
            "select e from Employee e where e.salary > ?1", sal);
            }
            public List<Employee> findEmployeeWithCommissionOver(double comm) {
            return getJpaTemplate()。find(
            "select e from Employee e where e.commission > ?1", comm);
            }
            public Employee save(Employee emp) {
            getJpaTemplate()。persist(emp);
            return emp;
            }
            public Employee update(Employee emp) {
            return getJpaTemplate()。merge(emp);
            }
            public void delete(Employee emp) {
            getJpaTemplate()。remove(emp);
            }
            }
            

在清單 10 中,最值得注意的就是各方法實(shí)現(xiàn)中編碼極其簡(jiǎn)單。JpaDaoSupport 類處理了大多數(shù)煩瑣的日常工作。JpaTemplate 助手類能:

  • 隱藏底層的 API 差異
  • 轉(zhuǎn)換異常
  • 管理 JPA 實(shí)體管理器
  • 打包事務(wù)處理
  • 把數(shù)據(jù)訪問標(biāo)準(zhǔn)化成對(duì)少數(shù)一致(跨全部 Spring DAO 實(shí)現(xiàn))和定義良好的方法的訪問

表 2 總結(jié)了清單 10 中的 JpaTemplate 方法,這是一種常用的方法:

方法 說明
find(Class <T> cls, Object id); 通過實(shí)例的主鍵找到保持的實(shí)例。
find(String query); 使用查詢字符串找到保持的對(duì)象。這個(gè)強(qiáng)大的查詢語言是 EJB QL 的擴(kuò)展版本,在 JSR-220 中有完整描述(請(qǐng)參閱 參考資料)。
persist(Object obj); 保存實(shí)例到數(shù)據(jù)庫。用 JPA 的說法,它用 JPA 實(shí)體管理器保持實(shí)例。
merge(Object obj); 用所提供的實(shí)例中的信息更新保存的對(duì)象實(shí)例。
remove(Object obj); 從數(shù)據(jù)庫中刪除保持的實(shí)例。

在幕后,JpaTemplate 輔助類利用 JPA 實(shí)體管理器處理全部操作。輔助類處理數(shù)據(jù)訪問期間例行的實(shí)體管理器檢索和關(guān)閉操作。

在處理某些具體需求時(shí),JpaTemplate 類中的其他方法可能有所幫助。請(qǐng)參考 Spring DAO API 的 JavaDoc 獲得更多細(xì)節(jié)(請(qǐng)參閱 參考資料)。

有了保持 EmployeeAddress 實(shí)例的能力和 EmployeeService 的具體實(shí)現(xiàn),接下來就可以根據(jù)真實(shí)關(guān)系數(shù)據(jù)庫進(jìn)行完整的測(cè)試了。





連接 Spring bean

至此,對(duì)于 Spring 框架什么時(shí)候和如何獲得機(jī)會(huì)去實(shí)際處理 POJO,仍然不清楚。謎題的數(shù)據(jù)訪問部分解決了,但仍有兩個(gè)問題存在:Spring 2 引擎怎么知道要做什么,如何指定要使用哪個(gè)關(guān)系數(shù)據(jù)庫?

立即就會(huì)解決這兩個(gè)問題;將看到如何向 Spring 引擎提供 bean 連接模板。秘密在于叫作 dwspring-service.xml 的 XML bean 描述符文件。這個(gè) bean 描述符文件是 Spring 2 框架操作概述 中提過的連接藍(lán)本。它描述了 Spring 應(yīng)用程序中不同的 bean 之間的關(guān)系。清單 11 顯示了這個(gè)文件:

            <?xml version="1.0" encoding="UTF-8"?>
            <beans xmlns="http://www.springframework.org/schema/beans"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">
            <bean id="employeeService" class="com.ibm.dw.spring2.EmployeeDAO">
            <property name="entityManagerFactory" ref="entityManagerFactory"/>
            </bean>
            <bean id="entityManagerFactory" class=
            "org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
            <property name="dataSource" ref="dataSource"/>
            <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.TopLinkJpaVendorAdapter">
            <property name="showSql" value="true"/>
            <property name="generateDdl" value="true"/>
            <property name="databasePlatform"
            value="oracle.toplink.essentials.platform.database.HSQLPlatform"/>
            </bean>
            </property>
            <property name="loadTimeWeaver">
            <bean class="org.springframework.instrument.classloading.SimpleLoadTimeWeaver"/>
            </property>
            </bean>
            <bean id="dataSource"
            class="org.springframework.jdbc.datasource.DriverManagerDataSource">
            <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
            <property name="url" value="jdbc:hsqldb:mem:dwspring"/>
            <property name="username" value="sa" />
            <property name="password" value=" " />
            </bean>
            <bean id="transactionManager"
            class="org.springframework.orm.jpa.JpaTransactionManager">
            <property name="entityManagerFactory" ref="entityManagerFactory"/>
            <property name="dataSource" ref="dataSource"/>
            </bean>
            </beans>
            

要測(cè)試 EmployeeDAO 實(shí)現(xiàn)的動(dòng)作,可以使用叫作 HSQLDB 的內(nèi)存內(nèi)(請(qǐng)參閱 參考資料)。HSQLDB 的可執(zhí)行文件是 “有依賴項(xiàng)的 Spring 2” 下載的一部分。

在清單 11 中,專門配置 HSQLDB 實(shí)例的行用黑體表示。稍后(在 編寫針對(duì) RDBMS 的 DAO 集成測(cè)試)中,將看到如何修改這些行,來針對(duì) DB2 Express-C 運(yùn)行集成測(cè)試。

請(qǐng)記住 EmployeeDAO 實(shí)際上擴(kuò)展了 JpaDaoSupport 類。這個(gè)類希望在裝入的時(shí)候被 JPA EntityManagerFactory “插入”。然后它可以用這個(gè)工廠得到所有數(shù)據(jù)訪問操作的 JPA EntityManager

圖 6 以圖形方式顯示 bean 在 dwspring2-service.xml 文件中如何連接在一起:

實(shí)際上,清單 11 是需要由 Spring 2 引擎創(chuàng)建的對(duì)象的連接計(jì)劃,圖 6 是這些對(duì)象的圖表。在清單 11 和圖 6 中要注意的一個(gè)重要項(xiàng)目就是 EmployeeDAO 如何通過叫作 employeeService 的bean 獲得。這個(gè)實(shí)例把自己的 entityManagerFactory 屬性設(shè)置成另一個(gè)名為 entityManagerFactory的 bean:

   <bean id="employeeService" class="com.ibm.dw.spring2.EmployeeDAO">
            <property name="entityManagerFactory" ref="entityManagerFactory"/>
            </bean>
            

ref="" 標(biāo)志是對(duì)上下文中定義的另一個(gè) bean 的引用 —— 通常是在同一個(gè)文件中。





依賴性注入

用外部創(chuàng)建的對(duì)象來填充屬性,就像剛才做的那樣,這叫作 注入——更具體地講,叫作依賴性注入 (DI),因?yàn)楸蛔⑷氲膶?duì)象通常是接收對(duì)象進(jìn)行正確操作所依賴的事物。DI 在 Spring 中框架中使用得很多。使用 DI,編寫組件代碼時(shí)不需要主動(dòng)查詢或查找依賴服務(wù)(例如,查詢 EntityManagerFactory)。相反,可以就像依賴服務(wù)已經(jīng)存在一樣地編寫組件代碼,Spring 引擎會(huì)在代碼執(zhí)行之前把實(shí)際的依賴性注入組件。

依賴性注入的應(yīng)用程序

如果查看 清單 11 中一直到 entityManagerFactory 連接的部分,您會(huì)注意到 Spring 注入了以下依賴項(xiàng):

  • dataSource
  • jpaVendorAdapter
  • loadTimeWeaver

dataSource bean 是 org.springframework.jdbc.datasource.DriverManagerDataSource 的實(shí)例,用 HSQLDB RDBMS 的內(nèi)存中實(shí)例進(jìn)行了配置。

jpaVendorAdapter 屬性通過連接到 Spring 應(yīng)用程序?qū)嶋H JPA 實(shí)現(xiàn)的 bean 注入。在這個(gè)示例中,使用的是 JPA 引用實(shí)現(xiàn),通過 org.springframework.orm.jpa.vendor.TopLinkJpaVendorAdapter 類來訪問。這個(gè)類接著需要用 databasePlatform property 進(jìn)行配置。這個(gè)屬性被設(shè)置成 oracle.toplink.essentials.platform.database.HSQLPlatform,此配置支持對(duì) HSQLDB RDBMS 的訪問。這個(gè) bean 的 generateDdl 屬性控制著是否生成和執(zhí)行數(shù)據(jù)定義語言腳本。如果這個(gè)屬性設(shè)為 true,那么這個(gè) bean 每次裝入時(shí),都會(huì)重新建立數(shù)據(jù)庫中的方案。為了集成測(cè)試的目的,應(yīng)當(dāng)保持這個(gè)屬性為 true。

dataSource bean 的配置中,創(chuàng)建了 org.springframework.jdbc.datasource.DriverManagerDataSource 的實(shí)例。它的參數(shù)設(shè)置有:

  • HSQLDB 數(shù)據(jù)庫驅(qū)動(dòng)程序
  • 創(chuàng)建內(nèi)存中數(shù)據(jù)庫的 JDBC UR(JDBC URL 的 mem 部分)
  • 用戶名和口令(對(duì) HSQLDB,默認(rèn)分別是 sa""

最后一個(gè) transactionManager bean 是為后面的集成測(cè)試配置的。不需要連接這個(gè) bean,因?yàn)楹竺嬉玫臏y(cè)試基類會(huì)按類型查找這個(gè) bean。

至此,您應(yīng)已對(duì) Spring 2 如何連接 bean 有了一定的連接。您還應(yīng)了解如何把數(shù)據(jù)庫從 HSQLDB 轉(zhuǎn)換到 DB2 Express-C,這一步要在下一節(jié)進(jìn)行)編寫針對(duì) RDBMS 的 DAO 集成測(cè)試)。
 

編寫針對(duì) RDBMS 的 DAO 集成測(cè)試

在這一節(jié),編寫和運(yùn)行一個(gè)集成測(cè)試,根據(jù)數(shù)據(jù)庫測(cè)試員工信息應(yīng)用程序。

測(cè)試 EmployeeService 的 EmployeeDAO 實(shí)現(xiàn)

現(xiàn)在還剩下的唯一問題是:如何和在什么時(shí)候調(diào)用 Spring 2 引擎,它怎么知道該如何使用 dwspring2-service.xml 配置文件?

看一下 EmployeeServiceIntegrationTest.java 中的集成測(cè)試源代碼,答案就一目了然了。這個(gè)集成測(cè)試針對(duì)實(shí)際的 RDBMS 測(cè)試 EmployeeServiceEmployeeDAO 實(shí)現(xiàn)。請(qǐng)參閱清單 12 中的代碼片斷:

            package com.ibm.dw.spring2;
            import java.util.Date;
            ...
            import org.springframework.test.jpa.AbstractJpaTests;
            public class EmployeeServiceIntegrationTest extends AbstractJpaTests {
            private EmployeeService employeeService;
            private long JoeSmithId = 99999;
            public void setEmployeeService(EmployeeService employeeService) {
            this.employeeService = employeeService;
            }
            protected String[] getConfigLocations() {
            return new String[] { "classpath:/com/ibm/dw/spring2/dwspring2-service.xml" };
            }
            

這套集成測(cè)試是在 Spring 2 庫的 AbstractJpaTests 類的幫助下編寫的。通過實(shí)現(xiàn)清單 12 中強(qiáng)調(diào)的 getConfigLocations() 方法,可以提供一個(gè)或多個(gè)需要由 Spring 2 引擎解析的 bean 配置文件??梢杂卸鄠€(gè)配置文件,因?yàn)榉蛛x后端和用戶界面 bean 配置文件是一種常見實(shí)踐。

清單 12 中的 setEmployeeService() 是依賴性注入的示例。當(dāng) Spring 2 引擎裝入這個(gè) EmployeeServiceIntegrationTest 類時(shí)(派生自 AbstractJpaTests),它發(fā)現(xiàn)一個(gè)沒有填充的依賴項(xiàng) —— 類型為 EmployeeService 的屬性。引擎就查找 dwspring2-service.xml 文件,查找配置好的類型為 EmployeeService 的bean,并通過 setEmployeeService() 方法注入。





按類型自動(dòng)連接

您可能注意到,在 dwspring2-service.xml 文件中,對(duì)于 employeeService 的注入缺少明確的注入指令。實(shí)際上,這個(gè)注入是自動(dòng)發(fā)生的。在 Spring 的術(shù)語中,這叫作自動(dòng)連接。

AbstractJpaTests 基類派生自 AbstractDependencyInjectionSpringContextTests 類。 AbstractDependencyInjectionSpringContextTests 通過把 Spring 的按類型自動(dòng)連接特性設(shè)為默認(rèn),簡(jiǎn)化了測(cè)試。如果在應(yīng)用程序的上下文(在這個(gè)示例中,由 dwspring2-service.xml 文件配置)中發(fā)現(xiàn)相同類型的 bean,那么它的子類(EmployeeServiceIntegrationTest 就是這樣一個(gè)子類)的任何依賴項(xiàng)(公共屬性)就被自動(dòng)注入。





集成測(cè)試設(shè)置

AbstractJpaTests 針對(duì)測(cè)試的一個(gè)有用特性,就是在事務(wù)中執(zhí)行測(cè)試,然后在測(cè)試后回滾所有效果的能力。這切實(shí)地加快了測(cè)試,因?yàn)樵诿看螠y(cè)試運(yùn)行期間,不再需要?jiǎng)h除和重建數(shù)據(jù)了。

清單 13 顯示了執(zhí)行每個(gè)測(cè)試的初始設(shè)計(jì)的代碼。這個(gè)設(shè)置代碼用三個(gè) Employee 和它們的相關(guān) Addresse 填充數(shù)據(jù)庫。必須在與每個(gè)測(cè)試相同的事務(wù)內(nèi)執(zhí)行這個(gè)代碼。否則,就不會(huì)看到注入的數(shù)據(jù),因?yàn)槭聞?wù)總會(huì)被回滾。要在相同事務(wù)中執(zhí)行設(shè)置,要覆蓋 onSetUpInTransaction() 方法,如清單 13 所示:

            protected void onSetUpInTransaction() throws Exception {
            Employee emp1 = new Employee("0001", "Joe", "R","Smith",
            "4853", "Engineer", 3, ‘M‘,
            20000.00, 0.00, 0.00,
            new Address(10, "Walker Street")
            , new Date(), new Date());
            Employee emp2 = new Employee("0002", "John","T","Lockheed",
            "4333", "Sales", 2, ‘M‘,
            40000.00, 0.00, 5000.00,
            new Address(20, "Walker Street")
            , new Date(), new Date());
            Employee emp3 = new Employee("0003", "Mary","M","Johnson",
            "4383", "Admin", 3, ‘F‘,
            60000.00, 0.00, 390.00,
            new Address(123, "Booth Ave")
            , new Date(), new Date());
            employeeService.save(emp1);
            employeeService.save(emp2);
            employeeService.save(emp3);
            JoeSmithId = emp1.getEmpid();
            }
            

請(qǐng)注意,通過基于 JPA 的 employeeService 創(chuàng)建并保持 Employee 實(shí)例有多簡(jiǎn)單。因?yàn)?save() 方法調(diào)用 JPA 的 persist() 操作,而且操作會(huì)從 Employee 級(jí)聯(lián)到 Address 對(duì)象(在 Employee POJO 的 JPA 注釋指定這個(gè)),所以也可以依靠 JPA 在地址表中創(chuàng)建新記錄。

各測(cè)試每次執(zhí)行時(shí),都會(huì)執(zhí)行 onSetUpInTransaction() 中的設(shè)置代碼。這確保了在每次測(cè)試之前,都有三個(gè)員工。

作為測(cè)試示例,清單 14 顯示了 EmployeeDAOupdate() 方法的測(cè)試方法 testModifyEmployee()

            public void testModifyEmployee() {
            String oldLastName = "Lockheed";
            String newLastName = "Williams";
            Employee emp = employeeService
            .findByEmployeeLastName(oldLastName)。get(0);
            emp.setLastName(newLastName);
            Employee emp2 = employeeService.update(emp);
            assertEquals(newLastName, emp2.getLastName());
            List<Employee> results = employeeService
            .findByEmployeeLastName(oldLastName);
            assertEquals(0, results.size());
            results = results = employeeService
            .findByEmployeeLastName(newLastName);
            assertEquals(1, results.size());
            }
            

清單 14 中的測(cè)試用例 testModifyEmployee()EmployeeServiceupdate()把員工“John Lockheed”的姓氏從“Lockheed”改成“Williams”。然后它調(diào)用 findByEmployeeLastName("Williams"),檢驗(yàn)最后有一個(gè)員工“Williams”。它還檢驗(yàn)沒有姓氏為“Lockheed”的員工存在。

EmployeeServiceIntegrationTest 中還有其他許多測(cè)試??梢匝芯窟@些代碼查看如何利用 EmployeeService 實(shí)現(xiàn)上的方法操縱實(shí)際數(shù)據(jù)(請(qǐng)參閱 下載)。

接下來,設(shè)置根據(jù) RDBMS 運(yùn)行這些集成測(cè)試的環(huán)境。





下載和安裝 JPA 引用實(shí)現(xiàn)

JPA 是 EJB 3.0 規(guī)范的一部分。EJB 3.0 又是 Java EE 5 規(guī)范的核心組件。因?yàn)檫@點(diǎn),JPA 的開源引用實(shí)現(xiàn)成為了 GlassFish Java EE 5 引用服務(wù)器的一部分。

如果還沒有做,那么請(qǐng)立即下載和安裝 JPA 實(shí)現(xiàn)來執(zhí)行集成測(cè)試(請(qǐng)參閱 參考資料)。

準(zhǔn)備類路徑

要在代碼中訪問 JPA 功能,在構(gòu)建和運(yùn)行時(shí)類路徑中必須有 JPA 引用實(shí)現(xiàn):

  1. 在 Eclipse 中,在導(dǎo)航器面板中右擊項(xiàng)目名稱,并選擇 Properties。
  2. 選擇 Java Build Path,然后在對(duì)話框中,選擇 Libraries 選項(xiàng)卡。
  3. 單擊 Add External JARs 按鈕,并確保包含了 JPA 下載的 toplink-essentials.jar。

要成功地編譯和運(yùn)行集成測(cè)試,在構(gòu)建和運(yùn)行時(shí)類路徑中必須有依賴的庫。表 3 顯示了在構(gòu)建和運(yùn)行時(shí)類路徑中必須有的 JAR 文件??梢栽?Eclipse 中設(shè)置這些路徑:

JAR 文件 原始下載
commons-logging.jar spring-framework-2.x-with-dependencies.zip
db2cc.jar IBM DB 2 Express-C 發(fā)布
db2cc_licence_cu.jar IBM DB2 Express-C 發(fā)布版
hsqldb.jar spring-framework-2.x-with-dependencies.zip
junit.jar spring-framework-2.x-with-dependencies.zip
log4j-1.x.xx.jar spring-framework-2.x-with-dependencies.zip
persistence.jar spring-framework-2.x-with-dependencies.zip
spring.jar spring-framework-2.x-with-dependencies.zip
spring-core.jar spring-framework-2.x-with-dependencies.zip
spring-jpa.jar spring-framework-2.x-with-dependencies.zip
spring-mock.jar spring-framework-2.x-with-dependencies.zip
toplink-essential.jar JPA 引用實(shí)現(xiàn)下載




包含 persistence.xml 文件

雖然在這個(gè) Spring JPA 集成場(chǎng)景中, persistence.xml 文件沒有提供配置信息,但 JPA 規(guī)范要求這個(gè)文件。

persistence.xml 文件是持久性單元 的說明。在 JPA 術(shù)語中,持久性單元包含 EntityManagerFactory (和相關(guān)的配置信息),它創(chuàng)建的 EntityManagers 和這些 EntityManager 管理的類(以及這些類的元數(shù)據(jù),以注釋或 XML 形式)。

在這個(gè)示例中,persistence.xml 文件非常簡(jiǎn)單,如清單 15 所示:

            <persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
            <persistence-unit name="dwSpring2Jpa" transaction-type="RESOURCE_LOCAL"/>
            </persistence>
            

在某些廠商的配置中,persistence.xml 文件可以包含相關(guān)的說明信息,但是 Spring/JPA 集成中沒有。只需要確定具有一份 META-INF/persistence.xml 的副本即可。





運(yùn)行 Spring 集成測(cè)試

構(gòu)建路徑配置之后,即可運(yùn)行集成測(cè)試。

要在 Eclipse 中運(yùn)行集成測(cè)試,請(qǐng)?jiān)?Navigator 面板中選中 EmployeeServiceIntegrationTest.java 文件,然后右擊選中 Run As -> JUnit Test。這就開始了集成測(cè)試。請(qǐng)記住這個(gè)測(cè)試是針對(duì)一個(gè)內(nèi)存中的數(shù)據(jù)庫運(yùn)行的,非???。在執(zhí)行期間,應(yīng)當(dāng)在 Eclipse 的控制臺(tái)窗口看到表創(chuàng)建、SQL 插入和查詢的日志消息。請(qǐng)參閱圖 7 查看集成測(cè)試的運(yùn)行示例:




把數(shù)據(jù)源從內(nèi)存中的數(shù)據(jù)庫切換到 DB2 Express-C

一般來說,在使用 Spring DAO 和 DAO 設(shè)計(jì)模式時(shí),對(duì)應(yīng)用程序代碼來說,數(shù)據(jù)源是完全封裝并隱藏的。實(shí)際上,示例中的代碼完全獨(dú)立于使用的關(guān)系數(shù)據(jù)庫,而且獨(dú)立于使用的 JPA 廠商。這種靈活性允許您在執(zhí)行得快的數(shù)據(jù)庫(例如內(nèi)存中的數(shù)據(jù)庫)上測(cè)試應(yīng)用程序代碼,然后把同樣的代碼部署到健壯和可伸縮的商業(yè)級(jí)數(shù)據(jù)庫(例如 IBM DB2 Express-C)上。

要在不同的數(shù)據(jù)庫之間切換,只需要修改 dwspring2-service.xml bean 描述符文件。清單 16 顯示了從 HSQLDB 切換到 IBM DB2 Express-C 需要的修改(用黑體強(qiáng)調(diào)):

            <?xml version="1.0" encoding="UTF-8"?>
            <beans xmlns="http://www.springframework.org/schema/beans"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">
            <bean id="employeeService" class="com.ibm.dw.spring2.EmployeeDAO">
            <property name="entityManagerFactory" ref="entityManagerFactory"/>
            </bean>
            <bean id="entityManagerFactory" class=
            "org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
            <property name="dataSource" ref="dataSource"/>
            <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.TopLinkJpaVendorAdapter">
            <property name="showSql" value="true"/>
            <property name="generateDdl" value="true"/>
            <property name="databasePlatform"
            value="oracle.toplink.essentials.platform.database.DB2Platform"/>
            </bean>
            </property>
            <property name="loadTimeWeaver">
            <bean class="org.springframework.instrument.classloading.SimpleLoadTimeWeaver"/>
            </property>
            </bean>
            <bean id="dataSource" class=
            "org.springframework.jdbc.datasource.DriverManagerDataSource">
            <property name="driverClassName" value="com.ibm.db2.jcc.DB2Driver"/>
            <property name="url" value="jdbc:db2://192.168.23.36:50000/dwspring"/>
            <property name="username" value="bill" />
            <property name="password" value="lotus123" />
            </bean>
            <bean id="transactionManager" class=
            "org.springframework.orm.jpa.JpaTransactionManager">
            <property name="entityManagerFactory" ref="entityManagerFactory"/>
            <property name="dataSource" ref="dataSource"/>
            </bean>
            </beans>
            

The modifications highlighted 清單 16 中突出的修改假設(shè)在 192.168.23.36 的端口 50000 (默認(rèn)的)上有一個(gè) DB2 Express-C 實(shí)例。需要?jiǎng)?chuàng)建叫作 dwspring 的數(shù)據(jù)庫,并向應(yīng)用程序用戶(在清單 16 中是 bill;請(qǐng)把它改成自己的特定用戶)提供完整訪問。

現(xiàn)在再次運(yùn)行集成測(cè)試。測(cè)試連接到 DB2 Express-C 數(shù)據(jù)庫,創(chuàng)建表,并執(zhí)行全部測(cè)試。這次的執(zhí)行稍微慢了一些 —— 因?yàn)闇y(cè)試要跨網(wǎng)絡(luò)執(zhí)行,還要使用磁盤上的數(shù)據(jù)存儲(chǔ)——您會(huì)注意到在執(zhí)行中有些小變化。圖 8 顯示了在 Eclipse 中針對(duì) DB2 Express-C 的典型運(yùn)行:

應(yīng)用程序的數(shù)據(jù)層現(xiàn)在已經(jīng)完成并經(jīng)過了完整測(cè)試。這一層可以用于各種不同的用戶界面。例如,可以在上面放置一個(gè)命令行接口層,或者創(chuàng)建使用它的 GUI 胖客戶。在用戶界面和數(shù)據(jù)訪問層之間的解耦非常重要,因?yàn)樗鼓軌蜉p松重新設(shè)計(jì)和獨(dú)立地應(yīng)用經(jīng)過測(cè)試的數(shù)據(jù)層代碼。

對(duì)于這個(gè)練習(xí),要使用 Spring MVC 為應(yīng)用程序創(chuàng)建基于 Web 的用戶界面。
 

理解 Spring MVC

在這一節(jié),您將使用 Spring MVC 為員工信息應(yīng)用程序創(chuàng)建基于 Web 的用戶界面。

用 Spring 實(shí)現(xiàn) MVC 設(shè)計(jì)模式

模型-視圖-控制器(MVC)設(shè)計(jì)模式是把 Web 層和數(shù)據(jù)層組件連接在一起,創(chuàng)建 Web 應(yīng)用程序的最好方式。幾乎所有的現(xiàn)代應(yīng)用程序框架都提供了對(duì)構(gòu)建 MVC 模式應(yīng)用程序的支持。

MVC 模式提供的一個(gè)關(guān)鍵優(yōu)勢(shì)就是應(yīng)用程序中數(shù)據(jù)和表示之間清晰的隔離。這種清晰的隔離讓數(shù)據(jù)和表示能夠相互獨(dú)立地發(fā)展。在生產(chǎn)系統(tǒng)中,這非常有價(jià)值,因?yàn)閿?shù)據(jù)和表示隨著時(shí)間的變化和需求的不同會(huì)有所變化。

對(duì)于 MVC 設(shè)計(jì)模式的深入介紹,請(qǐng)參閱 參考資料。以下討論僅為概述,僅適用于我們的示例應(yīng)用程序。

應(yīng)用程序的模型部分包含的元素封裝了數(shù)據(jù)和數(shù)據(jù)上的操作。實(shí)際上,已經(jīng)看到了示例應(yīng)用程序中的模型 —— 域模型。整個(gè)域模型創(chuàng)建和測(cè)試時(shí),沒有整合任何表示元素(用戶界面、報(bào)表,等等)。MVC 設(shè)計(jì)模式允許在獨(dú)立于視圖的情況下創(chuàng)建和測(cè)試模型。

應(yīng)用程序的視圖部分完全是用 Java Server Pages(JSP)技術(shù)和 JSP Standard Tag Library(JSTL)創(chuàng)建的。您很快就會(huì)發(fā)現(xiàn),可以獨(dú)立于模型創(chuàng)建視圖。在生產(chǎn)中這非常重要,因?yàn)?Web 頁面設(shè)計(jì)人員(而不是 Java 開發(fā)人員)可以處理視圖的創(chuàng)建和測(cè)試。

控制器是模型和視圖之間的關(guān)鍵連接。Spring MVC 是構(gòu)建基于 Web 的應(yīng)用程序的框架。在 Spring MVC 中,控制器處理傳入的 HTTP Web 請(qǐng)求。

有一組功能豐富的控制器庫組件可以用來開發(fā)子類。所有 Spring MVC 控制器庫類都實(shí)現(xiàn)了 org.springframework.web.servlet.mvc.Controller 接口。這個(gè)接口只有一個(gè)方法:

ModelAndView handleRequest(HttpServletRequest request,
            HttpServletResponse response)
            throws Exception
            

控制器檢查和處理傳入的請(qǐng)求,然后返回 ModelAndView 對(duì)象。Spring 有一個(gè)叫作 org.springframework.web.servlet.mvc.AbstractController 的抽象類,它實(shí)現(xiàn)了這個(gè)接口并處理大多數(shù)緩存和會(huì)話管理特性。在實(shí)踐中,所有專門的 Spring 控制器都派生自這個(gè)抽象類。表 4 描述了在示例應(yīng)用程序中使用的兩個(gè) Spring 控制器類:

控制器類 說明
AbstractController 抽象控制器基類。在控制器不需要處理傳入的命令/參數(shù)或處理表單時(shí)有用。在示例應(yīng)用程序中,它呈現(xiàn)初始頁面,列出數(shù)據(jù)庫中的全部員工。
AbstractCommandController 處理傳入命令的抽象控制器類。這個(gè)類解析傳入的 HTTP 請(qǐng)求并把指定的 Java 對(duì)象實(shí)例綁定到請(qǐng)求參數(shù),從而實(shí)現(xiàn)在控制器邏輯中對(duì)參數(shù)的輕松處理。

關(guān)于 Spring 中可以使用的其他控制器基類的更多信息,請(qǐng)參閱 參考資料。





創(chuàng)建 MainController

這個(gè)應(yīng)用程序中的 MainController 處理進(jìn)入應(yīng)用程序的初始進(jìn)入請(qǐng)求。這是在用戶通過 URL http://localhost:8080/dwspring/index.cgi 進(jìn)入應(yīng)用程序的主頁時(shí)發(fā)生的。

這個(gè)控制器顯示系統(tǒng)中的所有員工,并允許用戶單擊員工號(hào)上的連接,得到員工細(xì)節(jié)。MainController 的代碼如清單 17 所示:

            package com.ibm.dw.spring2.web;
            import java.util.List;
            import javax.servlet.http.HttpServletRequest;
            import javax.servlet.http.HttpServletResponse;
            import org.springframework.web.servlet.ModelAndView;
            import org.springframework.web.servlet.mvc.AbstractController;
            import com.ibm.dw.spring2.Employee;
            import com.ibm.dw.spring2.EmployeeService;
            public class MainController extends AbstractController{
            public ModelAndView handleRequestInternal(HttpServletRequest req,
            HttpServletResponse resp) {
            List <Employee> emps =  employeeService.findAll();
            return new ModelAndView("home", "employees", emps);
            }
            private EmployeeService employeeService;
            public void setEmployeeService(EmployeeService employeeService) {
            this.employeeService = employeeService;
            }
            }
            

在清單 17 中,請(qǐng)注意 MainController 沒有顯示任何用戶界面。相反,它訪問數(shù)據(jù)層,得到員工列表。為了得到列表,它調(diào)用 EmployeeService 實(shí)現(xiàn)的 findAll() 方法。這個(gè)實(shí)現(xiàn)通過 Spring 的依賴性注入被 “注入” 控制器,是 EmployeeDAO 的實(shí)例。馬上就會(huì)在 dwspring2-servlet.xml bean 描述符中配置它(請(qǐng)參見 連接 Spring MVC bean)。

要呈現(xiàn)用戶界面,控制器創(chuàng)建一個(gè) ModelAndView 對(duì)象并返回它。ModelAndView 對(duì)象創(chuàng)建時(shí)有三個(gè)參數(shù),如表 5 所述:

位置 參數(shù) 說明
1 視圖名稱 這是命名視圖的字符串。這個(gè)字符串被傳遞給視圖解析器組件,解析為特定視圖/表示組件 —— 本例中是 JSP。視圖解析器在運(yùn)行時(shí)由 Spring 引擎連接 。在 dwspring2-servlet.xml bean 描述符文件中配置視圖解析器。
2 模型名稱 命名從域模型提取的數(shù)據(jù)的字符串。數(shù)據(jù)本身在第三個(gè)參數(shù)中傳遞。這個(gè)數(shù)據(jù)通常由視圖在執(zhí)行期間呈現(xiàn)。對(duì)于 JSP,這個(gè)名稱是變量的名稱。變量值可以用 JSP 命令或 JSTL 標(biāo)記呈現(xiàn)。
3 模型對(duì)象 這是視圖呈現(xiàn)的模型數(shù)據(jù)。在視圖中,可按照參數(shù) 2 提供的名稱引用。

要掌握 Spring MVC 的操作,重要的是理解請(qǐng)求流程。圖 9 顯示了通過 Spring MVC 組件的請(qǐng)求流:

在圖 9 中,虛線矩形中的是 Spring 組件。進(jìn)入請(qǐng)求先由 Spring 的 DispatcherServlet 處理。這個(gè) servlet 通過連接的 URL 映射器組件對(duì)請(qǐng)求的進(jìn)入 URL 進(jìn)行映射。這個(gè)映射器組件提供了控制請(qǐng)求的實(shí)際控制器。一旦控制器處理請(qǐng)求,就把視圖名稱傳遞回 Spring MVC。然后 Spring MVC 調(diào)用連接的視圖解析器組件,傳遞進(jìn)要呈現(xiàn)的模型數(shù)據(jù)。視圖解析器組件把視圖名稱解析為視圖對(duì)象。這個(gè)視圖對(duì)象被傳遞給模型數(shù)據(jù),并向用戶呈現(xiàn)。

Both the URL 映射器和視圖解析器都是 Spring 2 提供的組件,可以在 bean 描述符 XML 文件中指導(dǎo) Spring 引擎連接它們。





連接 Spring MVC bean

在 Spring MVC 中,Web 應(yīng)用程序是在 Web 應(yīng)用程序上下文 中執(zhí)行的。這個(gè)上下文由 bean 描述符連接 XML 文件配置,就像數(shù)據(jù)層代碼一樣。

默認(rèn)情況下,用以下規(guī)則形成配置文件的名稱:采用 Spring DispatcherServlet 的 servlet 名稱,后面加上 _servlet.xml。

Spring DispatcherServlet 類通過 Web 容器(在這個(gè)示例中是 Tomcat)接受傳入的 Web 請(qǐng)求,并把它分配到某個(gè)控制器。

在這個(gè)示例中,DispatcherServletdwspring2 名稱配置,因此,配置文件應(yīng)當(dāng)在 dwspring2_servlet.xml。這個(gè)配置文件如清單 18 所示:

            <?xml version="1.0" encoding="UTF-8"?>
            <beans xmlns="http://www.springframework.org/schema/beans"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">
            <bean id="mainController" class="com.ibm.dw.spring2.web.MainController">
            <property name="employeeService">
            <ref bean="employeeService"/>
            </property>
            </bean>
            <bean name="empDetailsController" class="com.ibm.dw.spring2.web.EmpDetailsController">
            <property name="employeeService">
            <ref bean="employeeService"/>
            </property>
            </bean>
            <bean id="controllermap" class=
            "org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
            <property name="mappings">
            <props>
            <prop key="/home.cgi">mainController</prop>
            <prop key="/empdet.cgi">empDetailsController</prop>
            </props>
            </property>
            </bean>
            <bean id="viewResolver" class=
            "org.springframework.web.servlet.view.InternalResourceViewResolver">
            <property name="prefix" value="/jsp/" />
            <property name="suffix" value=".jsp"/>
            </bean>
            </beans>
            

在清單 18 中,可以看到不同 bean 的連接。首先,mainController bean 是 MainController 類的實(shí)例,被注入了一個(gè)到 employeeService bean 的引用。當(dāng)然這個(gè) bean 是在 dwspring2-service.xml 中在數(shù)據(jù)層定義的 EmployeeDAO 的實(shí)例。

另一個(gè)控制器,叫作 empDetailsController,也通過引用注入了相同的 employeeService 實(shí)例。在這一節(jié)后面將看到這個(gè)控制器的代碼。

被連接的第三個(gè) bean 是 controllermap。這是 org.springframework.web.servlet.handler.SimpleUrlHandlerMapping 的實(shí)例。這個(gè) Spring 提供的 bean 能根據(jù)映射屬性把進(jìn)入的 URL 請(qǐng)求映射到不同的控制器。在這個(gè)示例中,/home.cgi 被映射到 MainController,/empdet.cgi 被映射到 empDetailsController。

連接的最后一個(gè) bean 是視圖解析器。這個(gè) Spring 提供的 bean 把視圖名稱映射到實(shí)際視圖資源。這個(gè)示例中使用的是 org.springframework.web.servlet.view.InternalResourceViewResolver 的實(shí)例。這個(gè)視圖解析器根據(jù)進(jìn)入的視圖名稱,添加前綴和后綴,創(chuàng)建資源 URL。在清單 18 中,前綴被配置為 /jsp,后綴是 .jsp。例如,名為 home 的視圖被映射到 /jsp/home.jsp。這意味著 URL /jsp/home.jsp 呈現(xiàn)名為 home的視圖。





配置 Spring DispatcherServlet

WEB-INF/web.xml 部署描述符包含 DispatcherServlet 的配置。清單 19 顯示了相關(guān)的代碼片斷:

            <servlet>
            <description>
            Spring MVC Dispatcher Servlet</description>
            <display-name>
            DispatcherServlet</display-name>
            <servlet-name>dwspring2</servlet-name>
            <servlet-class>
            org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <load-on-startup>1</load-on-startup>
            </servlet>
            <servlet-mapping>
            <servlet-name>dwspring2</servlet-name>
            <url-pattern>*.cgi</url-pattern>
            </servlet-mapping>
            

指定 <load-on-startup> 確保在第一次啟動(dòng) Web 應(yīng)用程序時(shí),裝入 servlet。這還會(huì)觸發(fā) dwspring2-servlet.xml 配置文件的解析。

<servlet-mapping> 標(biāo)簽告訴 Tomcat 對(duì) for *.cgi 資源的全部 Web 請(qǐng)求路由到 DisplatcherServlet。* 號(hào)代表通配符匹配。所以,任何 http://host:port/dwspring/*.cgi 形式的請(qǐng)求,都被轉(zhuǎn)發(fā)到 DispatcherServlet。





用 Spring MVC 創(chuàng)建基于 JSP/JSTL 的用戶界面

MainController 類把 EmployeeList 傳遞給 home 視圖。 home 視圖由 InternalResourceViewResolver 解析成 /jsp/home.jsp。清單 20 是 home.jsp 的代碼。這個(gè)頁面只顯示在 ModelAndView 中以 employees 傳遞過來的員工列表。

            <%@ page language="java" contentType="text/html; charset=ISO-8859-1"
            pageEncoding="ISO-8859-1"%>
            <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
            <head>
            <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
            <link rel="stylesheet"  type="text/css" href="css/dwstyles.css"/>
            <title>dW Spring 2 Employee Data from DB2 via JPA</title>
            </head>
            <body>
            <h1>Spring 2 JPA Employee List</h1>
            <br>
              <table>
            <tr>
            <th>Employee number</th>
            <th>Name</th>
            </tr>
            <c:forEach var="emp" items="${employees}">
            <tr>
            <td><a href="empdet.cgi?empID=${emp.empid}">${emp.empno}</td>
            <td>${emp.firstName} ${emp.midInitial}  ${emp.lastName}</td>
            </tr>
            </c:forEach>
            </table>
            </body>
            </html>
            

在清單 20 中,可以看到用 JSTL 的 <c:forEach> 標(biāo)記迭代 ${employees} 列表中的每個(gè)員工。代碼在 ${emp.empno} 信息周圍創(chuàng)建鏈接。例如,圍繞 Joe Smith 這行生成的 URL 是 http://localhost:8080/dwspring/empdet.cgi?empID=1。

在點(diǎn)擊這個(gè) URL 鏈接時(shí),映射到 empdat.cgi 的資源被激活。根據(jù) web.xml 配置,Tomcat 知道要把全部 *.cgi 資源請(qǐng)求發(fā)送給 Spring 的 DispatcherServlet,而且 Spring 的 DispatcherServlet 根據(jù) dwspring2-servlet.xml 中的配置,也知道用 SimpleUrlHandlerMapping。SimpleUrlHandlerMapping 告訴 Spring 引擎把請(qǐng)求引導(dǎo)到 EmpDetailsController





處理命令的控制器

EmpDetailsController 的代碼如清單 21 所示:

            package com.ibm.dw.spring2.web;
            import javax.servlet.http.HttpServletRequest;
            import javax.servlet.http.HttpServletResponse;
            import org.springframework.validation.BindException;
            import org.springframework.web.servlet.ModelAndView;
            import org.springframework.web.servlet.mvc.AbstractCommandController;
            import com.ibm.dw.spring2.Employee;
            import com.ibm.dw.spring2.EmployeeService;
            public class EmpDetailsController extends AbstractCommandController{
            public EmpDetailsController() {
            setCommandClass(EmployeeDetailsCommand.class);
            }
            private EmployeeService employeeService;
            public void setEmployeeService(EmployeeService employeeService) {
            this.employeeService = employeeService;
            }
            protected ModelAndView handle(HttpServletRequest req,
            HttpServletResponse resp, Object cmd, BindException ex) throws Exception {
            EmployeeDetailsCommand ecmd = (EmployeeDetailsCommand) cmd;
            Employee emp = employeeService.findById(ecmd.getEmpID());
            return new ModelAndView("empdetails", "emp", emp);
            }
            }
            

EmpDetailsControllerAbstractCommandController 的子類。 AbstractCommandController 是個(gè)有用的控制器,在處理根據(jù)按鈕或 URL 點(diǎn)擊需要執(zhí)行的命令時(shí),可以從它派生子類。

在這個(gè)示例中,用戶點(diǎn)擊員工編號(hào) URL,命令被用來顯示員工的詳細(xì)信息。

AbstractCommandController 主要的增值是:它解析進(jìn)入的請(qǐng)求,得到請(qǐng)求參數(shù),并把參數(shù)綁定到所定義的進(jìn)入命令類的實(shí)例。進(jìn)入請(qǐng)求參數(shù)的名稱與命令類的屬性名稱匹配。命令類叫作 EmployeeDetailsCommand。EmployeeDetailsCommand 的代碼如清單 22 所示:

            package com.ibm.dw.spring2.web;
            public class EmployeeDetailsCommand {
            private long empID;
            public long getEmpID() {
            return empID;
            }
            public void setEmpID(long empID) {
            this.empID = empID;
            }
            }
            

在清單 22 中,可以看到 EmployeeDetailsCommand 命令類的簡(jiǎn)單結(jié)構(gòu)。它只有一個(gè)屬性,叫作 empIDAbstractCommandController 查詢叫作 empID 的進(jìn)入請(qǐng)求參數(shù),并把它綁定到 EmployeeDetailsCommand 類的實(shí)例。然后把它傳遞給 EmployeeDetailsController,在這里調(diào)用 handle() 方法(請(qǐng)參閱 清單 21)。

一般來說,可以從 AbstractCommandController 派生子類,處理任意數(shù)量的進(jìn)入請(qǐng)求參數(shù)。需要做的全部工作就是用對(duì)應(yīng)的屬性定義命令類。

清單 21 中,EmployeeDetailsController 把傳遞過來的 cmd 對(duì)象的類型轉(zhuǎn)換回 EmployeeDetailsCommand 實(shí)例,并提取 empID。然后用 empID 查詢特定 Employee 實(shí)例的 EmployeeService。這是通過 DAO 的 employeeService.findById() 方法進(jìn)行的。

清單 21 中,生成的 Employee 記錄作為 作為 emp,被傳遞給視圖。指定的視圖是 empdetails 視圖。同樣,Spring 引擎查看 InternalResourceViewResolver 來解析視圖。這個(gè)視圖解析器加上適當(dāng)?shù)那熬Y和后綴,并返回 /jsp/empdetails.jsp 作為視圖處理器。

empdetails.jsp 的代碼如清單 23 所示:

            <%@ page language="java" contentType="text/html; charset=ISO-8859-1"
            pageEncoding="ISO-8859-1"%>
            <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
            <head>
            <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
            <link rel="stylesheet"  type="text/css" href="css/dwstyles.css"/>
            <title>dW Spring 2 Employee Detail Information</title>
            </head>
            <body>
            <h1>Spring 2 JPA Employee Details</h1>
            <br>
               <table>
            <tr>
            <th colspan="2">Employee Information</th>
            </tr>
            <tr>
            <td>Employee No.</td><td>${emp.empno}</td>
            </tr>
            <tr>
            <td>Name</td><td>${emp.firstName} ${emp.midInitial}  ${emp.lastName}</td>
            </tr>
            <tr>
            <td>Address</td><td>${emp.addr.number} ${emp.addr.street}</td>
            </tr>
            <tr>
            <td>Phone</td><td>${emp.phoneNumber}</td>
            </tr>
            <tr>
            <td>Salary</td><td>${emp.salary}</td>
            </tr>
            <tr>
            <td>Bonus</td><td>${emp.bonus}</td>
            </tr>
            <tr>
            <td>Commission</td><td>${emp.commission}</td>
            </tr>
            </table>
            <br/>
            <br/>
            <a href="home.cgi">Go back to employee list</a>
            </body>
            </html>
            

在清單 23 中,進(jìn)入的 ${emp} 變量被用來顯示 HTML 表格中的員工詳細(xì)信息。

請(qǐng)注意,頁面底部的鏈接映射回員工清單頁面。URL 是 home.cgi。如果點(diǎn)擊這個(gè)鏈接,映射過程就再次開始,并傳遞到 MainController、然后 /jsp/home.jsp,依次類推。





添加層級(jí)樣式表

home.jsp 和 empdet.jsp 用 css/dwstyles.css 樣式表格式化它們的 HTML。樣式表代碼如清單 24 所示:

            h1 {
            font-family: arial;
            font-size: 28;
            align: left;
            font-weight: bold;
            font-style: italic;
            color: green;
            }
            h2 {
            font-family: serif, times;
            font-size: 18;
            align: left;
            color: blue;
            }
            th {
            font-family: verdana, arial;
            font-size: 13;
            font-weight: bold;
            align: left;
            background-color: black;
            color: white;
            }
            td {
            font-family: verdana, arial;
            font-size: 12;
            font-style: italic;
            text-align: left;
            }
            table {
            border-style: solid;
            border-width: thin;
            }
            .label {
            font-size: 16;
            font-weight: bold;
            font-style:   normal;
            text-align: right;
            }
            .name {
            font-size: 24;
            font-weight: bold;
            font-style:   italic;
            font-family: serif, times roman;
            }
            





創(chuàng)建 Eclipse WTP 動(dòng)態(tài) Web 項(xiàng)目

要查看完整應(yīng)用程序的效果,需要:

  1. 編譯代碼。
  2. 構(gòu)建可部署的 WAR 文件。WAR 文件是用標(biāo)準(zhǔn)格式創(chuàng)建的 JAR 文件,用于在 J2EE 兼容的 Web 層容器(例如 Tomcat)中部署。
  3. 把 WAR 文件部署到 Tomcat 服務(wù)器。

File->New->Project... 在 Eclipse 中創(chuàng)建新的動(dòng)態(tài) Web 項(xiàng)目。在 New Project 向?qū)е校?Web 分類下選擇 Dynamic Web Project,把項(xiàng)目命名為 spring2Web. See Figure 10:

接下來,添加源文件,并如圖 11 所示安排它們的位置。如果已經(jīng)下載了源文件發(fā)布(請(qǐng)參閱 下載),可以在文件管理器中拖放文件,把這些文件添加到 src 文件夾。




添加應(yīng)用程序庫

與數(shù)據(jù)層代碼的集成測(cè)試環(huán)境不同,WAR 文件中的 Web 應(yīng)用程序必須包含它需要的全部庫 JAR。圖 12 顯示了應(yīng)當(dāng)拖放到 WEB-INF/lib 目錄的 JAR 文件:

在圖 12 中,請(qǐng)注意可以在有依賴項(xiàng)的 Spring 下載的 lib\j2ee 下找到 jstl.jar 和 servlet-api.jar。 standard.jar 標(biāo)記庫來自 lib\jakarta-taglibs 目錄。

除了庫 JAR 文件,圖 12 還顯示了其他配置文件的位置。請(qǐng)確保在繼續(xù)之前找到了需要的文件。





從 Eclipse 項(xiàng)目導(dǎo)出 WAR 文件

要?jiǎng)?chuàng)建能部署到 Tomcat 的 WAR 文件,需要構(gòu)建項(xiàng)目并把它導(dǎo)出為 WAR 文件。

可以在導(dǎo)航器的 spring2Web 項(xiàng)目上右擊,選擇 Build Project,為 Tomcat 構(gòu)建項(xiàng)目。

把項(xiàng)目導(dǎo)出成 WAR 文件,請(qǐng)選擇 File->Export... 。在導(dǎo)出向?qū)е?,選擇 WAR File。Export 向?qū)?duì)話框如圖 13 所示。把導(dǎo)出的 WAR 文件命名為 dwspring.war 并單擊 Next

 

Tomcat 5.5 作為 Spring MVC 應(yīng)用程序的宿主

這一節(jié)介紹如何配置 Apache Tomcat,這是個(gè)開源的 Web 層容器,它與 Spring 協(xié)作,容納示例應(yīng)用程序。

Tomcat 5.5 中的 JSP 和 servlet 支持

毫無疑問,Tomcat 是目前為止最流行和最成熟的開源的 Web 層服務(wù)器。作為 Web 層服務(wù)器,Tomcat 可以運(yùn)行和執(zhí)行包含 JSP 和 servlet 的 Web 應(yīng)用程序。將要使用的 Tomcat 5.5.x 支持 Servlet 2.4 和 JSP 2.0 標(biāo)準(zhǔn)(請(qǐng)參閱 參考資料)。

如果您還未安裝,請(qǐng)立即下載和安裝最新版的 Tomcat 5.5.x 來運(yùn)行示例(請(qǐng)參閱 參考資料)。請(qǐng)從服務(wù)器上選擇 ZIP 文件下載,并把它解壓縮到選中的目錄。

這一節(jié)提供一些 Tomcat 服務(wù)器的通用操作指南。請(qǐng)參閱 Tomcat 文檔獲得更多細(xì)節(jié)。

基本 Tomcat 5.5 操作

對(duì) Tomcat 服務(wù)器執(zhí)行得最頻繁的操作有:

  • 啟動(dòng)和停止服務(wù)器
  • 向服務(wù)器部署和反部署應(yīng)用程序

啟動(dòng)和停止 Tomcat 服務(wù)器

在解壓縮 Tomcat 服務(wù)器的可執(zhí)行文件后,可以進(jìn)入服務(wù)器的 bin 子目錄,運(yùn)行 startup.bat 腳本啟動(dòng)服務(wù)器。彈出另一個(gè)運(yùn)行服務(wù)器的控制臺(tái)窗口。

要關(guān)閉服務(wù)器,請(qǐng)進(jìn)入 bin 子目錄并運(yùn)行 shutdown.bat 腳本。

部署 應(yīng)用程序到 Tomcat

要容易地部署應(yīng)用程序到 Tomcat,可以用內(nèi)置的管理器 Web 應(yīng)用程序或直接把 WAR 文件拷貝到 Tomcat 服務(wù)器的 webapps 子目錄。

如果想用管理器 Web 應(yīng)用程序,需要給一個(gè)用戶提供“manager”角色,啟用訪問。在開始 Tomcat 服務(wù)器之前,請(qǐng)查看 conf 子目錄中叫作 tomcat-users.xml 的文件。在這個(gè)文件中,查看:

<user username="tomcat" password="tomcat" roles="tomcat "/>
            

把這行改成:

<user username="tomcat" password="tomcat" roles="tomcat, manager"/>
            

然后可以通過 http://localhost:8080/manager/html 訪問管理器應(yīng)用程序。

如果通過把 WAR 文件直接拷貝到 webapps 目錄來部署,可能要在 Tomcat 服務(wù)器檢測(cè)到更新并部署新 WAR 文件之前稍等一會(huì)。

如果重新部署失敗

與使用示例應(yīng)用程序時(shí)的體驗(yàn)一樣,可能需要把代碼的新版本重新部署到 Tomcat 服務(wù)器。

根據(jù)使用的 Tomcat 發(fā)行版,在部署/重新部署 dwspring.war 文件時(shí),有時(shí)會(huì)遇到部署問題。如果在向服務(wù)器部署時(shí)遇到問題,部署 WAR 的解決問題的途徑是:

  1. 停止 Tomcat 服務(wù)器。
  2. 進(jìn)入 webapps 目錄,確保刪除了 dwspring.war 文件。
  3. 刪除 dwspring 目錄。
  4. 把新的 dwspring.war 文件復(fù)制到 webapps 目錄。
  5. 重新啟動(dòng) Tomcat 服務(wù)器。

為 Spring 2 準(zhǔn)備 Tomcat

在可以向 Tomcat 成功部署 dwspring.war 文件之前,需要進(jìn)行一些服務(wù)器設(shè)置。

在這一節(jié)執(zhí)行的主要過程有:

  1. 把 Spring 2 類裝入器添加到 Tomcat
  2. 把 Spring 2 上下文裝入器偵聽器添加到 Tomcat
  3. 把 DB2 JDBC 驅(qū)動(dòng)程序復(fù)制到 Tomcat
  4. 為 Tomcat 配置 JNDI DB2 數(shù)據(jù)源

把 Spring 2 類裝入器添加到 Tomcat 服務(wù)器

當(dāng) Spring JPA 應(yīng)用程序在 Tomcat 上運(yùn)行時(shí),要讓 JPA 支持正常工作,需要在類裝入期間進(jìn)行字節(jié)碼“連接”。來自 Tomcat 的標(biāo)準(zhǔn)類裝入器不支持這個(gè)。需要用特定于 Spring 的類裝入器實(shí)現(xiàn)這個(gè)功能。

要把這個(gè)特定于 Spring 的類裝入器安裝到 Tomcat 服務(wù)器,首先要把 spring-tomcat-weaver.jar 拷貝到 Tomcat 的 server/lib 子目錄。這個(gè)目錄包含的庫屬于 Tomcat 服務(wù)器私有??梢栽?Spring 2.0 下載的 dist/weaver 目錄下找到 spring-tomcat-weaver.jar 庫。

接下來,必須讓 Tomcat 知道對(duì)于示例應(yīng)用程序,應(yīng)當(dāng)替換標(biāo)準(zhǔn)類裝入器??梢栽?WAR 文件的 META-INF/context.xml 文件中指定這點(diǎn)。清單 25 中的粗體代碼配置類裝入器:


清單 25. 在 META-INF/context.xml 文件中配置類裝入器

            <Context>
            <Loader loaderClass="org.springframework.instrument.
            classloading.tomcat.TomcatInstrumentableClassLoader"/>
            ...
            </Context>
            





回頁首


把 Spring 2 的上下文裝入器偵聽器添加到 Tomcat

Spring 2 要求掛接到 Tomcat 的上下文裝入管道??梢栽?WAR 文件的 WEB-INF/web.xml 文件添加以下行進(jìn)行這個(gè)配置:

   <listener>
            <listener-class>
            org.springframework.web.context.ContextLoaderListener
            </listener-class>
            </listener>
            

在 web.xml 文件中,這必須在 <servlet><servlet-mapping> 定義之前。





回頁首


把 DB2 JDBC 驅(qū)動(dòng)程序拷貝到 Tomcat 服務(wù)器

Tomcat 是 Web 層容器,所以能夠管理自己的數(shù)據(jù)庫連接,也能做連接池。數(shù)據(jù)庫源由 Tomcat 管理,可以通過標(biāo)準(zhǔn)的 Java 命名和目錄接口 (JNDI)查詢機(jī)制訪問。員工系統(tǒng)在 Tomcat 內(nèi)作為 Web 應(yīng)用程序運(yùn)行,應(yīng)當(dāng)通過 Tomcat 的 JNDI 得到數(shù)據(jù)源。

要讓 Tomcat 在 Web 應(yīng)用程序部署期間找到 JDBC 驅(qū)動(dòng)程序,需要把 JAR 文件拷貝到 Tomcat 的系統(tǒng)庫目錄。在啟動(dòng) Tomcat 服務(wù)器之前,請(qǐng)把兩個(gè) JDBC 驅(qū)動(dòng)程序 JAR 文件從 DB2 發(fā)布拷貝到 Tomcat 的 common\lib 目錄。這兩個(gè)文件的名稱是 db2cc.jar 和 db2cc_licence_cu.jar。

放在 common\lib 目錄中的庫可以供 Tomcat 服務(wù)器和 Web 應(yīng)用程序共同使用。





回頁首


在 Tomcat 5 上配置 DB2 數(shù)據(jù)源管理和 JNDI

可以把一個(gè)應(yīng)用程序可以訪問的 DB2 數(shù)據(jù)源配置成 Web 應(yīng)用程序上下文的 JNDI 資源。方法是把清單 26 中突出的代碼放在自己 WAR 的 META-INF/context.xml 文件中:


清單 26. 在 META-INF/context.xml 中配置 JNDI 資源

            <Context>
            ...
            <Resource name="jdbc/dwspring2" auth="Container" type="javax.sql.DataSource"
            maxActive="100" maxIdle="30" maxWait="10000"
            username="bill"
            password="lotus123" driverClassName="com.ibm.db2.jcc.DB2Driver"
            url="jdbc:db2://192.168.23.36:50000/dwspring"/>
            </Context>
            

需要替換清單 26 中的 DB2 Express-C 服務(wù)器主機(jī)、用戶名和口令,反映自己的 DB2 Express-C 安裝情況。

清單 27 的配置通過名稱 java:comp/env/jdbc/dwspring2 提供了 JNDI 數(shù)據(jù)源。

還必須向部署描述符 web.xml 文件添加資源引用。請(qǐng)把這個(gè) <resource-ref> 元素添加到 web.xml 文件的末尾,如清單 27 所示:


清單 27. 添加數(shù)據(jù)源的 JNDI 資源引用

            ...
            <resource-ref>
            <description>DB Connection</description>
            <res-ref-name>jdbc/dwspring2</res-ref-name>
            <res-type>javax.sql.DataSource</res-type>
            <res-auth>Container</res-auth>
            </resource-ref>
            

清單 27 的配置使得可以在 Web 應(yīng)用程序中使用容器 JDBC 管理的數(shù)據(jù)源。
 

為 Tomcat 部署配置 Spring 2 應(yīng)用程序

要為 Tomcat 部署配置數(shù)據(jù)層代碼,仍然需要兩個(gè)細(xì)節(jié):

  • 告訴 Spring 2 引擎關(guān)于 bean 描述符配置文件的位置
  • 把 Spring 2 數(shù)據(jù)源配置的連接改為通過 JNDI 使用 Tomcat 的數(shù)據(jù)源管理和池管理

告訴 Spring 2 引擎配置文件的位置

要讓 Spring 2 引擎連接需要的數(shù)據(jù)層 bean,首先必須中找到并處理數(shù)據(jù)層的 bean 描述符配置文件。

在這個(gè)示例中,文件叫作 dwspring-service.xml。需要在部署的時(shí)候,在提供給 Tomcat 服務(wù)器的上下文參數(shù)中指定這個(gè)文件的位置。

這個(gè)上下文參數(shù)需要是 WEB-INF/web.xml 部署描述符的第一個(gè)元素,在清單 28 中用粗體表示:

            <?xml version="1.0" encoding="UTF-8"?>
            <web-app id="WebApp_ID" version="2.4"
            xmlns="http://java.sun.com/xml/ns/j2ee"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
            http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
            <display-name>
            spring2web</display-name>
            <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/dwspring2-service.xml</param-value>
            </context-param>
            ...
            

dwspring2-service.xml 是前面在測(cè)試時(shí)使用的配置文件的改動(dòng)版本。

這個(gè)配置文件被修改成通過 JNDI 使用 Tomcat 服務(wù)器的 JDBC 連接管理,而不是自己的連接管理。





修改 Spring 2 連接查找 Tomcat JNDI 數(shù)據(jù)源

dwspring2-service.xml 中使用 Tomcat 5 得到 JNDI 數(shù)據(jù)源需要進(jìn)行的修改在清單 29 中已突出顯示:

            <?xml version="1.0" encoding="UTF-8"?>
            <beans xmlns="http://www.springframework.org/schema/beans"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">
            <bean id="employeeService" class="com.ibm.dw.spring2.EmployeeDAO">
            <property name="entityManagerFactory" ref="entityManagerFactory"/>
            </bean>
            <bean id="entityManagerFactory" class=
            "org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
            <property name="dataSource" ref="dataSource"/>
            <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.TopLinkJpaVendorAdapter">
            <property name="showSql" value="true"/>
            <property name="generateDdl" value="false"/>
            <property name="databasePlatform" value=
            "oracle.toplink.essentials.platform.database.DB2Platform"/>
            </bean>
            </property>
            <property name="loadTimeWeaver">
            <bean class="org.springframework.instrument.classloading.SimpleLoadTimeWeaver"/>
            </property>
            </bean>
            <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
            <property name="jndiName" value="java:comp/env/jdbc/dwspring2" />
            </bean>
            <bean id="transactionManager"
            class="org.springframework.orm.jpa.JpaTransactionManager">
            <property name="entityManagerFactory" ref="entityManagerFactory"/>
            <property name="dataSource" ref="dataSource"/>
            </bean>
            </beans>
            

清單 29 中用于創(chuàng)建數(shù)據(jù)源的 bean 現(xiàn)在是 org.springframework.jndi.JndiObjectFactoryBean??梢杂眠@個(gè) bean 執(zhí)行對(duì)特定的容器管理資源的 JNDI 查詢。在這個(gè)示例中,Tomcat 管理的 DB2 Express-C 數(shù)據(jù)源的名稱被配置成 java:comp/env/jdbc/dwspring2。

在清單 29 中,請(qǐng)注意 jpaVendorAdapterGenerateDdl 屬性被設(shè)置成 false。這是必需的,因?yàn)椴幌胱?Spring 2 在每次啟動(dòng)應(yīng)用程序時(shí)都刪除和重建所有數(shù)據(jù)庫表。只有在集成測(cè)試期間,這個(gè)屬性才應(yīng)設(shè)置成 true
 

試用 Spring 2 Web 應(yīng)用程序

在這一節(jié),將以編程方式向員工數(shù)據(jù)庫添加一些數(shù)據(jù),并試用員工信息應(yīng)用程序。

在事務(wù)內(nèi)添加數(shù)據(jù)到 DB2 Express-C

在可以試用應(yīng)用程序之前,需要在數(shù)據(jù)庫中有些員工數(shù)據(jù)。因?yàn)榧蓽y(cè)試在每次運(yùn)行時(shí)都會(huì)刪除所有表(通過回滾事務(wù)),所以數(shù)據(jù)庫不包含任何已經(jīng)可以使用的數(shù)據(jù)。

當(dāng)然,可以用 DB2 Express-C 工具手工輸入數(shù)據(jù)。但在這里將學(xué)習(xí)如何用編程的方式添加數(shù)據(jù)。

集成測(cè)試基于 AbstractJpaTests 的一個(gè)精彩特性,是測(cè)試完成時(shí)數(shù)據(jù)庫的所有變化都被回滾掉這一事實(shí),這就允許下一步驟迅速執(zhí)行。如果每個(gè)步驟都在事務(wù)中運(yùn)行到結(jié)束,那么數(shù)據(jù)修改會(huì)在 RDBMS 中持久,這就需要在啟動(dòng)下一測(cè)試之前刪除它們。所以在理論上,如果想向數(shù)據(jù)庫添加數(shù)據(jù),這個(gè)類一點(diǎn)用也沒有。

幸運(yùn)的是,在基于 AbstractJpaTests 的集成測(cè)試中修改的任何數(shù)據(jù),都可以在測(cè)試中調(diào)用 setComplete() 方法,提交給 RDBMS。這個(gè)方法不是回滾數(shù)據(jù),而是提交數(shù)據(jù),從而讓修改持久。

FillTableWithEmployeeInfo 類是 Spring2Tutorial 項(xiàng)目的一部分,如清單 30 所示。這個(gè)類利用這個(gè)功能把六個(gè)員工的信息保持到 DB2 Express-C。

            package com.ibm.dw.spring2;
            import java.util.Date;
            import java.util.List;
            import org.springframework.test.jpa.AbstractJpaTests;
            public class FillTableWithEmployeeInfo extends AbstractJpaTests {
            private EmployeeService employeeService;
            public void setEmployeeService(EmployeeService employeeService) {
            this.employeeService = employeeService;
            }
            protected String[] getConfigLocations() {
            return new String[] { "classpath:/com/ibm/dw/spring2/dwspring2-service.xml" };
            }
            public void testTableFiller() {
            Employee emp1 = new Employee("0001", "Joe", "R","Smith",
            "4853", "Engineer", 3, ‘M‘,
            20000.00, 0.00, 0.00,
            new Address(10, "Walker Street")
            , new Date(), new Date());
            Employee emp2 = new Employee("0002", "John","T","Lockheed",
            "4333", "Sales", 2, ‘M‘,
            40000.00, 0.00, 5000.00,
            new Address(20, "Walker Street")
            , new Date(), new Date());
            Employee emp3 = new Employee("0003", "Mary","M","Johnson",
            "4383", "Admin", 3, ‘F‘,
            60000.00, 0.00, 390.00,
            new Address(123, "Booth Ave")
            , new Date(), new Date());
            Employee emp4 = new Employee("0004", "Mike","S","Lee",
            "4322", "Sales", 3, ‘M‘,
            30000.00, 0.00, 20000.00,
            new Address(7, "Wilard Drive")
            , new Date(), new Date());
            Employee emp5 = new Employee("0005", "Joan","K","Winfry",
            "4113", "Marketing", 2, ‘F‘,
            40000.00, 0.00, 0.00,
            new Address(1293, "Davis Blvd")
            , new Date(), new Date());
            Employee emp6 = new Employee("0006", "Steve","L","Bingham",
            "4632", "Marketing", 3, ‘M‘,
            50000.00, 0.00, 0.00,
            new Address(5, "Booth Ave")
            , new Date(), new Date());
            employeeService.save(emp1);
            employeeService.save(emp2);
            employeeService.save(emp3);
            employeeService.save(emp4);
            employeeService.save(emp5);
            employeeService.save(emp6);
            setComplete();
            }
            }
            

在 Eclipse 的導(dǎo)航器視圖,右擊 FillTableWithEmployeeInfo.java 并選擇 Run As... JUnit Test,用數(shù)據(jù)填充數(shù)據(jù)庫。





使用員工信息應(yīng)用程序

要在部署到 Tomcat 之后訪問應(yīng)用程序的主頁,請(qǐng)?jiān)跒g覽器中輸入以下 URL:http://localhost:8080/dwspring/home.cgi。這假設(shè)正在本地機(jī)器上運(yùn)行 Tomcat 服務(wù)器。如果使用網(wǎng)絡(luò)上的其他服務(wù)器,只要替換 URL 中的主機(jī)名即可。

應(yīng)用程序的第一頁,MainController 和 home.jsp,如圖 14 所示:

每個(gè)員工編號(hào)都是一個(gè)可以點(diǎn)擊的 URL 鏈接,點(diǎn)擊之后顯示包含選中員工的員工細(xì)節(jié)頁面(圖 15):

圖 15 的頁面由 EmpDetailsController 創(chuàng)建,通過 empdet.jsp 視圖呈現(xiàn)。
 

結(jié)束語

Spring 2 是種功能全面的框架??梢杂眠\(yùn)行時(shí)通過 XML 配置文件動(dòng)態(tài)連接起來的軟件組件,創(chuàng)建基于 Web 的服務(wù)器端應(yīng)用程序。

可以用 POJO 實(shí)現(xiàn)業(yè)務(wù)對(duì)象。POJO 易于在容器之外創(chuàng)建和測(cè)試,也可以在其他應(yīng)用程序或同一應(yīng)用程序的其他部分重用。

Spring 2 的 JPA 集成允許在 POJO 源代碼內(nèi)通過 JPA 注釋把數(shù)據(jù)庫持久性元數(shù)據(jù)添加到 POJO。

有了 Spring DAO 支持,可以創(chuàng)建在 POJO 上操作的服務(wù),并用 JPA 負(fù)責(zé)持久性 —— 不需要顯式地編寫煩瑣的實(shí)體管理器和事務(wù)管理代碼。

Spring 的 JPA 測(cè)試支持允許在支持 JPA 的 POJO 和 DSO 上創(chuàng)建集成測(cè)試??梢栽趦?nèi)存中運(yùn)行的數(shù)據(jù)庫或外部服務(wù)器上的數(shù)據(jù)上真正的 RDBMS 上執(zhí)行這些測(cè)試。

可以用 Spring MVC 把基于 Web 的用戶界面添加到測(cè)試過的數(shù)據(jù)層棧。Spring MVC 可以與各種視圖技術(shù)一起工作,包括本教程中使用的 JSP 和 JSTL。通過保持模型(域模型)代碼與用戶界面代碼的清晰分離, Spring 簡(jiǎn)化了 Web 應(yīng)用程序的維護(hù)。

可以把基于 Web 的 Spring 應(yīng)用程序打包到 WAR 文件,在 Tomcat 5 服務(wù)器上進(jìn)行生產(chǎn)部署。
 

下載

描述 名字 大小 下載方法
source code j-spring2code.zip 20KB HTTP
 
前一頁 第 14 頁,共 16 頁 后一頁


對(duì)本教程的評(píng)價(jià)

幫助我們改進(jìn)這些內(nèi)容


參考資料

學(xué)習(xí)

獲得產(chǎn)品和技術(shù)
  • 開放源碼 | Eclipse 專區(qū):developerWorks 提供了關(guān)于這個(gè)健壯的框架和工具平臺(tái)的豐富信息。

  • Spring framework:下載 Spring 2 框架。

  • DB2 Express-C:DB2 9 的免費(fèi)版本,它提供與 DB2 Express Edition 相同的核心數(shù)據(jù)服務(wù)器特性,為使用 C/C++、Java、.NET、PHP 和其他編程語言構(gòu)建和部署應(yīng)用程序提供了堅(jiān)實(shí)基礎(chǔ)。

  • Apache Tomcat:Tomcat 服務(wù)器的最新版本可以從 Tomcat 的官方站點(diǎn)獲得。

  • JPA reference implementation:JPA 參考實(shí)現(xiàn)可以從開源的 GlassFish Java EE 5 服務(wù)器項(xiàng)目上獨(dú)立下載。
本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服