隨筆-88  評(píng)論-77  文章-48  trackbacks-0

          如果關(guān)心開發(fā)人員的最新熱點(diǎn),那么您可能聽說過 IOC (控制倒置,Inversion of Control)容器和 AOP (面向方面編程)。不過,像許多開發(fā)人員一樣,您可能不清楚在自己的開發(fā)工作中如何使用這些技術(shù)。在本文中,通過具體介紹使用 Hibernate 和 Spring 在企業(yè)應(yīng)用程序中構(gòu)建一個(gè)事務(wù)持久層,您會(huì)認(rèn)識(shí)到這些技術(shù)。

          Hibernate 是 Java 平臺(tái)上的一種流行的、容易使用的開放源代碼對(duì)象關(guān)系(OR)映射框架。Spring 是一個(gè) AOP 框架和 IOC 容器。這兩種技術(shù)一起提供了本文中介紹的開發(fā)工作的基礎(chǔ)。將使用 Hibernate 把一些持久性對(duì)象映射到關(guān)系數(shù)據(jù)庫(kù)中,用 Spring 使 Hibernate 更容易使用并提供聲明性事務(wù)支持。由于為示例類編寫測(cè)試代碼時(shí)使用了 DbUnit,我還附帶介紹了一點(diǎn) TDD (測(cè)試驅(qū)動(dòng)的開發(fā))的內(nèi)容。

          注意,本文假定讀者熟悉 Java 平臺(tái)上的企業(yè)開發(fā),包括 JDBC、OR 映射內(nèi)容、J2EE 設(shè)計(jì)模式如 DAO,以及聲明性事務(wù)支持,如 Enterprise JavaBean (EJB)技術(shù)所提供的事務(wù)支持。理解這里的討論不需要成為這些技術(shù)的專家,也不需要熟悉 AOP、IOC 或者 TDD,因?yàn)樵诒疚闹袑?duì)這三者都做了介紹。

          我將首先介紹兩種開發(fā)技術(shù),然后分析例子。

          Hibernate 簡(jiǎn)介

          Hibernate 是 Java 平臺(tái)上的一種全功能的、開放源代碼 OR 映射框架。Hibernate 在許多方面類似于 EJB CMP CMR (容器管理的持久性/容器管理的關(guān)系)和 JDO(Java Data Objects)。與 JDO 不同,Hibernate 完全著眼于關(guān)系數(shù)據(jù)庫(kù)的 OR 映射,并且包括比大多數(shù)商業(yè)產(chǎn)品更多的功能。大多數(shù) EJB CMP CMR 解決方案使用代碼生成實(shí)現(xiàn)持久性代碼,而 JDO 使用字節(jié)碼修飾。與之相反,Hibernate 使用反射和運(yùn)行時(shí)字節(jié)碼生成,使它對(duì)于最終用戶幾乎是透明的(以前 Hibernate 的實(shí)現(xiàn)只使用反射,它有助于調(diào)試,當(dāng)前版本保留了這種選項(xiàng))。

          移植基于 Hibernate 的應(yīng)用程序

          如果應(yīng)用程序必須在多個(gè) RDBMS 系統(tǒng)上運(yùn)行 ,那么基于 Hibernate 的應(yīng)用程序可以毫不費(fèi)力地移植到 IBM DB2、MySQL、PostgreSQL、Sybase、Oracle、HypersonicSQL 和許多其他數(shù)據(jù)庫(kù)。我最近甚至將一個(gè)應(yīng)用程序從 MySQL 移植到 Hibernate 沒有很好支持的 Firebird,而這種移植是很容易的。有關(guān)在 Postgres 和 MySQL 之間轉(zhuǎn)換的案例分析,請(qǐng)參閱 參考資料

          Hibernate 可以模擬繼承(有幾種方式)、關(guān)聯(lián)(一對(duì)一或者一對(duì)多、containment 和 aggregation)和 composition。我將在本文中討論每種關(guān)系類型的幾個(gè)例子。

          Hibernate 提供了一種稱為 Hibernate Query Language (HQL) 的 查詢語言,它類似于 JDO 的 JDOQL 和 EJB 的 EJB QL,盡管它更接近于前者。但是 Hibernate 沒有就此止步:它還可以進(jìn)行直接的 SQL 查詢和/或使用 object criteria很容易地在運(yùn)行時(shí)構(gòu)成查詢條件。在本文的例子中我將只使用 HQL。

          與 EJB CMP CMR 不同,Hibernate 像 JDO 一樣可以在 J2EE 容器內(nèi)部或者外部工作,這可以讓那些進(jìn)行 TDD 和敏捷開發(fā)的人受益。





          回頁(yè)首


          Spring 簡(jiǎn)介

          AOP 專家 Nicholas Lesiecki 第一次向我解釋 AOP 時(shí),他說的我一個(gè)詞也沒理解,我覺得就像第一次考慮使用 IOC 容器的可能性時(shí)一樣。每一種技術(shù)的概念基礎(chǔ)本身就需要很好地消化,每一種技術(shù)所使用的各種各樣的縮寫讓事情更糟了——特別是其中許多術(shù)語與我們已經(jīng)使用的根本不一樣了。

          像許多技術(shù)一樣,理解這兩種技術(shù)的實(shí)際使用比學(xué)習(xí)理論更容易。經(jīng)過自己對(duì) AOP 和 IOC 容器實(shí)現(xiàn)(即 XWork、PicoContainer 和 Spring)的分析,我發(fā)現(xiàn)這些技術(shù)可以幫助我獲得功能,而不會(huì)在多框架中添加基于代碼的依賴性。它們都將成為我后面開發(fā)項(xiàng)目的一部分。

          簡(jiǎn)單地說,AOP 讓開發(fā)人員可以創(chuàng)建非行為性的關(guān)注點(diǎn),稱為橫切關(guān)注點(diǎn),并將它們插入到應(yīng)用程序代碼中。使用 AOP 后,公共服務(wù)(比如日志、持久性、事務(wù)等)就可以分解成方面并應(yīng)用到域?qū)ο笊希瑫r(shí)不會(huì)增加域?qū)ο蟮膶?duì)象模型的復(fù)雜性。

          關(guān)于 DbUnit

          用新的框架開發(fā)而不進(jìn)行單元測(cè)試,就像不帶保護(hù)網(wǎng)走鋼絲:當(dāng)然可以這樣做,但是很可能會(huì)受傷。我選擇在有保護(hù)網(wǎng)的條件下開發(fā),對(duì)我來說這個(gè)保護(hù)網(wǎng)就是 TDD。在有 DbUnit 之前,對(duì)依賴于數(shù)據(jù)庫(kù)的代碼進(jìn)行測(cè)試是不太容易的。DbUnit 是 JUnit 的一個(gè)擴(kuò)展,它提供了依賴于數(shù)據(jù)庫(kù)的單元測(cè)試的框架。我用 DbUnit 編寫本文中示例類的測(cè)試代碼。雖然在本文中沒有出現(xiàn),不過在本文源代碼中提供了 DbUnit 代碼(請(qǐng)參閱 參考資料 )。有關(guān) DbUnit 的介紹,請(qǐng)參閱 Philippe Girolami 的“ Control your test-environment with DbUnit and Anthill ” ( developerWorks,2004 年 4 月)。

          IOC 允許創(chuàng)建一個(gè)可以構(gòu)造對(duì)象的應(yīng)用環(huán)境,然后向這些對(duì)象傳遞它們的協(xié)作對(duì)象。正如單詞 倒置 所表明的,IOC 就像反過來的 JNDI。沒有使用一堆抽象工廠、服務(wù)定位器、單元素(singleton)和直接構(gòu)造(straight construction),每一個(gè)對(duì)象都是用其協(xié)作對(duì)象構(gòu)造的。因此是由容器管理協(xié)作對(duì)象(collaborator)。

          Spring 既是一個(gè) AOP 框架、也是一個(gè) IOC 容器。我記得 Grady Booch 說過,對(duì)象最好的地方是可以替換它們,而 Spring 最好的地方是它有助于您替換它們。有了 Spring,只要用 JavaBean 屬性和配置文件加入依賴性(協(xié)作對(duì)象)。然后可以很容易地在需要時(shí)替換具有類似接口的協(xié)作對(duì)象。

          Spring 為 IOC 容器和 AOP 提供了很好的入口(on-ramp)。因此,不需要熟悉 AOP 就可以理解本文中的例子。所需要知道的就是將要用 AOP 為示例應(yīng)用程序聲明式地添加事務(wù)支持,與使用 EJB 技術(shù)時(shí)的方式基本相同。要了解 IOC 容器、AOP 和 Spring 的更多內(nèi)容,請(qǐng)參閱 參考資料





          回頁(yè)首


          具體到業(yè)務(wù)

          在本文的其余部分,所有的討論都將基于一個(gè)實(shí)際的例子。起點(diǎn)是一個(gè)企業(yè)應(yīng)用程序,要為它實(shí)現(xiàn)一個(gè)事務(wù)持久層。持久層是一個(gè)對(duì)象關(guān)系數(shù)據(jù)庫(kù),它包括像 UserUser GroupRolesContactInfo 這些熟悉的抽象。

          在深入到數(shù)據(jù)庫(kù)的要素——查詢和事務(wù)管理——之前,需要建立它的基礎(chǔ):對(duì)象關(guān)系映射。我將用 Hibernate 設(shè)置它,并只使用一點(diǎn) Spring。





          回頁(yè)首


          用 Hibernate 進(jìn)行 OR 映射

          Hibernate 使用 XML ( *.hbm.xml) 文件將 Java 類映射到表,將 JavaBean 屬性映射到數(shù)據(jù)庫(kù)表。幸運(yùn)的是,有一組 XDoclet 標(biāo)簽支持 Hibernate 開發(fā),這使得創(chuàng)建所需要的 *.hbm.xml 文件更容易了。清單 1 中的代碼將一個(gè) Java 類映射到數(shù)據(jù)庫(kù)表。關(guān)于 XDoclet 標(biāo)簽的更多內(nèi)容,請(qǐng)參閱 參考資料

          清單 1. 將 Java 類映射到 DB 表

          												
          												
          												
          														
          														
          														
          																
          																
          																
          																		
          																				[User.java]
          																		
          																
          																/**
           * 
                  @hibernate.class table="TBL_USER"
           * ..
           * ..
           * ...
           */
          public class User {
          
          	private Long id = new Long(-1);
          	private String email;
          	private String password;
          	
          	.
          	.
          	.
          
          	/**
          	 * @return
          	 * 
                  
          														
          														
          														
          														
          																
          																
          																
          																		
          																		
          																		
          																				@hibernate.id column="PK_USER_ID" 
          	 * 		unsaved-value="-1" 
          	 *              generator-class="native" 
          																		
          																
          																 
          	 */
          	public Long getId() {
          		return id;
          	}
          
          	...
          
          
          	/**
          	 * 
                  
          														
          														
          														
          														
          																
          																
          																
          																		
          																		
          																		
          																				@hibernate.property column="VC_EMAIL" 
          	 * 			type="string" 
          	 * 			update="false"
          	 * 			insert="true"
          	 * 			unique="true"
          	 * 			not-null="true"
          	 * 			length="82" 
          																		
          																
          																	 * @return
          	 */
          	public String getEmail() {
          		return email;
          	}
          
          	/**
          	 * 
                  
          														
          														
          														
          														
          																
          																
          																
          																		
          																		
          																		
          																				@hibernate.property column="VC_PASSWORD" 
          	 * 			type="string" 
          	 * 			update="false"
          	 * 			insert="true"
          	 * 			unique="true"
          	 * 			not-null="true"
          	 * 			length="20" 
          																		
          																
          																	 * @return
          	 */
          	public String getPassword() {
          		return password;
          	}
          
          	...
          	...
          	...
          }
          			
          
                
          														
          														
          														
          												
          												
          												
          										

          可以看到, @hibernate.class table="TBL_USER" 標(biāo)簽將 User 映射到 TBL_USER 表。 @hibernate.property column="VC_PASSWORD" 將 JavaBean 屬性 password 映射到 VC_PASSWORD 列。 @hibernate.id column="PK_USER_ID" 標(biāo)簽聲明 id 屬性是主鍵,它將使用本機(jī)( generator-class="native" )數(shù)據(jù)庫(kù)機(jī)制生成鍵(例如,Oracle sequences 和 SQL Server Identity 鍵)。Hibernate 可以指定 generator-class="native" 以外的、其他可以想象的得到主鍵獲得策略,不過我更愿意使用 native。 typelength屬性用于從 Hibernate *.hbm.xml OR 映射文件生成表。這些 final 屬性是可選的,因?yàn)槭褂玫目赡懿皇?green-field 數(shù)據(jù)庫(kù)。在這個(gè)例子中,已經(jīng)有數(shù)據(jù)庫(kù)了,所以不需要額外的屬性。( green-field 應(yīng)用程序是一個(gè)新的應(yīng)用程序, green-field 數(shù)據(jù)是新應(yīng)用程序的一個(gè)新數(shù)據(jù)庫(kù)。不會(huì)經(jīng)常開發(fā)一個(gè)全新的應(yīng)用程序,不過偶爾有一兩次也不錯(cuò))。

          看過了表如何映射到類以及列如何映射到 JavaBean 屬性,該使用 Hibernate 在 OR 數(shù)據(jù)庫(kù)中設(shè)置一些關(guān)系了。

          設(shè)置對(duì)象關(guān)系

          在本節(jié)中,我將只觸及 Hibernate 提供的設(shè)置對(duì)象間關(guān)系的選項(xiàng)的一小部分。首先設(shè)置像 UserUser GroupRolesContactInfo 這些類之間的關(guān)系。其中一些關(guān)系如圖 1 所示,這是數(shù)據(jù)庫(kù)的驗(yàn)證對(duì)象模型。


          圖 1. 關(guān)系的圖示
          關(guān)系的圖示

          如您所見,在上述抽象中存在各種各樣的關(guān)系。 UserContactInfo 有一對(duì)一關(guān)系。 ContactInfo 的生命周期與 User 相同(用數(shù)據(jù)庫(kù)的術(shù)語,UML 中的組成 aka 級(jí)聯(lián)刪除)。如果刪除 User ,則相應(yīng)的 ContactInfo 也會(huì)刪除。在 User s 與 Role s 之間存在多對(duì)多關(guān)系(即與獨(dú)立生命周期相關(guān)聯(lián))。在 Group s 與 User s 之間存在一對(duì)多關(guān)系,因?yàn)榻M有許多用戶。用戶可以存在于組外,即是 aggregation 而不是 composition (用數(shù)據(jù)庫(kù)的說法,在 Group s 和 Users 之間沒有級(jí)聯(lián)刪除關(guān)系)。此外, UserEmployee 有子類關(guān)系,就是說, Employee 的類型為 User 。表 1 顯示了如何用 XDoclet 標(biāo)簽創(chuàng)建一些不同類型的對(duì)象關(guān)系。

          表 1. 用 XDoclet 創(chuàng)建對(duì)象關(guān)系

          關(guān)系 Java/XDoclet SQL DDL(由 Hibernate Schema Export 生成的 MySQL)
          組包含用戶

          一對(duì)多

          Aggregation

          雙向
          (Group<-->Users)

          [Group.java]
          /**
          *
          * @return
          *
          * @hibernate.bag name="users"
          * cascade="save-update"
          * lazy="true"
          * inverse="true"
          *
          * @hibernate.collection-key
          * column="FK_GROUP_ID"
          *
          * @hibernate.collection-one-to-many
          * class="net.sf.hibernateExamples.User"
          */
          public List getUsers() {
          return users;
          }

          [User.java]
          /**
          * @hibernate.many-to-one
          * column="FK_GROUP_ID"
          * class="net.sf.hibernateExamples.Group"
          */
          public Group getGroup() {
          return group;
          }


          create table TBL_USER (
          PK_USER_ID BIGINT NOT NULL AUTO_INCREMENT,
          USER_TYPE VARCHAR(255) not null,
          FK_GROUP_ID BIGINT,
          VC_EMAIL VARCHAR(82) not null unique,
          primary key (PK_USER_ID)
          )


          create table TBL_GROUP (
          PK_GROUP_ID BIGINT NOT NULL AUTO_INCREMENT,
          VC_DESCRIPTION VARCHAR(255),
          VC_NAME VARCHAR(40) unique,
          primary key (PK_GROUP_ID)
          )

          alter table TBL_USER add index (FK_GROUP_ID),
          add constraint FK_111 foreign key (FK_GROUP_ID)
          references TBL_GROUP (PK_GROUP_ID)

          用戶有聯(lián)系信息

          一對(duì)一

          Composition
          單向
          (User-->ContactInfo)

          [User.java]
          /**
          * @return
          *
          * @hibernate.one-to-one cascade="all"
          *
          */
          public ContactInfo getContactInfo() {
          return contactInfo;
          }

          [ContactInfo.java]
          (Nothing to see here. Unidirectional!)
          create table TBL_USER (
          PK_USER_ID BIGINT NOT NULL AUTO_INCREMENT,
          USER_TYPE VARCHAR(255) not null,
          FK_GROUP_ID BIGINT,
          VC_EMAIL VARCHAR(82) not null unique,
          primary key (PK_USER_ID)
          )

          create table TBL_CONTACT_INFO (
          PK_CONTACT_INFO_ID BIGINT not null,
          ...
          ...
          ...
          primary key (PK_CONTACT_INFO_ID)
          )

          用戶與角色關(guān)聯(lián)

          多對(duì)多

          Association

          單向
          (Users-->Roles)

          [User.java]
          /**
          * @return
          * @hibernate.bag
          * table="TBL_JOIN_USER_ROLE"
          * cascade="all"
          * inverse="true"
          *
          * @hibernate.collection-key
          * column="FK_USER_ID"
          *
          * @hibernate.collection-many-to-many
          * class="net.sf.hibernateExamples.Role"
          * column="FK_ROLE_ID"
          *
          */
          public List getRoles() {
          return roles;
          }

          [Role.java]
          Nothing to see here. Unidirectional!
          create table TBL_ROLE (
          PK_ROLE_ID BIGINT NOT NULL AUTO_INCREMENT,
          VC_DESCRIPTION VARCHAR(200),
          VC_NAME VARCHAR(20),
          primary key (PK_ROLE_ID)
          )

          create table TBL_USER (
          PK_USER_ID BIGINT NOT NULL AUTO_INCREMENT,
          USER_TYPE VARCHAR(255) not null,
          FK_GROUP_ID BIGINT,
          VC_EMAIL VARCHAR(82) not null unique,
          primary key (PK_USER_ID)
          )

          create table TBL_JOIN_USER_ROLE (
          FK_USER_ID BIGINT not null,
          FK_ROLE_ID BIGINT not null
          )

          雇員是用戶

          Inheritance

          用戶

          雇員

          [User.java]
          /**
          * @hibernate.class table="TBL_USER"
          * discriminator-value="2"
          * @hibernate.discriminator column="USER_TYPE"
          *
          ...
          ...
          ...
          */
          public class User {

          [Employee.java]
          /**
          * @hibernate.subclass discriminator-value = "1"
          */
          public class Employee extends User{

          create table TBL_USER (
          PK_USER_ID BIGINT NOT NULL AUTO_INCREMENT,
          USER_TYPE VARCHAR(255) not null,
          FK_GROUP_ID BIGINT,
          VC_EMAIL VARCHAR(82) not null unique,
          primary key (PK_USER_ID)
          )

          要了解在 Hibernate 中設(shè)置對(duì)象關(guān)系的更多內(nèi)容,請(qǐng)參閱 參考資料





          回頁(yè)首


          Hibernate 中的查詢

          Hibernate 有三種類型的查詢:

          • Criteria, object composition
          • SQL
          • HQL

          在下面的例子中將只使用 HQL。本節(jié)還要使用 Spring,用它的 AOP-driven HibernateTemplate 簡(jiǎn)化 Hibernate 會(huì)話的處理。在本節(jié)將開發(fā)一個(gè) DAO(Data Access Object)。要了解更多關(guān)于 DAO 的內(nèi)容,請(qǐng)參閱 參考資料

          清單 2 展示了兩個(gè)方法:一個(gè)使用 HQL 查詢的組查詢,另一個(gè)是后面接一個(gè)操作的組查詢。注意在第二個(gè)方法中,Spring HibernateTemplate 是如何簡(jiǎn)化會(huì)話管理的。

          清單 2. 使用查詢

          												
          												
          												
          														
          														
          														
          																import net.sf.hibernate.HibernateException;
          import net.sf.hibernate.Session;
          import net.sf.hibernate.Query;
          import org.springframework.orm.hibernate.HibernateCallback;
          import org.springframework.orm.hibernate.support.HibernateDaoSupport;
          
          
          
          /**
           * @author Richard Hightower
           * ArcMind Inc. http://www.arc-mind.com
           */
          public class UserDAO extends HibernateDaoSupport{
          
            .
            .
            .
          
            /**
          	* Demonstrates looking up a group with a HQL query
          	* @param email
          	* @return
          	*/	
          	public Group findGroupByName(String name) {
          	   return (Group) getHibernateTemplate().find("from Group g where g.name=?",name).get(0);
          	}
          	
          	/**
          	 * Demonstrates looking up a group and forcing it to populate users (relationship was lazy)
          	 * @param email
          	 * @return
          	 */	
          	public Group findPopulatedGroupByName(final String name) {
          	   HibernateCallback callback = new HibernateCallback(){
          
          	    public Object doInHibernate(Session session) throws HibernateException, SQLException {
          			Group group =null;
          			String query = "from Group g where g.name=?";
          			Query queryObject = getHibernateTemplate().createQuery(session, query);
          			queryObject.setParameter(0, name);
          			group = (Group) queryObject.list().get(0);
          			group.getUsers().size();//force load
          			return group;
          			}
          			
          		};
          		
          		return (Group) getHibernateTemplate().execute(callback);
          	}
            .
            .
            .
          }			
          
          														
          												
          												
          												
          										

          您可能會(huì)注意到第二個(gè)方法比第一個(gè)方法復(fù)雜得多,因?yàn)樗鼜?qiáng)迫加載 users 集合。因?yàn)?Group->Users 之間的關(guān)系設(shè)置為 lazy initialize(即表 2 中 lazy="true" ),組對(duì)象需要一個(gè)活躍的會(huì)話以查詢用戶。在定義 GroupUser s 之間關(guān)系時(shí)設(shè)置這個(gè)屬性為 lazy="false" ,則不需要第二個(gè)方法。在這種情況下,可能使用第一種方法 ( findGroupByName ) 列出組,用第二種方法( findPopulatedGroupByName )查看組細(xì)節(jié)。





          回頁(yè)首


          Spring IOC 和 Hibernate

          使用 Spring 時(shí),在 J2EE 容器內(nèi)和容器外工作一樣容易。比如在最近的項(xiàng)目中,我在 Eclipse 中,使用 HSQL 和本地?cái)?shù)據(jù)庫(kù)對(duì)使用 Hibernate 事務(wù)管理器的 Hypersonic SQL 數(shù)據(jù)庫(kù)進(jìn)行持久性單元測(cè)試。然后,在部署到 J2EE 服務(wù)器時(shí),將持久層轉(zhuǎn)換為使用 J2EE 數(shù)據(jù)源(通過 JNDI)、JTA 事務(wù)和使用 FireBird (一個(gè)開放源代碼版本的 Interbase)。這是用 Spring 作為 IOC 容器完成的。

          從清單 3 中可以看出,Spring 允許加入依賴性。注意清單中應(yīng)用程序上下文文件是如何配置 dataSource 的。 dataSource 傳遞給 sessionFactorysessionFactory 傳遞給 UserDAO

          清單 3. Spring IOC 和 Hibernate

          												
          												
          												
          														
          														
          														
          																<beans>
          
          	<!-- Datasource that works in any application server
          		You could easily use J2EE data source instead if this were
          		running inside of a J2EE container.
          	 -->
          	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
          		<property name="driverClassName"><value>com.mysql.jdbc.Driver</value></property>
          		<property name="url"><value>jdbc:mysql://localhost:3306/mysql</value></property>
          		<property name="username"><value>root</value></property>
          		<property name="password"><value></value></property>
          	</bean>
          
          	<!-- Hibernate SessionFactory -->
          	<bean id="sessionFactory" class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
          		<property name="dataSource"><ref local="dataSource"/></property>
          		
          		<!-- Must references all OR mapping files. -->
          		<property name="mappingResources">
          			<list>
          		        <value>net/sf/hibernateExamples/User.hbm.xml</value>
          		        <value>net/sf/hibernateExamples/Group.hbm.xml</value>
          		        <value>net/sf/hibernateExamples/Role.hbm.xml</value>		        
          		        <value>net/sf/hibernateExamples/ContactInfo.hbm.xml</value>
          			</list>
          		</property>
          		
          		<!-- Set the type of database; changing this one property will port this to Oracle, 
          			 MS SQL etc. -->
          		<property name="hibernateProperties">
          			<props>
          			  <prop key="hibernate.dialect">net.sf.hibernate.dialect.MySQLDialect</prop>
          			</props>
          		</property>
          	</bean>
          	
          	<!-- Pass the session factory to our UserDAO -->
          	<bean id="userDAO" class="net.sf.hibernateExamples.UserDAO">
          		<property name="sessionFactory"><ref local="sessionFactory"/></property>
          	</bean>
          	
          </beans>			
          
          														
          												
          												
          												
          										

          設(shè)置了 UserDAO 后,下一步就是定義并使用更多的查詢以展示可以完成的操作。Hibernate 可以用預(yù)定義查詢將查詢存儲(chǔ)到源代碼之外,如清單 4 所示。

          清單 4. 預(yù)定義查詢

          												
          												
          												
          														
          														
          														
          																
          																
          																
          																		
          																				[User.java]
          																		
          																
          																/**
           * @author Richard Hightower
           * ArcMind Inc. http://www.arc-mind.com
           * @hibernate.class table="TBL_USER" discriminator-value="2" 
           * @hibernate.discriminator column="USER_TYPE"
           * 
           * @hibernate.query name="AllUsers" query="from User user order by user.email asc"
           * 
           * @hibernate.query name="OverheadStaff" 
           * query="from Employee employee join employee.group g where g.name not in ('ENGINEERING','IT')"
           * 
           * @hibernate.query name="CriticalStaff" 
           * query="from Employee employee join employee.group g where g.name in ('ENGINEERING','IT')"
           * 
           * @hibernate.query name="GetUsersInAGroup" 
           * query="select user from Group g join g.users user"
           * 
           * @hibernate.query name="GetUsersNotInAGroup" 
           * query="select user from User user where user.group is null"
           * 
           * @hibernate.query name="UsersBySalaryGreaterThan" 
           * query="from User user inner join user.contactInfo info where info.salary > ?1"
           * 
           * @hibernate.query name="UsersBySalaryBetween" 
           * query="from User user join user.contactInfo info where info.salary between ?1 AND ?2"
           * 
           * @hibernate.query name="UsersByLastNameLike" 
           * query="from User user join user.contactInfo info where info.lastName like ?1"
           * 
           * @hibernate.query name="GetEmailsOfUsers" 
           * query="select user.email from Group g join g.users as user where g.name = ?1"
           * 
           */
          public class User {
          			.
          			.
          			.			
          
                
          														
          														
          														
          												
          												
          												
          										

          上述代碼定義了幾個(gè)預(yù)定義查詢。 預(yù)定義查詢 是存儲(chǔ)在 *.hbm.xml文件中的查詢。在清單 5 中,可以看到如何執(zhí)行預(yù)定義查詢。

          清單 5. 使用預(yù)定義查詢

          												
          												
          												
          														
          														
          														
          																
          																
          																
          																		
          																				[UserDAO.java]
          																		
          																
          																	/**
          	 * Demonstrates a query that returns a String.
          	 */                
          	public String[] getUserEmailsInGroup(String groupName){
          		List emailList =
          		getHibernateTemplate().findByNamedQuery("GetEmailsOfUsers"); 
          		return (String [])
          			emailList.toArray(new String[emailList.size()]);
          	}
          
          
          	/**
          	 * Demonstrates a query that returns a list of Users
          	 *
          	 * @return A list of emails of all of the users in the authentication system.
          	 * 
          	 */                
          	public List getUsers(){
          		return getHibernateTemplate().findByNamedQuery("AllUsers");
          	}
          
                /**
          	* Demonstrates passing a single argument to a query. 
          	*
          	* @return A list of UserValue objects.
          	* 
          	*/                    
          	public List getUsersBySalary(float salary){
          	   return getHibernateTemplate()
          	       .findByNamedQuery("UsersBySalaryGreaterThan",
          	            new Float(salary));
          	}
          
          
          	/**
          	 * Demonstrates passing multiple arguments to a query
          	 * 
          	 * @return A list of UserValue objects.
          	 * 
          	 */                    
          	public List getUsersBySalaryRange(float start, float stop){
                          return getHibernateTemplate()
          		 .findByNamedQuery("UsersBySalaryBetween",
          			new Object[] {new Float(start), new Float(stop)});
          	}
          	
          
                
          														
          														
          														
          												
          												
          												
          										

          查詢進(jìn)行時(shí),可以在持久層中加上最后一層:使用 Spring 的事務(wù)管理。





          回頁(yè)首


          用 Spring 管理事務(wù)

          Spring 可以聲明式地管理事務(wù)。例如, UserDAO.addUser 方法當(dāng)前不是在單個(gè)事務(wù)中執(zhí)行的。因此,組中的每一個(gè)用戶都插入到自己的事務(wù)中,如清單 6 所示。

          清單 6. 添加一組用戶

          												
          												
          												
          														
          														
          														
          																
          																
          																
          																		
          																				[UserDAO.java]
          																		
          																
          																/**
           * @param group
           */
          public void addGroup(Group group) {
          	getHibernateTemplate().save(group);
          	
          }
          
          
                  [UserDAOTest.java]
          
          public void testAddGroupOfUsers(){
          	Group group = new Group();
          	
          	for (int index=0; index < 10; index++){
          		User user = new User();
          		user.setEmail("rick"+index+"@foobar.com" );
          		user.setPassword("foobar");
          		group.addUser(user);	
          	}
          	
          	group.setName("testGroup");
          	
          	userDAO.addGroup(group);
          	assertNotNull(group.getId());
          	
          	Group group2 = userDAO.findPopulatedGroupByName("testGroup");
          	
          	assertEquals("testGroup",group2.getName());
          	assertEquals(10, group2.getUsers().size());
          	String email = ((User)group2.getUsers().get(0)).getEmail();
          	assertEquals("rick0@foobar.com", email);
          
          	
          }
          
          
                
          														
          														
          														
          												
          												
          												
          										

          不建議使用上述解決方案,因?yàn)槊恳粋€(gè) User 都要在自己的事務(wù)中插入到數(shù)據(jù)庫(kù)中。如果出現(xiàn)問題,那么只能添加部分用戶。如果希望保留 ACID 屬性(即保證所有都發(fā)生或者所有都不發(fā)生),可以通過程序進(jìn)行事務(wù)管理,但是它很快就會(huì)變得一團(tuán)糟了。相反,應(yīng)使用 Spring 的 AOP 來支持聲明式的事務(wù),如清單 7 所示。

          清單 7. 聲明式管理事務(wù)

          												
          												
          												
          														
          														
          														
          																
          																
          																
          																		
          																				[applicationContext.xml]
          																		
          																
          																<!-- Pass the session factory to our UserDAO -->
             <bean id="userDAOTarget" class="net.sf.hibernateExamples.UserDAOImpl">
          	<property name="sessionFactory"><ref local="sessionFactory"/></property>
             </bean>
          	
             <bean id="transactionManager" 
             	class="org.springframework.orm.hibernate.HibernateTransactionManager">
                <property name="sessionFactory"><ref bean="sessionFactory"/></property>
             </bean>
          
             <bean id="userDAO"
               class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
          	<property name="transactionManager"><ref local="transactionManager"/></property>
          	<property name="target"><ref local="userDAOTarget"/></property>
          	<property name="transactionAttributes">
          		<props>
          			<prop key="addGroup">PROPAGATION_REQUIRED</prop>
          		</props>
          	</property>
              </bean>
          
                
          														
          														
          														
          												
          												
          												
          										

          注意在準(zhǔn)備清單 7 的代碼時(shí),我重新改寫了 UserDAO 并提取了其接口。這個(gè)接口現(xiàn)在是 UserDAO ,它的實(shí)現(xiàn)類是 UserDAOImpl 。這樣清單 7 中的事務(wù)代碼就使用了帶有事務(wù)屬性 (PROPAGATION_REQUIRED) UserDAO.addGroup() 方法。現(xiàn)在只要底層數(shù)據(jù)庫(kù)支持,就可以在一個(gè)事務(wù)中添加所有用戶。





          回頁(yè)首


          結(jié)束語

          在本文中,介紹了如何使用 Hibernate 和 Spring 實(shí)現(xiàn)一個(gè)事務(wù)持久層。Hibernate 是一種先進(jìn)的 OR 映射工具,而 Spring 是一個(gè) AOP 框架和 IOC 容器。這兩種技術(shù)的綜合使用,使得開發(fā)人員可以編寫媲美數(shù)據(jù)庫(kù)廠商的代碼,它可以在 J2EE 容器中運(yùn)行,也可以單獨(dú)運(yùn)行。使用了 DbUnit (JUnit 的擴(kuò)展)構(gòu)建和測(cè)試本文中例子的所有代碼,雖然這不是討論的重點(diǎn)。

          要了解有關(guān) AOP、IOC 容器和測(cè)試驅(qū)動(dòng)開發(fā)的更多內(nèi)容,請(qǐng)參閱 參考資料

          如果關(guān)心開發(fā)人員的最新熱點(diǎn),那么您可能聽說過 IOC (控制倒置,Inversion of Control)容器和 AOP (面向方面編程)。不過,像許多開發(fā)人員一樣,您可能不清楚在自己的開發(fā)工作中如何使用這些技術(shù)。在本文中,通過具體介紹使用 Hibernate 和 Spring 在企業(yè)應(yīng)用程序中構(gòu)建一個(gè)事務(wù)持久層,您會(huì)認(rèn)識(shí)到這些技術(shù)。

          Hibernate 是 Java 平臺(tái)上的一種流行的、容易使用的開放源代碼對(duì)象關(guān)系(OR)映射框架。Spring 是一個(gè) AOP 框架和 IOC 容器。這兩種技術(shù)一起提供了本文中介紹的開發(fā)工作的基礎(chǔ)。將使用 Hibernate 把一些持久性對(duì)象映射到關(guān)系數(shù)據(jù)庫(kù)中,用 Spring 使 Hibernate 更容易使用并提供聲明性事務(wù)支持。由于為示例類編寫測(cè)試代碼時(shí)使用了 DbUnit,我還附帶介紹了一點(diǎn) TDD (測(cè)試驅(qū)動(dòng)的開發(fā))的內(nèi)容。

          注意,本文假定讀者熟悉 Java 平臺(tái)上的企業(yè)開發(fā),包括 JDBC、OR 映射內(nèi)容、J2EE 設(shè)計(jì)模式如 DAO,以及聲明性事務(wù)支持,如 Enterprise JavaBean (EJB)技術(shù)所提供的事務(wù)支持。理解這里的討論不需要成為這些技術(shù)的專家,也不需要熟悉 AOP、IOC 或者 TDD,因?yàn)樵诒疚闹袑?duì)這三者都做了介紹。

          我將首先介紹兩種開發(fā)技術(shù),然后分析例子。

          Hibernate 簡(jiǎn)介

          Hibernate 是 Java 平臺(tái)上的一種全功能的、開放源代碼 OR 映射框架。Hibernate 在許多方面類似于 EJB CMP CMR (容器管理的持久性/容器管理的關(guān)系)和 JDO(Java Data Objects)。與 JDO 不同,Hibernate 完全著眼于關(guān)系數(shù)據(jù)庫(kù)的 OR 映射,并且包括比大多數(shù)商業(yè)產(chǎn)品更多的功能。大多數(shù) EJB CMP CMR 解決方案使用代碼生成實(shí)現(xiàn)持久性代碼,而 JDO 使用字節(jié)碼修飾。與之相反,Hibernate 使用反射和運(yùn)行時(shí)字節(jié)碼生成,使它對(duì)于最終用戶幾乎是透明的(以前 Hibernate 的實(shí)現(xiàn)只使用反射,它有助于調(diào)試,當(dāng)前版本保留了這種選項(xiàng))。

          移植基于 Hibernate 的應(yīng)用程序

          如果應(yīng)用程序必須在多個(gè) RDBMS 系統(tǒng)上運(yùn)行 ,那么基于 Hibernate 的應(yīng)用程序可以毫不費(fèi)力地移植到 IBM DB2、MySQL、PostgreSQL、Sybase、Oracle、HypersonicSQL 和許多其他數(shù)據(jù)庫(kù)。我最近甚至將一個(gè)應(yīng)用程序從 MySQL 移植到 Hibernate 沒有很好支持的 Firebird,而這種移植是很容易的。有關(guān)在 Postgres 和 MySQL 之間轉(zhuǎn)換的案例分析,請(qǐng)參閱 參考資料

          Hibernate 可以模擬繼承(有幾種方式)、關(guān)聯(lián)(一對(duì)一或者一對(duì)多、containment 和 aggregation)和 composition。我將在本文中討論每種關(guān)系類型的幾個(gè)例子。

          Hibernate 提供了一種稱為 Hibernate Query Language (HQL) 的 查詢語言,它類似于 JDO 的 JDOQL 和 EJB 的 EJB QL,盡管它更接近于前者。但是 Hibernate 沒有就此止步:它還可以進(jìn)行直接的 SQL 查詢和/或使用 object criteria很容易地在運(yùn)行時(shí)構(gòu)成查詢條件。在本文的例子中我將只使用 HQL。

          與 EJB CMP CMR 不同,Hibernate 像 JDO 一樣可以在 J2EE 容器內(nèi)部或者外部工作,這可以讓那些進(jìn)行 TDD 和敏捷開發(fā)的人受益。





          回頁(yè)首


          Spring 簡(jiǎn)介

          AOP 專家 Nicholas Lesiecki 第一次向我解釋 AOP 時(shí),他說的我一個(gè)詞也沒理解,我覺得就像第一次考慮使用 IOC 容器的可能性時(shí)一樣。每一種技術(shù)的概念基礎(chǔ)本身就需要很好地消化,每一種技術(shù)所使用的各種各樣的縮寫讓事情更糟了——特別是其中許多術(shù)語與我們已經(jīng)使用的根本不一樣了。

          像許多技術(shù)一樣,理解這兩種技術(shù)的實(shí)際使用比學(xué)習(xí)理論更容易。經(jīng)過自己對(duì) AOP 和 IOC 容器實(shí)現(xiàn)(即 XWork、PicoContainer 和 Spring)的分析,我發(fā)現(xiàn)這些技術(shù)可以幫助我獲得功能,而不會(huì)在多框架中添加基于代碼的依賴性。它們都將成為我后面開發(fā)項(xiàng)目的一部分。

          簡(jiǎn)單地說,AOP 讓開發(fā)人員可以創(chuàng)建非行為性的關(guān)注點(diǎn),稱為橫切關(guān)注點(diǎn),并將它們插入到應(yīng)用程序代碼中。使用 AOP 后,公共服務(wù)(比如日志、持久性、事務(wù)等)就可以分解成方面并應(yīng)用到域?qū)ο笊希瑫r(shí)不會(huì)增加域?qū)ο蟮膶?duì)象模型的復(fù)雜性。

          關(guān)于 DbUnit

          用新的框架開發(fā)而不進(jìn)行單元測(cè)試,就像不帶保護(hù)網(wǎng)走鋼絲:當(dāng)然可以這樣做,但是很可能會(huì)受傷。我選擇在有保護(hù)網(wǎng)的條件下開發(fā),對(duì)我來說這個(gè)保護(hù)網(wǎng)就是 TDD。在有 DbUnit 之前,對(duì)依賴于數(shù)據(jù)庫(kù)的代碼進(jìn)行測(cè)試是不太容易的。DbUnit 是 JUnit 的一個(gè)擴(kuò)展,它提供了依賴于數(shù)據(jù)庫(kù)的單元測(cè)試的框架。我用 DbUnit 編寫本文中示例類的測(cè)試代碼。雖然在本文中沒有出現(xiàn),不過在本文源代碼中提供了 DbUnit 代碼(請(qǐng)參閱 參考資料 )。有關(guān) DbUnit 的介紹,請(qǐng)參閱 Philippe Girolami 的“ Control your test-environment with DbUnit and Anthill ” ( developerWorks,2004 年 4 月)。

          IOC 允許創(chuàng)建一個(gè)可以構(gòu)造對(duì)象的應(yīng)用環(huán)境,然后向這些對(duì)象傳遞它們的協(xié)作對(duì)象。正如單詞 倒置 所表明的,IOC 就像反過來的 JNDI。沒有使用一堆抽象工廠、服務(wù)定位器、單元素(singleton)和直接構(gòu)造(straight construction),每一個(gè)對(duì)象都是用其協(xié)作對(duì)象構(gòu)造的。因此是由容器管理協(xié)作對(duì)象(collaborator)。

          Spring 既是一個(gè) AOP 框架、也是一個(gè) IOC 容器。我記得 Grady Booch 說過,對(duì)象最好的地方是可以替換它們,而 Spring 最好的地方是它有助于您替換它們。有了 Spring,只要用 JavaBean 屬性和配置文件加入依賴性(協(xié)作對(duì)象)。然后可以很容易地在需要時(shí)替換具有類似接口的協(xié)作對(duì)象。

          Spring 為 IOC 容器和 AOP 提供了很好的入口(on-ramp)。因此,不需要熟悉 AOP 就可以理解本文中的例子。所需要知道的就是將要用 AOP 為示例應(yīng)用程序聲明式地添加事務(wù)支持,與使用 EJB 技術(shù)時(shí)的方式基本相同。要了解 IOC 容器、AOP 和 Spring 的更多內(nèi)容,請(qǐng)參閱 參考資料





          回頁(yè)首


          具體到業(yè)務(wù)

          在本文的其余部分,所有的討論都將基于一個(gè)實(shí)際的例子。起點(diǎn)是一個(gè)企業(yè)應(yīng)用程序,要為它實(shí)現(xiàn)一個(gè)事務(wù)持久層。持久層是一個(gè)對(duì)象關(guān)系數(shù)據(jù)庫(kù),它包括像 UserUser GroupRolesContactInfo 這些熟悉的抽象。

          在深入到數(shù)據(jù)庫(kù)的要素——查詢和事務(wù)管理——之前,需要建立它的基礎(chǔ):對(duì)象關(guān)系映射。我將用 Hibernate 設(shè)置它,并只使用一點(diǎn) Spring。





          回頁(yè)首


          用 Hibernate 進(jìn)行 OR 映射

          Hibernate 使用 XML ( *.hbm.xml) 文件將 Java 類映射到表,將 JavaBean 屬性映射到數(shù)據(jù)庫(kù)表。幸運(yùn)的是,有一組 XDoclet 標(biāo)簽支持 Hibernate 開發(fā),這使得創(chuàng)建所需要的 *.hbm.xml 文件更容易了。清單 1 中的代碼將一個(gè) Java 類映射到數(shù)據(jù)庫(kù)表。關(guān)于 XDoclet 標(biāo)簽的更多內(nèi)容,請(qǐng)參閱 參考資料

          清單 1. 將 Java 類映射到 DB 表

          												
          												
          												
          														
          														
          														
          																
          																
          																
          																		
          																				[User.java]
          																		
          																
          																/**
           * 
                  @hibernate.class table="TBL_USER"
           * ..
           * ..
           * ...
           */
          public class User {
          
          	private Long id = new Long(-1);
          	private String email;
          	private String password;
          	
          	.
          	.
          	.
          
          	/**
          	 * @return
          	 * 
                  
          														
          														
          														
          														
          																
          																
          																
          																		
          																		
          																		
          																				@hibernate.id column="PK_USER_ID" 
          	 * 		unsaved-value="-1" 
          	 *              generator-class="native" 
          																		
          																
          																 
          	 */
          	public Long getId() {
          		return id;
          	}
          
          	...
          
          
          	/**
          	 * 
                  
          														
          														
          														
          														
          																
          																
          																
          																		
          																		
          																		
          																				@hibernate.property column="VC_EMAIL" 
          	 * 			type="string" 
          	 * 			update="false"
          	 * 			insert="true"
          	 * 			unique="true"
          	 * 			not-null="true"
          	 * 			length="82" 
          																		
          																
          																	 * @return
          	 */
          	public String getEmail() {
          		return email;
          	}
          
          	/**
          	 * 
                  
          														
          														
          														
          														
          																
          																
          																
          																		
          																		
          																		
          																				@hibernate.property column="VC_PASSWORD" 
          	 * 			type="string" 
          	 * 			update="false"
          	 * 			insert="true"
          	 * 			unique="true"
          	 * 			not-null="true"
          	 * 			length="20" 
          																		
          																
          																	 * @return
          	 */
          	public String getPassword() {
          		return password;
          	}
          
          	...
          	...
          	...
          }
          			
          
                
          														
          														
          														
          												
          												
          												
          										

          可以看到, @hibernate.class table="TBL_USER" 標(biāo)簽將 User 映射到 TBL_USER 表。 @hibernate.property column="VC_PASSWORD" 將 JavaBean 屬性 password 映射到 VC_PASSWORD 列。 @hibernate.id column="PK_USER_ID" 標(biāo)簽聲明 id 屬性是主鍵,它將使用本機(jī)( generator-class="native" )數(shù)據(jù)庫(kù)機(jī)制生成鍵(例如,Oracle sequences 和 SQL Server Identity 鍵)。Hibernate 可以指定 generator-class="native" 以外的、其他可以想象的得到主鍵獲得策略,不過我更愿意使用 native。 typelength屬性用于從 Hibernate *.hbm.xml OR 映射文件生成表。這些 final 屬性是可選的,因?yàn)槭褂玫目赡懿皇?green-field 數(shù)據(jù)庫(kù)。在這個(gè)例子中,已經(jīng)有數(shù)據(jù)庫(kù)了,所以不需要額外的屬性。( green-field 應(yīng)用程序是一個(gè)新的應(yīng)用程序, green-field 數(shù)據(jù)是新應(yīng)用程序的一個(gè)新數(shù)據(jù)庫(kù)。不會(huì)經(jīng)常開發(fā)一個(gè)全新的應(yīng)用程序,不過偶爾有一兩次也不錯(cuò))。

          看過了表如何映射到類以及列如何映射到 JavaBean 屬性,該使用 Hibernate 在 OR 數(shù)據(jù)庫(kù)中設(shè)置一些關(guān)系了。

          設(shè)置對(duì)象關(guān)系

          在本節(jié)中,我將只觸及 Hibernate 提供的設(shè)置對(duì)象間關(guān)系的選項(xiàng)的一小部分。首先設(shè)置像 UserUser GroupRolesContactInfo 這些類之間的關(guān)系。其中一些關(guān)系如圖 1 所示,這是數(shù)據(jù)庫(kù)的驗(yàn)證對(duì)象模型。


          圖 1. 關(guān)系的圖示
          關(guān)系的圖示

          如您所見,在上述抽象中存在各種各樣的關(guān)系。 UserContactInfo 有一對(duì)一關(guān)系。 ContactInfo 的生命周期與 User 相同(用數(shù)據(jù)庫(kù)的術(shù)語,UML 中的組成 aka 級(jí)聯(lián)刪除)。如果刪除 User ,則相應(yīng)的 ContactInfo 也會(huì)刪除。在 User s 與 Role s 之間存在多對(duì)多關(guān)系(即與獨(dú)立生命周期相關(guān)聯(lián))。在 Group s 與 User s 之間存在一對(duì)多關(guān)系,因?yàn)榻M有許多用戶。用戶可以存在于組外,即是 aggregation 而不是 composition (用數(shù)據(jù)庫(kù)的說法,在 Group s 和 Users 之間沒有級(jí)聯(lián)刪除關(guān)系)。此外, UserEmployee 有子類關(guān)系,就是說, Employee 的類型為 User 。表 1 顯示了如何用 XDoclet 標(biāo)簽創(chuàng)建一些不同類型的對(duì)象關(guān)系。

          表 1. 用 XDoclet 創(chuàng)建對(duì)象關(guān)系

          關(guān)系 Java/XDoclet SQL DDL(由 Hibernate Schema Export 生成的 MySQL)
          組包含用戶

          一對(duì)多

          Aggregation

          雙向
          (Group<-->Users)

          [Group.java]
          /**
          *
          * @return
          *
          * @hibernate.bag name="users"
          * cascade="save-update"
          * lazy="true"
          * inverse="true"
          *
          * @hibernate.collection-key
          * column="FK_GROUP_ID"
          *
          * @hibernate.collection-one-to-many
          * class="net.sf.hibernateExamples.User"
          */
          public List getUsers() {
          return users;
          }

          [User.java]
          /**
          * @hibernate.many-to-one
          * column="FK_GROUP_ID"
          * class="net.sf.hibernateExamples.Group"
          */
          public Group getGroup() {
          return group;
          }


          create table TBL_USER (
          PK_USER_ID BIGINT NOT NULL AUTO_INCREMENT,
          USER_TYPE VARCHAR(255) not null,
          FK_GROUP_ID BIGINT,
          VC_EMAIL VARCHAR(82) not null unique,
          primary key (PK_USER_ID)
          )


          create table TBL_GROUP (
          PK_GROUP_ID BIGINT NOT NULL AUTO_INCREMENT,
          VC_DESCRIPTION VARCHAR(255),
          VC_NAME VARCHAR(40) unique,
          primary key (PK_GROUP_ID)
          )

          alter table TBL_USER add index (FK_GROUP_ID),
          add constraint FK_111 foreign key (FK_GROUP_ID)
          references TBL_GROUP (PK_GROUP_ID)

          用戶有聯(lián)系信息

          一對(duì)一

          Composition
          單向
          (User-->ContactInfo)

          [User.java]
          /**
          * @return
          *
          * @hibernate.one-to-one cascade="all"
          *
          */
          public ContactInfo getContactInfo() {
          return contactInfo;
          }

          [ContactInfo.java]
          (Nothing to see here. Unidirectional!)
          create table TBL_USER (
          PK_USER_ID BIGINT NOT NULL AUTO_INCREMENT,
          USER_TYPE VARCHAR(255) not null,
          FK_GROUP_ID BIGINT,
          VC_EMAIL VARCHAR(82) not null unique,
          primary key (PK_USER_ID)
          )

          create table TBL_CONTACT_INFO (
          PK_CONTACT_INFO_ID BIGINT not null,
          ...
          ...
          ...
          primary key (PK_CONTACT_INFO_ID)
          )

          用戶與角色關(guān)聯(lián)

          多對(duì)多

          Association

          單向
          (Users-->Roles)

          [User.java]
          /**
          * @return
          * @hibernate.bag
          * table="TBL_JOIN_USER_ROLE"
          * cascade="all"
          * inverse="true"
          *
          * @hibernate.collection-key
          * column="FK_USER_ID"
          *
          * @hibernate.collection-many-to-many
          * class="net.sf.hibernateExamples.Role"
          * column="FK_ROLE_ID"
          *
          */
          public List getRoles() {
          return roles;
          }

          [Role.java]
          Nothing to see here. Unidirectional!
          create table TBL_ROLE (
          PK_ROLE_ID BIGINT NOT NULL AUTO_INCREMENT,
          VC_DESCRIPTION VARCHAR(200),
          VC_NAME VARCHAR(20),
          primary key (PK_ROLE_ID)
          )

          create table TBL_USER (
          PK_USER_ID BIGINT NOT NULL AUTO_INCREMENT,
          USER_TYPE VARCHAR(255) not null,
          FK_GROUP_ID BIGINT,
          VC_EMAIL VARCHAR(82) not null unique,
          primary key (PK_USER_ID)
          )

          create table TBL_JOIN_USER_ROLE (
          FK_USER_ID BIGINT not null,
          FK_ROLE_ID BIGINT not null
          )

          雇員是用戶

          Inheritance

          用戶

          雇員

          [User.java]
          /**
          * @hibernate.class table="TBL_USER"
          * discriminator-value="2"
          * @hibernate.discriminator column="USER_TYPE"
          *
          ...
          ...
          ...
          */
          public class User {

          [Employee.java]
          /**
          * @hibernate.subclass discriminator-value = "1"
          */
          public class Employee extends User{

          create table TBL_USER (
          PK_USER_ID BIGINT NOT NULL AUTO_INCREMENT,
          USER_TYPE VARCHAR(255) not null,
          FK_GROUP_ID BIGINT,
          VC_EMAIL VARCHAR(82) not null unique,
          primary key (PK_USER_ID)
          )

          要了解在 Hibernate 中設(shè)置對(duì)象關(guān)系的更多內(nèi)容,請(qǐng)參閱 參考資料





          回頁(yè)首


          Hibernate 中的查詢

          Hibernate 有三種類型的查詢:

          • Criteria, object composition
          • SQL
          • HQL

          在下面的例子中將只使用 HQL。本節(jié)還要使用 Spring,用它的 AOP-driven HibernateTemplate 簡(jiǎn)化 Hibernate 會(huì)話的處理。在本節(jié)將開發(fā)一個(gè) DAO(Data Access Object)。要了解更多關(guān)于 DAO 的內(nèi)容,請(qǐng)參閱 參考資料

          清單 2 展示了兩個(gè)方法:一個(gè)使用 HQL 查詢的組查詢,另一個(gè)是后面接一個(gè)操作的組查詢。注意在第二個(gè)方法中,Spring HibernateTemplate 是如何簡(jiǎn)化會(huì)話管理的。

          清單 2. 使用查詢

          												
          												
          												
          														
          														
          														
          																import net.sf.hibernate.HibernateException;
          import net.sf.hibernate.Session;
          import net.sf.hibernate.Query;
          import org.springframework.orm.hibernate.HibernateCallback;
          import org.springframework.orm.hibernate.support.HibernateDaoSupport;
          
          
          
          /**
           * @author Richard Hightower
           * ArcMind Inc. http://www.arc-mind.com
           */
          public class UserDAO extends HibernateDaoSupport{
          
            .
            .
            .
          
            /**
          	* Demonstrates looking up a group with a HQL query
          	* @param email
          	* @return
          	*/	
          	public Group findGroupByName(String name) {
          	   return (Group) getHibernateTemplate().find("from Group g where g.name=?",name).get(0);
          	}
          	
          	/**
          	 * Demonstrates looking up a group and forcing it to populate users (relationship was lazy)
          	 * @param email
          	 * @return
          	 */	
          	public Group findPopulatedGroupByName(final String name) {
          	   HibernateCallback callback = new HibernateCallback(){
          
          	    public Object doInHibernate(Session session) throws HibernateException, SQLException {
          			Group group =null;
          			String query = "from Group g where g.name=?";
          			Query queryObject = getHibernateTemplate().createQuery(session, query);
          			queryObject.setParameter(0, name);
          			group = (Group) queryObject.list().get(0);
          			group.getUsers().size();//force load
          			return group;
          			}
          			
          		};
          		
          		return (Group) getHibernateTemplate().execute(callback);
          	}
            .
            .
            .
          }			
          
          														
          												
          												
          												
          										

          您可能會(huì)注意到第二個(gè)方法比第一個(gè)方法復(fù)雜得多,因?yàn)樗鼜?qiáng)迫加載 users 集合。因?yàn)?Group->Users 之間的關(guān)系設(shè)置為 lazy initialize(即表 2 中 lazy="true" ),組對(duì)象需要一個(gè)活躍的會(huì)話以查詢用戶。在定義 GroupUser s 之間關(guān)系時(shí)設(shè)置這個(gè)屬性為 lazy="false" ,則不需要第二個(gè)方法。在這種情況下,可能使用第一種方法 ( findGroupByName ) 列出組,用第二種方法( findPopulatedGroupByName )查看組細(xì)節(jié)。





          回頁(yè)首


          Spring IOC 和 Hibernate

          使用 Spring 時(shí),在 J2EE 容器內(nèi)和容器外工作一樣容易。比如在最近的項(xiàng)目中,我在 Eclipse 中,使用 HSQL 和本地?cái)?shù)據(jù)庫(kù)對(duì)使用 Hibernate 事務(wù)管理器的 Hypersonic SQL 數(shù)據(jù)庫(kù)進(jìn)行持久性單元測(cè)試。然后,在部署到 J2EE 服務(wù)器時(shí),將持久層轉(zhuǎn)換為使用 J2EE 數(shù)據(jù)源(通過 JNDI)、JTA 事務(wù)和使用 FireBird (一個(gè)開放源代碼版本的 Interbase)。這是用 Spring 作為 IOC 容器完成的。

          從清單 3 中可以看出,Spring 允許加入依賴性。注意清單中應(yīng)用程序上下文文件是如何配置 dataSource 的。 dataSource 傳遞給 sessionFactorysessionFactory 傳遞給 UserDAO

          清單 3. Spring IOC 和 Hibernate

          												
          												
          												
          														
          														
          														
          																<beans>
          
          	<!-- Datasource that works in any application server
          		You could easily use J2EE data source instead if this were
          		running inside of a J2EE container.
          	 -->
          	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
          		<property name="driverClassName"><value>com.mysql.jdbc.Driver</value></property>
          		<property name="url"><value>jdbc:mysql://localhost:3306/mysql</value></property>
          		<property name="username"><value>root</value></property>
          		<property name="password"><value></value></property>
          	</bean>
          
          	<!-- Hibernate SessionFactory -->
          	<bean id="sessionFactory" class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
          		<property name="dataSource"><ref local="dataSource"/></property>
          		
          		<!-- Must references all OR mapping files. -->
          		<property name="mappingResources">
          			<list>
          		        <value>net/sf/hibernateExamples/User.hbm.xml</value>
          		        <value>net/sf/hibernateExamples/Group.hbm.xml</value>
          		        <value>net/sf/hibernateExamples/Role.hbm.xml</value>		        
          		        <value>net/sf/hibernateExamples/ContactInfo.hbm.xml</value>
          			</list>
          		</property>
          		
          		<!-- Set the type of database; changing this one property will port this to Oracle, 
          			 MS SQL etc. -->
          		<property name="hibernateProperties">
          			<props>
          			  <prop key="hibernate.dialect">net.sf.hibernate.dialect.MySQLDialect</prop>
          			</props>
          		</property>
          	</bean>
          	
          	<!-- Pass the session factory to our UserDAO -->
          	<bean id="userDAO" class="net.sf.hibernateExamples.UserDAO">
          		<property name="sessionFactory"><ref local="sessionFactory"/></property>
          	</bean>
          	
          </beans>			
          
          														
          												
          												
          												
          										

          設(shè)置了 UserDAO 后,下一步就是定義并使用更多的查詢以展示可以完成的操作。Hibernate 可以用預(yù)定義查詢將查詢存儲(chǔ)到源代碼之外,如清單 4 所示。

          清單 4. 預(yù)定義查詢

          												
          												
          												
          														
          														
          														
          																
          																
          																
          																		
          																				[User.java]
          																		
          																
          																/**
           * @author Richard Hightower
           * ArcMind Inc. http://www.arc-mind.com
           * @hibernate.class table="TBL_USER" discriminator-value="2" 
           * @hibernate.discriminator column="USER_TYPE"
           * 
           * @hibernate.query name="AllUsers" query="from User user order by user.email asc"
           * 
           * @hibernate.query name="OverheadStaff" 
           * query="from Employee employee join employee.group g where g.name not in ('ENGINEERING','IT')"
           * 
           * @hibernate.query name="CriticalStaff" 
           * query="from Employee employee join employee.group g where g.name in ('ENGINEERING','IT')"
           * 
           * @hibernate.query name="GetUsersInAGroup" 
           * query="select user from Group g join g.users user"
           * 
           * @hibernate.query name="GetUsersNotInAGroup" 
           * query="select user from User user where user.group is null"
           * 
           * @hibernate.query name="UsersBySalaryGreaterThan" 
           * query="from User user inner join user.contactInfo info where info.salary > ?1"
           * 
           * @hibernate.query name="UsersBySalaryBetween" 
           * query="from User user join user.contactInfo info where info.salary between ?1 AND ?2"
           * 
           * @hibernate.query name="UsersByLastNameLike" 
           * query="from User user join user.contactInfo info where info.lastName like ?1"
           * 
           * @hibernate.query name="GetEmailsOfUsers" 
           * query="select user.email from Group g join g.users as user where g.name = ?1"
           * 
           */
          public class User {
          			.
          			.
          			.			
          
                
          														
          														
          														
          												
          												
          												
          										

          上述代碼定義了幾個(gè)預(yù)定義查詢。 預(yù)定義查詢 是存儲(chǔ)在 *.hbm.xml文件中的查詢。在清單 5 中,可以看到如何執(zhí)行預(yù)定義查詢。

          清單 5. 使用預(yù)定義查詢

          												
          												
          												
          														
          														
          														
          																
          																
          																
          																		
          																				[UserDAO.java]
          																		
          																
          																	/**
          	 * Demonstrates a query that returns a String.
          	 */                
          	public String[] getUserEmailsInGroup(String groupName){
          		List emailList =
          		getHibernateTemplate().findByNamedQuery("GetEmailsOfUsers"); 
          		return (String [])
          			emailList.toArray(new String[emailList.size()]);
          	}
          
          
          	/**
          	 * Demonstrates a query that returns a list of Users
          	 *
          	 * @return A list of emails of all of the users in the authentication system.
          	 * 
          	 */                
          	public List getUsers(){
          		return getHibernateTemplate().findByNamedQuery("AllUsers");
          	}
          
                /**
          	* Demonstrates passing a single argument to a query. 
          	*
          	* @return A list of UserValue objects.
          	* 
          	*/                    
          	public List getUsersBySalary(float salary){
          	   return getHibernateTemplate()
          	       .findByNamedQuery("UsersBySalaryGreaterThan",
          	            new Float(salary));
          	}
          
          
          	/**
          	 * Demonstrates passing multiple arguments to a query
          	 * 
          	 * @return A list of UserValue objects.
          	 * 
          	 */                    
          	public List getUsersBySalaryRange(float start, float stop){
                          return getHibernateTemplate()
          		 .findByNamedQuery("UsersBySalaryBetween",
          			new Object[] {new Float(start), new Float(stop)});
          	}
          	
          
                
          														
          														
          														
          												
          												
          												
          										

          查詢進(jìn)行時(shí),可以在持久層中加上最后一層:使用 Spring 的事務(wù)管理。





          回頁(yè)首


          用 Spring 管理事務(wù)

          Spring 可以聲明式地管理事務(wù)。例如, UserDAO.addUser 方法當(dāng)前不是在單個(gè)事務(wù)中執(zhí)行的。因此,組中的每一個(gè)用戶都插入到自己的事務(wù)中,如清單 6 所示。

          清單 6. 添加一組用戶

          												
          												
          												
          														
          														
          														
          																
          																
          																
          																		
          																				[UserDAO.java]
          																		
          																
          																/**
           * @param group
           */
          public void addGroup(Group group) {
          	getHibernateTemplate().save(group);
          	
          }
          
          
                  [UserDAOTest.java]
          
          public void testAddGroupOfUsers(){
          	Group group = new Group();
          	
          	for (int index=0; index < 10; index++){
          		User user = new User();
          		user.setEmail("rick"+index+"@foobar.com" );
          		user.setPassword("foobar");
          		group.addUser(user);	
          	}
          	
          	group.setName("testGroup");
          	
          	userDAO.addGroup(group);
          	assertNotNull(group.getId());
          	
          	Group group2 = userDAO.findPopulatedGroupByName("testGroup");
          	
          	assertEquals("testGroup",group2.getName());
          	assertEquals(10, group2.getUsers().size());
          	String email = ((User)group2.getUsers().get(0)).getEmail();
          	assertEquals("rick0@foobar.com", email);
          
          	
          }
          
          
                
          														
          														
          														
          												
          												
          												
          										

          不建議使用上述解決方案,因?yàn)槊恳粋€(gè) User 都要在自己的事務(wù)中插入到數(shù)據(jù)庫(kù)中。如果出現(xiàn)問題,那么只能添加部分用戶。如果希望保留 ACID 屬性(即保證所有都發(fā)生或者所有都不發(fā)生),可以通過程序進(jìn)行事務(wù)管理,但是它很快就會(huì)變得一團(tuán)糟了。相反,應(yīng)使用 Spring 的 AOP 來支持聲明式的事務(wù),如清單 7 所示。

          清單 7. 聲明式管理事務(wù)

          												
          												
          												
          														
          														
          														
          																
          																
          																
          																		
          																				[applicationContext.xml]
          																		
          																
          																<!-- Pass the session factory to our UserDAO -->
             <bean id="userDAOTarget" class="net.sf.hibernateExamples.UserDAOImpl">
          	<property name="sessionFactory"><ref local="sessionFactory"/></property>
             </bean>
          	
             <bean id="transactionManager" 
             	class="org.springframework.orm.hibernate.HibernateTransactionManager">
                <property name="sessionFactory"><ref bean="sessionFactory"/></property>
             </bean>
          
             <bean id="userDAO"
               class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
          	<property name="transactionManager"><ref local="transactionManager"/></property>
          	<property name="target"><ref local="userDAOTarget"/></property>
          	<property name="transactionAttributes">
          		<props>
          			<prop key="addGroup">PROPAGATION_REQUIRED</prop>
          		</props>
          	</property>
              </bean>
          
                
          														
          														
          														
          												
          												
          												
          										

          注意在準(zhǔn)備清單 7 的代碼時(shí),我重新改寫了 UserDAO 并提取了其接口。這個(gè)接口現(xiàn)在是 UserDAO ,它的實(shí)現(xiàn)類是 UserDAOImpl 。這樣清單 7 中的事務(wù)代碼就使用了帶有事務(wù)屬性 (PROPAGATION_REQUIRED) UserDAO.addGroup() 方法。現(xiàn)在只要底層數(shù)據(jù)庫(kù)支持,就可以在一個(gè)事務(wù)中添加所有用戶。





          回頁(yè)首


          結(jié)束語

          在本文中,介紹了如何使用 Hibernate 和 Spring 實(shí)現(xiàn)一個(gè)事務(wù)持久層。Hibernate 是一種先進(jìn)的 OR 映射工具,而 Spring 是一個(gè) AOP 框架和 IOC 容器。這兩種技術(shù)的綜合使用,使得開發(fā)人員可以編寫媲美數(shù)據(jù)庫(kù)廠商的代碼,它可以在 J2EE 容器中運(yùn)行,也可以單獨(dú)運(yùn)行。使用了 DbUnit (JUnit 的擴(kuò)展)構(gòu)建和測(cè)試本文中例子的所有代碼,雖然這不是討論的重點(diǎn)。

          要了解有關(guān) AOP、IOC 容器和測(cè)試驅(qū)動(dòng)開發(fā)的更多內(nèi)容,請(qǐng)參閱 參考資料

          如果關(guān)心開發(fā)人員的最新熱點(diǎn),那么您可能聽說過 IOC (控制倒置,Inversion of Control)容器和 AOP (面向方面編程)。不過,像許多開發(fā)人員一樣,您可能不清楚在自己的開發(fā)工作中如何使用這些技術(shù)。在本文中,通過具體介紹使用 Hibernate 和 Spring 在企業(yè)應(yīng)用程序中構(gòu)建一個(gè)事務(wù)持久層,您會(huì)認(rèn)識(shí)到這些技術(shù)。

          Hibernate 是 Java 平臺(tái)上的一種流行的、容易使用的開放源代碼對(duì)象關(guān)系(OR)映射框架。Spring 是一個(gè) AOP 框架和 IOC 容器。這兩種技術(shù)一起提供了本文中介紹的開發(fā)工作的基礎(chǔ)。將使用 Hibernate 把一些持久性對(duì)象映射到關(guān)系數(shù)據(jù)庫(kù)中,用 Spring 使 Hibernate 更容易使用并提供聲明性事務(wù)支持。由于為示例類編寫測(cè)試代碼時(shí)使用了 DbUnit,我還附帶介紹了一點(diǎn) TDD (測(cè)試驅(qū)動(dòng)的開發(fā))的內(nèi)容。

          注意,本文假定讀者熟悉 Java 平臺(tái)上的企業(yè)開發(fā),包括 JDBC、OR 映射內(nèi)容、J2EE 設(shè)計(jì)模式如 DAO,以及聲明性事務(wù)支持,如 Enterprise JavaBean (EJB)技術(shù)所提供的事務(wù)支持。理解這里的討論不需要成為這些技術(shù)的專家,也不需要熟悉 AOP、IOC 或者 TDD,因?yàn)樵诒疚闹袑?duì)這三者都做了介紹。

          我將首先介紹兩種開發(fā)技術(shù),然后分析例子。

          Hibernate 簡(jiǎn)介

          Hibernate 是 Java 平臺(tái)上的一種全功能的、開放源代碼 OR 映射框架。Hibernate 在許多方面類似于 EJB CMP CMR (容器管理的持久性/容器管理的關(guān)系)和 JDO(Java Data Objects)。與 JDO 不同,Hibernate 完全著眼于關(guān)系數(shù)據(jù)庫(kù)的 OR 映射,并且包括比大多數(shù)商業(yè)產(chǎn)品更多的功能。大多數(shù) EJB CMP CMR 解決方案使用代碼生成實(shí)現(xiàn)持久性代碼,而 JDO 使用字節(jié)碼修飾。與之相反,Hibernate 使用反射和運(yùn)行時(shí)字節(jié)碼生成,使它對(duì)于最終用戶幾乎是透明的(以前 Hibernate 的實(shí)現(xiàn)只使用反射,它有助于調(diào)試,當(dāng)前版本保留了這種選項(xiàng))。

          移植基于 Hibernate 的應(yīng)用程序

          如果應(yīng)用程序必須在多個(gè) RDBMS 系統(tǒng)上運(yùn)行 ,那么基于 Hibernate 的應(yīng)用程序可以毫不費(fèi)力地移植到 IBM DB2、MySQL、PostgreSQL、Sybase、Oracle、HypersonicSQL 和許多其他數(shù)據(jù)庫(kù)。我最近甚至將一個(gè)應(yīng)用程序從 MySQL 移植到 Hibernate 沒有很好支持的 Firebird,而這種移植是很容易的。有關(guān)在 Postgres 和 MySQL 之間轉(zhuǎn)換的案例分析,請(qǐng)參閱 參考資料

          Hibernate 可以模擬繼承(有幾種方式)、關(guān)聯(lián)(一對(duì)一或者一對(duì)多、containment 和 aggregation)和 composition。我將在本文中討論每種關(guān)系類型的幾個(gè)例子。

          Hibernate 提供了一種稱為 Hibernate Query Language (HQL) 的 查詢語言,它類似于 JDO 的 JDOQL 和 EJB 的 EJB QL,盡管它更接近于前者。但是 Hibernate 沒有就此止步:它還可以進(jìn)行直接的 SQL 查詢和/或使用 object criteria很容易地在運(yùn)行時(shí)構(gòu)成查詢條件。在本文的例子中我將只使用 HQL。

          與 EJB CMP CMR 不同,Hibernate 像 JDO 一樣可以在 J2EE 容器內(nèi)部或者外部工作,這可以讓那些進(jìn)行 TDD 和敏捷開發(fā)的人受益。





          回頁(yè)首


          Spring 簡(jiǎn)介

          AOP 專家 Nicholas Lesiecki 第一次向我解釋 AOP 時(shí),他說的我一個(gè)詞也沒理解,我覺得就像第一次考慮使用 IOC 容器的可能性時(shí)一樣。每一種技術(shù)的概念基礎(chǔ)本身就需要很好地消化,每一種技術(shù)所使用的各種各樣的縮寫讓事情更糟了——特別是其中許多術(shù)語與我們已經(jīng)使用的根本不一樣了。

          像許多技術(shù)一樣,理解這兩種技術(shù)的實(shí)際使用比學(xué)習(xí)理論更容易。經(jīng)過自己對(duì) AOP 和 IOC 容器實(shí)現(xiàn)(即 XWork、PicoContainer 和 Spring)的分析,我發(fā)現(xiàn)這些技術(shù)可以幫助我獲得功能,而不會(huì)在多框架中添加基于代碼的依賴性。它們都將成為我后面開發(fā)項(xiàng)目的一部分。

          簡(jiǎn)單地說,AOP 讓開發(fā)人員可以創(chuàng)建非行為性的關(guān)注點(diǎn),稱為橫切關(guān)注點(diǎn),并將它們插入到應(yīng)用程序代碼中。使用 AOP 后,公共服務(wù)(比如日志、持久性、事務(wù)等)就可以分解成方面并應(yīng)用到域?qū)ο笊希瑫r(shí)不會(huì)增加域?qū)ο蟮膶?duì)象模型的復(fù)雜性。

          關(guān)于 DbUnit

          用新的框架開發(fā)而不進(jìn)行單元測(cè)試,就像不帶保護(hù)網(wǎng)走鋼絲:當(dāng)然可以這樣做,但是很可能會(huì)受傷。我選擇在有保護(hù)網(wǎng)的條件下開發(fā),對(duì)我來說這個(gè)保護(hù)網(wǎng)就是 TDD。在有 DbUnit 之前,對(duì)依賴于數(shù)據(jù)庫(kù)的代碼進(jìn)行測(cè)試是不太容易的。DbUnit 是 JUnit 的一個(gè)擴(kuò)展,它提供了依賴于數(shù)據(jù)庫(kù)的單元測(cè)試的框架。我用 DbUnit 編寫本文中示例類的測(cè)試代碼。雖然在本文中沒有出現(xiàn),不過在本文源代碼中提供了 DbUnit 代碼(請(qǐng)參閱 參考資料 )。有關(guān) DbUnit 的介紹,請(qǐng)參閱 Philippe Girolami 的“ Control your test-environment with DbUnit and Anthill ” ( developerWorks,2004 年 4 月)。

          IOC 允許創(chuàng)建一個(gè)可以構(gòu)造對(duì)象的應(yīng)用環(huán)境,然后向這些對(duì)象傳遞它們的協(xié)作對(duì)象。正如單詞 倒置 所表明的,IOC 就像反過來的 JNDI。沒有使用一堆抽象工廠、服務(wù)定位器、單元素(singleton)和直接構(gòu)造(straight construction),每一個(gè)對(duì)象都是用其協(xié)作對(duì)象構(gòu)造的。因此是由容器管理協(xié)作對(duì)象(collaborator)。

          Spring 既是一個(gè) AOP 框架、也是一個(gè) IOC 容器。我記得 Grady Booch 說過,對(duì)象最好的地方是可以替換它們,而 Spring 最好的地方是它有助于您替換它們。有了 Spring,只要用 JavaBean 屬性和配置文件加入依賴性(協(xié)作對(duì)象)。然后可以很容易地在需要時(shí)替換具有類似接口的協(xié)作對(duì)象。

          Spring 為 IOC 容器和 AOP 提供了很好的入口(on-ramp)。因此,不需要熟悉 AOP 就可以理解本文中的例子。所需要知道的就是將要用 AOP 為示例應(yīng)用程序聲明式地添加事務(wù)支持,與使用 EJB 技術(shù)時(shí)的方式基本相同。要了解 IOC 容器、AOP 和 Spring 的更多內(nèi)容,請(qǐng)參閱 參考資料





          回頁(yè)首


          具體到業(yè)務(wù)

          在本文的其余部分,所有的討論都將基于一個(gè)實(shí)際的例子。起點(diǎn)是一個(gè)企業(yè)應(yīng)用程序,要為它實(shí)現(xiàn)一個(gè)事務(wù)持久層。持久層是一個(gè)對(duì)象關(guān)系數(shù)據(jù)庫(kù),它包括像 UserUser GroupRolesContactInfo 這些熟悉的抽象。

          在深入到數(shù)據(jù)庫(kù)的要素——查詢和事務(wù)管理——之前,需要建立它的基礎(chǔ):對(duì)象關(guān)系映射。我將用 Hibernate 設(shè)置它,并只使用一點(diǎn) Spring。





          回頁(yè)首


          用 Hibernate 進(jìn)行 OR 映射

          Hibernate 使用 XML ( *.hbm.xml) 文件將 Java 類映射到表,將 JavaBean 屬性映射到數(shù)據(jù)庫(kù)表。幸運(yùn)的是,有一組 XDoclet 標(biāo)簽支持 Hibernate 開發(fā),這使得創(chuàng)建所需要的 *.hbm.xml 文件更容易了。清單 1 中的代碼將一個(gè) Java 類映射到數(shù)據(jù)庫(kù)表。關(guān)于 XDoclet 標(biāo)簽的更多內(nèi)容,請(qǐng)參閱 參考資料

          清單 1. 將 Java 類映射到 DB 表

          												
          												
          												
          														
          														
          														
          																
          																
          																
          																		
          																				[User.java]
          																		
          																
          																/**
           * 
                  @hibernate.class table="TBL_USER"
           * ..
           * ..
           * ...
           */
          public class User {
          
          	private Long id = new Long(-1);
          	private String email;
          	private String password;
          	
          	.
          	.
          	.
          
          	/**
          	 * @return
          	 * 
                  
          														
          														
          														
          														
          																
          																
          																
          																		
          																		
          																		
          																				@hibernate.id column="PK_USER_ID" 
          	 * 		unsaved-value="-1" 
          	 *              generator-class="native" 
          																		
          																
          																 
          	 */
          	public Long getId() {
          		return id;
          	}
          
          	...
          
          
          	/**
          	 * 
                  
          														
          														
          														
          														
          																
          																
          																
          																		
          																		
          																		
          																				@hibernate.property column="VC_EMAIL" 
          	 * 			type="string" 
          	 * 			update="false"
          	 * 			insert="true"
          	 * 			unique="true"
          	 * 			not-null="true"
          	 * 			length="82" 
          																		
          																
          																	 * @return
          	 */
          	public String getEmail() {
          		return email;
          	}
          
          	/**
          	 * 
                  
          														
          														
          														
          														
          																
          																
          																
          																		
          																		
          																		
          																				@hibernate.property column="VC_PASSWORD" 
          	 * 			type="string" 
          	 * 			update="false"
          	 * 			insert="true"
          	 * 			unique="true"
          	 * 			not-null="true"
          	 * 			length="20" 
          																		
          																
          																	 * @return
          	 */
          	public String getPassword() {
          		return password;
          	}
          
          	...
          	...
          	...
          }
          			
          
                
          														
          														
          														
          												
          												
          												
          										

          可以看到, @hibernate.class table="TBL_USER" 標(biāo)簽將 User 映射到 TBL_USER 表。 @hibernate.property column="VC_PASSWORD" 將 JavaBean 屬性 password 映射到 VC_PASSWORD 列。 @hibernate.id column="PK_USER_ID" 標(biāo)簽聲明 id 屬性是主鍵,它將使用本機(jī)( generator-class="native" )數(shù)據(jù)庫(kù)機(jī)制生成鍵(例如,Oracle sequences 和 SQL Server Identity 鍵)。Hibernate 可以指定 generator-class="native" 以外的、其他可以想象的得到主鍵獲得策略,不過我更愿意使用 native。 typelength屬性用于從 Hibernate *.hbm.xml OR 映射文件生成表。這些 final 屬性是可選的,因?yàn)槭褂玫目赡懿皇?green-field 數(shù)據(jù)庫(kù)。在這個(gè)例子中,已經(jīng)有數(shù)據(jù)庫(kù)了,所以不需要額外的屬性。( green-field 應(yīng)用程序是一個(gè)新的應(yīng)用程序, green-field 數(shù)據(jù)是新應(yīng)用程序的一個(gè)新數(shù)據(jù)庫(kù)。不會(huì)經(jīng)常開發(fā)一個(gè)全新的應(yīng)用程序,不過偶爾有一兩次也不錯(cuò))。

          看過了表如何映射到類以及列如何映射到 JavaBean 屬性,該使用 Hibernate 在 OR 數(shù)據(jù)庫(kù)中設(shè)置一些關(guān)系了。

          設(shè)置對(duì)象關(guān)系

          在本節(jié)中,我將只觸及 Hibernate 提供的設(shè)置對(duì)象間關(guān)系的選項(xiàng)的一小部分。首先設(shè)置像 UserUser GroupRolesContactInfo 這些類之間的關(guān)系。其中一些關(guān)系如圖 1 所示,這是數(shù)據(jù)庫(kù)的驗(yàn)證對(duì)象模型。


          圖 1. 關(guān)系的圖示
          關(guān)系的圖示

          如您所見,在上述抽象中存在各種各樣的關(guān)系。 UserContactInfo 有一對(duì)一關(guān)系。 ContactInfo 的生命周期與 User 相同(用數(shù)據(jù)庫(kù)的術(shù)語,UML 中的組成 aka 級(jí)聯(lián)刪除)。如果刪除 User ,則相應(yīng)的 ContactInfo 也會(huì)刪除。在 User s 與 Role s 之間存在多對(duì)多關(guān)系(即與獨(dú)立生命周期相關(guān)聯(lián))。在 Group s 與 User s 之間存在一對(duì)多關(guān)系,因?yàn)榻M有許多用戶。用戶可以存在于組外,即是 aggregation 而不是 composition (用數(shù)據(jù)庫(kù)的說法,在 Group s 和 Users 之間沒有級(jí)聯(lián)刪除關(guān)系)。此外, UserEmployee 有子類關(guān)系,就是說, Employee 的類型為 User 。表 1 顯示了如何用 XDoclet 標(biāo)簽創(chuàng)建一些不同類型的對(duì)象關(guān)系。

          表 1. 用 XDoclet 創(chuàng)建對(duì)象關(guān)系

          關(guān)系 Java/XDoclet SQL DDL(由 Hibernate Schema Export 生成的 MySQL)
          組包含用戶

          一對(duì)多

          Aggregation

          雙向
          (Group<-->Users)

          [Group.java]
          /**
          *
          * @return
          *
          * @hibernate.bag name="users"
          * cascade="save-update"
          * lazy="true"
          * inverse="true"
          *
          * @hibernate.collection-key
          * column="FK_GROUP_ID"
          *
          * @hibernate.collection-one-to-many
          * class="net.sf.hibernateExamples.User"
          */
          public List getUsers() {
          return users;
          }

          [User.java]
          /**
          * @hibernate.many-to-one
          * column="FK_GROUP_ID"
          * class="net.sf.hibernateExamples.Group"
          */
          public Group getGroup() {
          return group;
          }


          create table TBL_USER (
          PK_USER_ID BIGINT NOT NULL AUTO_INCREMENT,
          USER_TYPE VARCHAR(255) not null,
          FK_GROUP_ID BIGINT,
          VC_EMAIL VARCHAR(82) not null unique,
          primary key (PK_USER_ID)
          )


          create table TBL_GROUP (
          PK_GROUP_ID BIGINT NOT NULL AUTO_INCREMENT,
          VC_DESCRIPTION VARCHAR(255),
          VC_NAME VARCHAR(40) unique,
          primary key (PK_GROUP_ID)
          )

          alter table TBL_USER add index (FK_GROUP_ID),
          add constraint FK_111 foreign key (FK_GROUP_ID)
          references TBL_GROUP (PK_GROUP_ID)

          用戶有聯(lián)系信息

          一對(duì)一

          Composition
          單向
          (User-->ContactInfo)

          [User.java]
          /**
          * @return
          *
          * @hibernate.one-to-one cascade="all"
          *
          */
          public ContactInfo getContactInfo() {
          return contactInfo;
          }

          [ContactInfo.java]
          (Nothing to see here. Unidirectional!)
          create table TBL_USER (
          PK_USER_ID BIGINT NOT NULL AUTO_INCREMENT,
          USER_TYPE VARCHAR(255) not null,
          FK_GROUP_ID BIGINT,
          VC_EMAIL VARCHAR(82) not null unique,
          primary key (PK_USER_ID)
          )

          create table TBL_CONTACT_INFO (
          PK_CONTACT_INFO_ID BIGINT not null,
          ...
          ...
          ...
          primary key (PK_CONTACT_INFO_ID)
          )

          用戶與角色關(guān)聯(lián)

          多對(duì)多

          Association

          單向
          (Users-->Roles)

          [User.java]
          /**
          * @return
          * @hibernate.bag
          * table="TBL_JOIN_USER_ROLE"
          * cascade="all"
          * inverse="true"
          *
          * @hibernate.collection-key
          * column="FK_USER_ID"
          *
          * @hibernate.collection-many-to-many
          * class="net.sf.hibernateExamples.Role"
          * column="FK_ROLE_ID"
          *
          */
          public List getRoles() {
          return roles;
          }

          [Role.java]
          Nothing to see here. Unidirectional!
          create table TBL_ROLE (
          PK_ROLE_ID BIGINT NOT NULL AUTO_INCREMENT,
          VC_DESCRIPTION VARCHAR(200),
          VC_NAME VARCHAR(20),
          primary key (PK_ROLE_ID)
          )

          create table TBL_USER (
          PK_USER_ID BIGINT NOT NULL AUTO_INCREMENT,
          USER_TYPE VARCHAR(255) not null,
          FK_GROUP_ID BIGINT,
          VC_EMAIL VARCHAR(82) not null unique,
          primary key (PK_USER_ID)
          )

          create table TBL_JOIN_USER_ROLE (
          FK_USER_ID BIGINT not null,
          FK_ROLE_ID BIGINT not null
          )

          雇員是用戶

          Inheritance

          用戶

          雇員

          [User.java]
          /**
          * @hibernate.class table="TBL_USER"
          * discriminator-value="2"
          * @hibernate.discriminator column="USER_TYPE"
          *
          ...
          ...
          ...
          */
          public class User {

          [Employee.java]
          /**
          * @hibernate.subclass discriminator-value = "1"
          */
          public class Employee extends User{

          create table TBL_USER (
          PK_USER_ID BIGINT NOT NULL AUTO_INCREMENT,
          USER_TYPE VARCHAR(255) not null,
          FK_GROUP_ID BIGINT,
          VC_EMAIL VARCHAR(82) not null unique,
          primary key (PK_USER_ID)
          )

          要了解在 Hibernate 中設(shè)置對(duì)象關(guān)系的更多內(nèi)容,請(qǐng)參閱 參考資料





          回頁(yè)首


          Hibernate 中的查詢

          Hibernate 有三種類型的查詢:

          • Criteria, object composition
          • SQL
          • HQL

          在下面的例子中將只使用 HQL。本節(jié)還要使用 Spring,用它的 AOP-driven HibernateTemplate 簡(jiǎn)化 Hibernate 會(huì)話的處理。在本節(jié)將開發(fā)一個(gè) DAO(Data Access Object)。要了解更多關(guān)于 DAO 的內(nèi)容,請(qǐng)參閱 參考資料

          清單 2 展示了兩個(gè)方法:一個(gè)使用 HQL 查詢的組查詢,另一個(gè)是后面接一個(gè)操作的組查詢。注意在第二個(gè)方法中,Spring HibernateTemplate 是如何簡(jiǎn)化會(huì)話管理的。

          清單 2. 使用查詢

          												
          												
          												
          														
          														
          														
          																import net.sf.hibernate.HibernateException;
          import net.sf.hibernate.Session;
          import net.sf.hibernate.Query;
          import org.springframework.orm.hibernate.HibernateCallback;
          import org.springframework.orm.hibernate.support.HibernateDaoSupport;
          
          
          
          /**
           * @author Richard Hightower
           * ArcMind Inc. http://www.arc-mind.com
           */
          public class UserDAO extends HibernateDaoSupport{
          
            .
            .
            .
          
            /**
          	* Demonstrates looking up a group with a HQL query
          	* @param email
          	* @return
          	*/	
          	public Group findGroupByName(String name) {
          	   return (Group) getHibernateTemplate().find("from Group g where g.name=?",name).get(0);
          	}
          	
          	/**
          	 * Demonstrates looking up a group and forcing it to populate users (relationship was lazy)
          	 * @param email
          	 * @return
          	 */	
          	public Group findPopulatedGroupByName(final String name) {
          	   HibernateCallback callback = new HibernateCallback(){
          
          	    public Object doInHibernate(Session session) throws HibernateException, SQLException {
          			Group group =null;
          			String query = "from Group g where g.name=?";
          			Query queryObject = getHibernateTemplate().createQuery(session, query);
          			queryObject.setParameter(0, name);
          			group = (Group) queryObject.list().get(0);
          			group.getUsers().size();//force load
          			return group;
          			}
          			
          		};
          		
          		return (Group) getHibernateTemplate().execute(callback);
          	}
            .
            .
            .
          }			
          
          														
          												
          												
          												
          										

          您可能會(huì)注意到第二個(gè)方法比第一個(gè)方法復(fù)雜得多,因?yàn)樗鼜?qiáng)迫加載 users 集合。因?yàn)?Group->Users 之間的關(guān)系設(shè)置為 lazy initialize(即表 2 中 lazy="true" ),組對(duì)象需要一個(gè)活躍的會(huì)話以查詢用戶。在定義 GroupUser s 之間關(guān)系時(shí)設(shè)置這個(gè)屬性為 lazy="false" ,則不需要第二個(gè)方法。在這種情況下,可能使用第一種方法 ( findGroupByName ) 列出組,用第二種方法( findPopulatedGroupByName )查看組細(xì)節(jié)。





          回頁(yè)首


          Spring IOC 和 Hibernate

          使用 Spring 時(shí),在 J2EE 容器內(nèi)和容器外工作一樣容易。比如在最近的項(xiàng)目中,我在 Eclipse 中,使用 HSQL 和本地?cái)?shù)據(jù)庫(kù)對(duì)使用 Hibernate 事務(wù)管理器的 Hypersonic SQL 數(shù)據(jù)庫(kù)進(jìn)行持久性單元測(cè)試。然后,在部署到 J2EE 服務(wù)器時(shí),將持久層轉(zhuǎn)換為使用 J2EE 數(shù)據(jù)源(通過 JNDI)、JTA 事務(wù)和使用 FireBird (一個(gè)開放源代碼版本的 Interbase)。這是用 Spring 作為 IOC 容器完成的。

          從清單 3 中可以看出,Spring 允許加入依賴性。注意清單中應(yīng)用程序上下文文件是如何配置 dataSource 的。 dataSource 傳遞給 sessionFactorysessionFactory 傳遞給 UserDAO

          清單 3. Spring IOC 和 Hibernate

          												
          												
          												
          														
          														
          														
          																<beans>
          
          	<!-- Datasource that works in any application server
          		You could easily use J2EE data source instead if this were
          		running inside of a J2EE container.
          	 -->
          	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
          		<property name="driverClassName"><value>com.mysql.jdbc.Driver</value></property>
          		<property name="url"><value>jdbc:mysql://localhost:3306/mysql</value></property>
          		<property name="username"><value>root</value></property>
          		<property name="password"><value></value></property>
          	</bean>
          
          	<!-- Hibernate SessionFactory -->
          	<bean id="sessionFactory" class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
          		<property name="dataSource"><ref local="dataSource"/></property>
          		
          		<!-- Must references all OR mapping files. -->
          		<property name="mappingResources">
          			<list>
          		        <value>net/sf/hibernateExamples/User.hbm.xml</value>
          		        <value>net/sf/hibernateExamples/Group.hbm.xml</value>
          		        <value>net/sf/hibernateExamples/Role.hbm.xml</value>		        
          		        <value>net/sf/hibernateExamples/ContactInfo.hbm.xml</value>
          			</list>
          		</property>
          		
          		<!-- Set the type of database; changing this one property will port this to Oracle, 
          			 MS SQL etc. -->
          		<property name="hibernateProperties">
          			<props>
          			  <prop key="hibernate.dialect">net.sf.hibernate.dialect.MySQLDialect</prop>
          			</props>
          		</property>
          	</bean>
          	
          	<!-- Pass the session factory to our UserDAO -->
          	<bean id="userDAO" class="net.sf.hibernateExamples.UserDAO">
          		<property name="sessionFactory"><ref local="sessionFactory"/></property>
          	</bean>
          	
          </beans>			
          
          														
          												
          												
          												
          										

          設(shè)置了 UserDAO 后,下一步就是定義并使用更多的查詢以展示可以完成的操作。Hibernate 可以用預(yù)定義查詢將查詢存儲(chǔ)到源代碼之外,如清單 4 所示。

          清單 4. 預(yù)定義查詢

          												
          												
          												
          														
          														
          														
          																
          																
          																
          																		
          																				[User.java]
          																		
          																
          																/**
           * @author Richard Hightower
           * ArcMind Inc. http://www.arc-mind.com
           * @hibernate.class table="TBL_USER" discriminator-value="2" 
           * @hibernate.discriminator column="USER_TYPE"
           * 
           * @hibernate.query name="AllUsers" query="from User user order by user.email asc"
           * 
           * @hibernate.query name="OverheadStaff" 
           * query="from Employee employee join employee.group g where g.name not in ('ENGINEERING','IT')"
           * 
           * @hibernate.query name="CriticalStaff" 
           * query="from Employee employee join employee.group g where g.name in ('ENGINEERING','IT')"
           * 
           * @hibernate.query name="GetUsersInAGroup" 
           * query="select user from Group g join g.users user"
           * 
           * @hibernate.query name="GetUsersNotInAGroup" 
           * query="select user from User user where user.group is null"
           * 
           * @hibernate.query name="UsersBySalaryGreaterThan" 
           * query="from User user inner join user.contactInfo info where info.salary > ?1"
           * 
           * @hibernate.query name="UsersBySalaryBetween" 
           * query="from User user join user.contactInfo info where info.salary between ?1 AND ?2"
           * 
           * @hibernate.query name="UsersByLastNameLike" 
           * query="from User user join user.contactInfo info where info.lastName like ?1"
           * 
           * @hibernate.query name="GetEmailsOfUsers" 
           * query="select user.email from Group g join g.users as user where g.name = ?1"
           * 
           */
          public class User {
          			.
          			.
          			.			
          
                
          														
          														
          														
          												
          												
          												
          										

          上述代碼定義了幾個(gè)預(yù)定義查詢。 預(yù)定義查詢 是存儲(chǔ)在 *.hbm.xml文件中的查詢。在清單 5 中,可以看到如何執(zhí)行預(yù)定義查詢。

          清單 5. 使用預(yù)定義查詢

          												
          												
          												
          														
          														
          														
          																
          																
          																
          																		
          																				[UserDAO.java]
          																		
          																
          																	/**
          	 * Demonstrates a query that returns a String.
          	 */                
          	public String[] getUserEmailsInGroup(String groupName){
          		List emailList =
          		getHibernateTemplate().findByNamedQuery("GetEmailsOfUsers"); 
          		return (String [])
          			emailList.toArray(new String[emailList.size()]);
          	}
          
          
          	/**
          	 * Demonstrates a query that returns a list of Users
          	 *
          	 * @return A list of emails of all of the users in the authentication system.
          	 * 
          	 */                
          	public List getUsers(){
          		return getHibernateTemplate().findByNamedQuery("AllUsers");
          	}
          
                /**
          	* Demonstrates passing a single argument to a query. 
          	*
          	* @return A list of UserValue objects.
          	* 
          	*/                    
          	public List getUsersBySalary(float salary){
          	   return getHibernateTemplate()
          	       .findByNamedQuery("UsersBySalaryGreaterThan",
          	            new Float(salary));
          	}
          
          
          	/**
          	 * Demonstrates passing multiple arguments to a query
          	 * 
          	 * @return A list of UserValue objects.
          	 * 
          	 */                    
          	public List getUsersBySalaryRange(float start, float stop){
                          return getHibernateTemplate()
          		 .findByNamedQuery("UsersBySalaryBetween",
          			new Object[] {new Float(start), new Float(stop)});
          	}
          	
          
                
          														
          														
          														
          												
          												
          												
          										

          查詢進(jìn)行時(shí),可以在持久層中加上最后一層:使用 Spring 的事務(wù)管理。





          回頁(yè)首


          用 Spring 管理事務(wù)

          Spring 可以聲明式地管理事務(wù)。例如, UserDAO.addUser 方法當(dāng)前不是在單個(gè)事務(wù)中執(zhí)行的。因此,組中的每一個(gè)用戶都插入到自己的事務(wù)中,如清單 6 所示。

          清單 6. 添加一組用戶

          												
          												
          												
          														
          														
          														
          																
          																
          																
          																		
          																				[UserDAO.java]
          																		
          																
          																/**
           * @param group
           */
          public void addGroup(Group group) {
          	getHibernateTemplate().save(group);
          	
          }
          
          
                  [UserDAOTest.java]
          
          public void testAddGroupOfUsers(){
          	Group group = new Group();
          	
          	for (int index=0; index < 10; index++){
          		User user = new User();
          		user.setEmail("rick"+index+"@foobar.com" );
          		user.setPassword("foobar");
          		group.addUser(user);	
          	}
          	
          	group.setName("testGroup");
          	
          	userDAO.addGroup(group);
          	assertNotNull(group.getId());
          	
          	Group group2 = userDAO.findPopulatedGroupByName("testGroup");
          	
          	assertEquals("testGroup",group2.getName());
          	assertEquals(10, group2.getUsers().size());
          	String email = ((User)group2.getUsers().get(0)).getEmail();
          	assertEquals("rick0@foobar.com", email);
          
          	
          }
          
          
                
          														
          														
          														
          												
          												
          												
          										

          不建議使用上述解決方案,因?yàn)槊恳粋€(gè) User 都要在自己的事務(wù)中插入到數(shù)據(jù)庫(kù)中。如果出現(xiàn)問題,那么只能添加部分用戶。如果希望保留 ACID 屬性(即保證所有都發(fā)生或者所有都不發(fā)生),可以通過程序進(jìn)行事務(wù)管理,但是它很快就會(huì)變得一團(tuán)糟了。相反,應(yīng)使用 Spring 的 AOP 來支持聲明式的事務(wù),如清單 7 所示。

          清單 7. 聲明式管理事務(wù)

          												
          												
          												
          														
          														
          														
          																
          																
          																
          																		
          																				[applicationContext.xml]
          																		
          																
          																<!-- Pass the session factory to our UserDAO -->
             <bean id="userDAOTarget" class="net.sf.hibernateExamples.UserDAOImpl">
          	<property name="sessionFactory"><ref local="sessionFactory"/></property>
             </bean>
          	
             <bean id="transactionManager" 
             	class="org.springframework.orm.hibernate.HibernateTransactionManager">
                <property name="sessionFactory"><ref bean="sessionFactory"/></property>
             </bean>
          
             <bean id="userDAO"
               class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
          	<property name="transactionManager"><ref local="transactionManager"/></property>
          	<property name="target"><ref local="userDAOTarget"/></property>
          	<property name="transactionAttributes">
          		<props>
          			<prop key="addGroup">PROPAGATION_REQUIRED</prop>
          		</props>
          	</property>
              </bean>
          
                
          														
          														
          														
          												
          												
          												
          										

          注意在準(zhǔn)備清單 7 的代碼時(shí),我重新改寫了 UserDAO 并提取了其接口。這個(gè)接口現(xiàn)在是 UserDAO ,它的實(shí)現(xiàn)類是 UserDAOImpl 。這樣清單 7 中的事務(wù)代碼就使用了帶有事務(wù)屬性 (PROPAGATION_REQUIRED) UserDAO.addGroup() 方法。現(xiàn)在只要底層數(shù)據(jù)庫(kù)支持,就可以在一個(gè)事務(wù)中添加所有用戶。





          回頁(yè)首


          結(jié)束語

          在本文中,介紹了如何使用 Hibernate 和 Spring 實(shí)現(xiàn)一個(gè)事務(wù)持久層。Hibernate 是一種先進(jìn)的 OR 映射工具,而 Spring 是一個(gè) AOP 框架和 IOC 容器。這兩種技術(shù)的綜合使用,使得開發(fā)人員可以編寫媲美數(shù)據(jù)庫(kù)廠商的代碼,它可以在 J2EE 容器中運(yùn)行,也可以單獨(dú)運(yùn)行。使用了 DbUnit (JUnit 的擴(kuò)展)構(gòu)建和測(cè)試本文中例子的所有代碼,雖然這不是討論的重點(diǎn)。

          要了解有關(guān) AOP、IOC 容器和測(cè)試驅(qū)動(dòng)開發(fā)的更多內(nèi)容,請(qǐng)參閱 參考資料

          posted on 2006-05-18 20:03 崛起的程序員 閱讀(605) 評(píng)論(0)  編輯  收藏 所屬分類: java
          主站蜘蛛池模板: 德庆县| 建阳市| 肇源县| 栾川县| 米林县| 福安市| 望都县| 武夷山市| 海南省| 德令哈市| 西丰县| 利川市| 横山县| 灌阳县| 化州市| 灵川县| 闽侯县| 梁山县| 温州市| 交城县| 合作市| 长泰县| 阿荣旗| 井研县| 凤阳县| 南部县| 东城区| 防城港市| 五原县| 沈丘县| 澎湖县| 临沭县| 江津市| 和政县| 灯塔市| 缙云县| 资溪县| 松溪县| 山东省| 黑龙江省| 江阴市|