| 時(shí)間:2006-05-16 作者:Seth White 瀏覽次數: 2654 本文關(guān)鍵字:open jpa, jpa, java persistence api, spring, medrec, pojo, Persistence, WebLogic Server, Seth White, 持久化 |
|

Java Persistence API(JPA)和Spring Framework的2.0版本已經(jīng)引起開(kāi)發(fā)人員廣泛的興趣。本文將考察如何將Spring 2.0和JPA與BEA WebLogic Server一起使用。具體來(lái)說(shuō),我們將使用Spring和JPA對WebLogic Server的病歷示例應用程序進(jìn)行更新。本文將展示Spring和JPA這個(gè)功能強大的組合如何形成基于POJO的簡(jiǎn)化應用架構的基礎。使用的技術(shù)主要包括WebLogic Server 9.1、Spring 2.0和Kodo JPA。
病歷示例應用程序(MedRec)是一個(gè)綜合應用程序,它演示了如何在WebLogic Server中使用多種技術(shù)。這些技術(shù)包括開(kāi)源技術(shù)(如Spring和Struts)以及WebLogic Server所使用的技術(shù)(如Web services、JSP、消息驅動(dòng)bean和JDBC)。
本文描述在MedRec更新版本中Java Persistence API(JPA)是如何與Spring Framework配合使用的。本文的一個(gè)重要目的是向開(kāi)發(fā)人員展示Spring 2.0、WebLogic Server 9.1和Kodo 4.0如何一起使用。通過(guò)閱讀本文,開(kāi)發(fā)人員將獲得對有關(guān)使用JPA和Spring 2.0版本中新增的JPA支持的了解。本文還將討論在企業(yè)應用的多個(gè)層次中重用JavaBean(POJO)時(shí)可能出現的挑戰。重用是基于Spring和JPA的應用架構所帶來(lái)的重要獲益之一。
對于不熟悉Java Persistence API的人來(lái)說(shuō),JPA是一個(gè)指定如何將Java對象保存在數據庫中的新增簡(jiǎn)化API。JPA正在作為EJB 3.0(JSR 220)的一部分而被開(kāi)發(fā),因為它將取代EJB 2.x實(shí)體bean,但是它在J2EE和J2SE應用程序中均可使用。JPA的一個(gè)最重要的特性是:它是基于POJO的。JPA還使用Java 5.0注釋來(lái)簡(jiǎn)化指定從Java對象到關(guān)系數據庫的映射的方式。BEA已經(jīng)宣告建立OpenJPA(一個(gè)很快就可用的基于Kodo的開(kāi)源項目),但是您現在就可以開(kāi)始使用Kodo 4.0的早期訪(fǎng)問(wèn)版本。
本文將從對Spring中的POJO和數據訪(fǎng)問(wèn)的概述開(kāi)始。接下來(lái)概述MedRec的架構,并對MedRec數據訪(fǎng)問(wèn)層進(jìn)行更詳細的描述。然后我們將詳細考察JPA persistent類(lèi),并討論它們需要遵循的設計模式。最后,我們將介紹Spring和Kodo JPA的XML配置,以便將所有內容聯(lián)系起來(lái)。MedRec的完整源代碼可隨本文下載。
Spring Framework最著(zhù)名的優(yōu)點(diǎn)可能是簡(jiǎn)化了的業(yè)務(wù)組件開(kāi)發(fā)。熟悉Spring的人都知道,通過(guò)使用反向控制(IoC)和面向方面編程(AOP),Spring允許開(kāi)發(fā)人員編寫(xiě)功能強大的業(yè)務(wù)組件,這些業(yè)務(wù)組件仍然是常規JavaBean或POJO (Plain Old Java Object)——正如它們通常被稱(chēng)作的那樣。
對于需要訪(fǎng)問(wèn)數據庫的企業(yè)應用程序,Spring也提供了一個(gè)框架來(lái)簡(jiǎn)化對封裝持久化數據訪(fǎng)問(wèn)的數據訪(fǎng)問(wèn)對象(DAO)的創(chuàng )建。Spring的POJO功能還涉及數據訪(fǎng)問(wèn)領(lǐng)域,因為它們查詢(xún)和修改的數據訪(fǎng)問(wèn)對象和域模型對象均可以是POJO。
Spring對POJO模式的使用具有一些重要的實(shí)際優(yōu)點(diǎn)。首先,就業(yè)務(wù)組件而言,POJO減輕了開(kāi)發(fā)人員為實(shí)現應用程序而必須做的工作。它不僅減少了代碼行,而且由于這些代碼本身就是標準Java,所以它們更簡(jiǎn)單。其次,使用POJO意味著(zhù)應用程序調用數據訪(fǎng)問(wèn)對象的其余代碼不依賴(lài)于特定的持久化技術(shù)。例如,如果應用程序正在使用原始JDBC或JDO,那么使用POJO則會(huì )使轉換到使用JPA作為持久化解決方案的行為相對容易。
第三個(gè)優(yōu)點(diǎn)涉及域模型對象本身。在MedRec中,域模型對象代表諸如病人、醫生和處方之類(lèi)的實(shí)體。由于這些類(lèi)是POJO,而且不像傳統的EJB 2.x實(shí)體bean那樣依賴(lài)于特定的持久化環(huán)境,所以這些類(lèi)可以在多個(gè)應用程序代碼需要訪(fǎng)問(wèn)域模型的層次中重用。在MedRec中,這包括JSP使用域模型對象呈現用戶(hù)界面的Web層,以及使用域模型類(lèi)作為參數并將類(lèi)型返回給Web服務(wù)方法的Web服務(wù)層。
如果希望全面概覽MedRec應用程序的架構及其使用Spring的方式,請閱讀Spring Integration with WebLogic Server(中文版,dev2dev,2006年3月)。這篇文章很好地描述了Spring和WebLogic Server之間的常規集成。這里我們將簡(jiǎn)要討論MedRec架構,以說(shuō)明JPA是如何用于MedRec環(huán)境的。
圖1展示了MedRec的整體架構。在最高層,MedRec實(shí)際上是兩個(gè)獨立的J2EE應用(EAR文件):MedRec應用程序和Physician應用程序。所有的數據訪(fǎng)問(wèn)都由MedRec部分完成,所以我們將關(guān)注這部分。

如圖1所示,MedRec應用程序分為幾個(gè)層:一個(gè)包含Web服務(wù)的表示層、一個(gè)服務(wù)層、一個(gè)包括數據訪(fǎng)問(wèn)的集成層以及數據庫層。作為集成層一部分的Spring數據訪(fǎng)問(wèn)對象(DAO)主要由服務(wù)層的業(yè)務(wù)對象調用。DAO和服務(wù)對象都是由Spring應用程序上下文配置的Spring bean。服務(wù)對象使用Spring的聲明式事務(wù)支持來(lái)控制事務(wù)。
Medical Records應用程序使用4種不同的DAO,每一種對應于一個(gè)需要保存的域模型類(lèi)——Patient、Prescription、Record和User。DAO實(shí)現類(lèi)分別被稱(chēng)為JpaPatientDAO、JpaPrescriptionDAO、JpaRecordDAO和JpaUserDAO,可以在com.bea.medrec.dao.orm包中找到。
讓我們更詳細地考察Patient DAO,以便弄清楚它是如何工作的。以下是用于處理病歷應用程序中病人數據的檢索和更新的數據訪(fǎng)問(wèn)對象接口:
public interface PatientDao {public Patient getById(Integer patientId)throws DataAccessException;public List getByEmail(String email)throws DataAccessException;public List getByLastName(String lastName)throws DataAccessException;public List getByLastNameFirstName(String lastName,String firstName) throws DataAccessException;public List getByLastNameWild(String lastName)throws DataAccessException;public Patient getBySsn(String ssn)throws DataAccessException;public List getByStatus(String status)throws DataAccessException;public Patient getByUsername(String username)throws DataAccessException;public Patient save(Patient patient)throws DataAccessException;public Patient update(Patient patient)throws DataAccessException;}請注意,Patient DAO接口是plain old Java interface(POJI)。它不擴展或導入任何特定于所使用的持久化技術(shù)(本例中是JPA)的Java類(lèi)型。大多數Patient DAO方法執行查詢(xún)并返回病人列表或單個(gè)病人對的。最后兩個(gè)方法分別用于在數據庫中保存新病人的信息或者更新現有病人的信息。
該接口上的每個(gè)方法都聲明了一個(gè)DataAccessException,它是由Spring framework定義的運行時(shí)異常。關(guān)于DataAccessException,有兩件事需要注意。首先,它是運行時(shí)異常,所以使用數據訪(fǎng)問(wèn)對象的應用程序代碼不需要像在JDBC和EJB 2.x實(shí)體bean的情況下那樣使用try-catch塊包裝每次調用。第二,DataAccessException是有用的,因為它包裝底層持久化技術(shù)所使用的特定異常類(lèi),從而使應用程序的其他部分獨立于持久化層。
關(guān)于接口就說(shuō)這么多了。接下來(lái),讓我們考察一下Patient DAO的實(shí)現。以下是基類(lèi)BaseDAO,MedRec中的所有數據訪(fǎng)問(wèn)對象都對它進(jìn)行擴展:
package com.bea.medrec.dao.orm;import org.springframework.orm.jpa.support.JpaDaoSupport;public abstract class BaseDao extends JpaDaoSupport {...}在我們的例子中,這是一個(gè)非常簡(jiǎn)單的類(lèi),但是一般來(lái)說(shuō),它可用于在不同數據訪(fǎng)問(wèn)對象實(shí)現上所共有的任何代碼。BaseDAO擴展了Spring的JpaDaoSupport類(lèi)。這是必須的,因為它為我們的DAO實(shí)現類(lèi)提供到Spring的特定于JPA的API的訪(fǎng)問(wèn)。
Patient DAO實(shí)現類(lèi)JpaPatientDao擴展了BaseDao:
public class JpaPatientDao extends BaseDao implements PatientDao {public Patient getById(Integer pId)throws DataAccessException {return getJpaTemplate().find(Patient.class, pId);}public List getByLastName(String pLastName)throws DataAccessException {List patients = getJpaTemplate().find("SELECT p " +"FROM " + Patient.class.getSimpleName() + " p " +"WHERE p.lastName LIKE ?1", pLastName);return (patients.isEmpty())?Collections.EMPTY_LIST : patients;}...}此代碼示例還展示了兩個(gè)示范查詢(xún)方法實(shí)現。第一個(gè)是getById(),它通過(guò)病人的惟一標識字段查找病人。這是通過(guò)調用Spring的JpaTemplate上的專(zhuān)用find方法完成的,它將Patient類(lèi)和惟一的id作為參數,并返回具有此id的Patient對象。getByLastName()方法返回具有相似的姓的病人列表。getByLastName()使用JpaTemplate.find()的替代版本,它將后跟查詢(xún)參數的EJB QL查詢(xún)作為參數,并返回匹配查詢(xún)的對象列表。以下是patient DAO的保存和更新方法:
public class JpaPatientDao extends BaseDao implements PatientDao {...public Patient save(Patient pPatient)throws DataAccessException {getJpaTemplate().persist(pPatient);return pPatient;}public Patient update(Patient pPatient)throws DataAccessException {return getJpaTemplate().merge(pPatient);}}在此patient DAO的保存和更新方法的實(shí)現中,save()方法用于將新病人的記錄插入數據庫,而update()則修改現有的病人記錄。每個(gè)方法返回一個(gè)對新的或更新后的Patient對象的引用。重點(diǎn)是要理解保存和合并不會(huì )立即導致數據庫更新。更改通常由JPA緩存直到當前事務(wù)提交。然后更改被批量發(fā)送到數據庫,但是只要有查詢(xún)在運行,未決更改也可以常常被刷新。
請注意一件有趣的事,我們的Patient DAO對象對JPA API的依賴(lài)很少,因為Spring模板被用于所有數據訪(fǎng)問(wèn)操作,它又內部委托給JPA。事實(shí)上,到目前為止,我們看到的對JPA的惟一直接依賴(lài)是getByLastName()方法使用的EJB QL查詢(xún)字符串。還請注意,雖然早些時(shí)候我們將Spring DAO稱(chēng)作POJO,但是這并不完全正確,因為DAO需要擴展Spring的JpaDaoSupport類(lèi)。然而,重要的是,暴露給應用程序其他部分的接口DAO是不依賴(lài)于JPA或Spring類(lèi)型的plain old Java interface。
到目前為止,我們已經(jīng)看到一些處理查詢(xún)和更新的示例數據訪(fǎng)問(wèn)代碼,那么持久化類(lèi)又是什么呢?想想POJO和元數據。但是,持久化POJO需要遵循許多規則,或者說(shuō)是設計模式。這些規則部分由JPA指定,而其他的是根據整個(gè)應用架構的要求而產(chǎn)生的,正如我們下面將看到的。
首先是一條關(guān)于元數據的注意事項。JPA為開(kāi)發(fā)人員提供了幾種選項來(lái)指定與持久化和對象-關(guān)系映射相關(guān)的JPA元數據:在外部XML文件中指定、在Java 5.0注釋中指定,或者使用這二者的組合。病歷應用程序使用JPA元數據的Java注釋。這樣做的好處是:元數據與應用該元數據的Java類(lèi)、字段和方法一起配置,這樣可以幫助理解。如果想使元數據與代碼分離,可以使用XML來(lái)代替。
讓我們考察一下Patient類(lèi)及其字段聲明,看看示例注釋JPA類(lèi)是什么樣的:
package com.bea.medrec.domain;import javax.persistence.*;import kodo.persistence.jdbc.ElementJoinColumn;@Entity()public class Patient implements Serializable{@Id()@GeneratedValue(strategy=IDENTITY)private Integer id;private Date dob;@Column(name = "first_name")private String firstName;private String gender;@Column(name = "last_name")private String lastName;@Column(name = "middle_name")private String middleName;private String phone;private String ssn;@ManyToOne(cascade={PERSIST, MERGE})@JoinColumn (name="address_id")private Address address;@OneToOne(cascade={PERSIST, MERGE})@JoinColumn (name="email")private User user;@OneToMany(tar on getEntity = Prescription.class)@ElementJoinColumn(name="pat_id")private Set prescriptions=null;@OneToMany (targetEntity=Record.class)@ElementJoinColumn(name="pat_id")private Set records =null;...}請注意,Patient類(lèi)使用Entity注釋進(jìn)行注釋。這是使用注釋的所有持久化類(lèi)必須遵循的。Entity注釋的存在告訴持久化引擎該類(lèi)是JPA實(shí)體。通常不要求Entity類(lèi)是公有的,但是MedRec的持久化類(lèi)需要是公有的,以便Web和Web服務(wù)層可以使用它們。
實(shí)體類(lèi)還需要實(shí)現Serializable,但是由于Patient類(lèi)由MedRec struts操作置于HTTP會(huì )話(huà)中,所以它需要是可串行化的。這樣應用程序可以在集群環(huán)境中工作,其中會(huì )話(huà)狀態(tài)可以在集群中各節點(diǎn)之間進(jìn)行串行化。關(guān)于實(shí)體類(lèi)的其他要求是:它必須是頂級類(lèi)且不是final。請參閱JPA規范(JSR220)以獲得完整的規則列表。
JPA實(shí)現需要在運行時(shí)讀取和寫(xiě)入實(shí)體類(lèi)的狀態(tài)(持久化字段)。JPA規范允許實(shí)現或者通過(guò)直接訪(fǎng)問(wèn)實(shí)體字段的方式或者通過(guò)調用JavaBean風(fēng)格的accessor方法(getter/setter)的方式來(lái)進(jìn)行此操作。使用的訪(fǎng)問(wèn)方式由持久化注釋的位置決定。例如,如果注釋位于類(lèi)的字段上,則使用字段訪(fǎng)問(wèn)。
您可能要問(wèn):為什么JPA支持字段和方法兩種訪(fǎng)問(wèn)方式呢?答案是靈活性。一些類(lèi)在它們的公有accessor方法中執行驗證。如果由于數據庫中的值溢出而拋出異常,這可能導致JPA實(shí)現出現問(wèn)題。如果類(lèi)在其setter中執行驗證,則最好對字段進(jìn)行注釋。另一方面,如果希望支持虛擬持久化屬性之類(lèi)的功能,那么就注釋accessor方法。
在JPA實(shí)體中,所有未使用Transient注釋?zhuān)ㄓ蒍PA定義)進(jìn)行注釋的非瞬態(tài)字段都是持久化字段。“持久化”意味著(zhù)字段被映射到數據庫中的列。請注意,Patient類(lèi)中的一些持久化字段沒(méi)有注釋。這是由于JPA定義的默認值(如默認的列名稱(chēng))對于這些字段來(lái)說(shuō)是正確的。如果字段名稱(chēng)與其映射的數據庫列名稱(chēng)不同,則必須使用Column注釋來(lái)為數據庫列指定一個(gè)不同的列名稱(chēng)。
每個(gè)實(shí)體必須具有主鍵。JPA支持簡(jiǎn)單(單字段)和復合(多字段)的主鍵。如果可以,最好一直使用簡(jiǎn)單主鍵,并讓數據庫生成主鍵值。這是因為,一旦對象被保存,JPA就不允許更改主鍵值。由于此原因,病歷應用程序中的大多數持久化類(lèi)(包括Patient類(lèi))都使用簡(jiǎn)單的生成主鍵。關(guān)于具有復合主鍵的類(lèi)的示例,請參考Group類(lèi)。Id注釋用于標記主鍵字段或標識字段。
GeneratedValue注釋用于表示在插入新的Patient時(shí)主鍵值是由數據庫生成的。數據庫可以以數種方式生成主鍵值,可使用strategy屬性來(lái)選擇其中一個(gè)。Patient類(lèi)使用標識策略,這意味著(zhù)id字段應被映射到數據庫自動(dòng)-增量或標識列。
在使用標識策略時(shí),需要注意這對類(lèi)的equals和hashcode方法所產(chǎn)生的影響。例如,如果equals方法比較id字段的值,請確保使用對象的方式不會(huì )導致equals方法在進(jìn)行數據庫插入操作之前(通常在事務(wù)提交時(shí))被調用,因為主鍵值直到這時(shí)才被分配。避免這種情況的一種方法是,(如果類(lèi)有的話(huà))在equals方法中使用自然鍵而不是生成鍵。例如,Patient Social Security number是可用于Patient equals方法中的很好的自然鍵。
大多數實(shí)體具有與域模型中其他的單個(gè)或多個(gè)實(shí)體的關(guān)系,Patient類(lèi)也不例外。圖2展示了Patient類(lèi)及其關(guān)系圖。

JPA使用一對一、多對一、一對多和多對多注釋來(lái)指定關(guān)系及其基數。每個(gè)關(guān)系字段必須使用這些注釋中的一個(gè)進(jìn)行注釋。例如,Patient具有與Address的多對一關(guān)系。這意味著(zhù)許多病人可能具有相同的地址。Patient與User類(lèi)具有一對一關(guān)系,該類(lèi)包含病人的用戶(hù)名和密碼信息。這意味著(zhù)每個(gè)Patient與單個(gè)的User相關(guān)。
Patient具有與Prescriptions和Records的一對多關(guān)系,因為特定的病人可以有多個(gè)處方,并可能多次看醫生(這將導致創(chuàng )建病歷)。相反,每個(gè)Prescription或Record只可以與一個(gè)Patient關(guān)聯(lián)。
JPA關(guān)系的一個(gè)有趣特性是操作的級聯(lián)。您可能已經(jīng)熟悉了大多數關(guān)系數據庫所支持的“級聯(lián)刪除”特性。JPA采用了類(lèi)似于刪除的級聯(lián)操作的理念,并將其應用于其他操作(例如插入和更新)。Patient類(lèi)將插入和關(guān)系操作級聯(lián)到相關(guān)的Address和User。在JPA中插入被視為持久化,因為數據庫插入是在對對象持久化時(shí)執行的。更新被視作合并,因為更新將對象中的更改合并到當前JPA持久化上下文中。這看上去有些混亂,但是請別擔心;只需記?。涸谙M乱呀?jīng)被保存的對象時(shí),要調用合并;當要在數據庫中插入新的持久化對象時(shí),要調用持久化。
確定是否對特定操作(例如持久化、合并或移除)進(jìn)行級聯(lián)是需要技巧的。在Patient類(lèi)的情況中,持久化被級聯(lián)到Address和User,因為應用程序也需要在插入Patient的地方插入新的Address和User。類(lèi)似地,在更新Patient的時(shí)候,Address和User可能也需要更新。Prescriptions和Records則不同。應用程序具有顯式持久化(以及更新)Prescription或Record的操作,所以不需要將這些操作從Patient級聯(lián)到這些類(lèi)。
您可能還記得前一節中我們曾說(shuō)多個(gè)Patient被允許引用相同的Address——從Patient到Address的關(guān)系是多對一。但是,我們剛剛也說(shuō)了Patient將插入級聯(lián)到Address,這導致為數據庫中的每位病人創(chuàng )建一個(gè)新地址。這是怎么回事呢?您在這里看到的是在Patient和Address之間使用生成的主鍵和級聯(lián)插入的有趣效果。因為用于地址的主鍵由數據庫自動(dòng)生成,所以每個(gè)Address將有惟一的主鍵,指向相同實(shí)際地址的兩個(gè)不同Address對象有可能都被插入到數據庫中。這對于許多應用程序(包括MedRec)來(lái)說(shuō)沒(méi)什么問(wèn)題,但是如果您希望阻止復制這樣的地址,則DAO需要執行一些附加的邏輯。
JPA將MedRec的實(shí)體類(lèi)映射到數據庫模式,而WebLogic Server 9.1 Web services實(shí)現將其映射到XML,這引發(fā)了一些影響實(shí)體類(lèi)設計的其他問(wèn)題。
JPA支持單向和雙向關(guān)系,但是如果考察MedRec中的實(shí)體類(lèi),會(huì )看到所有關(guān)系都是單向的。這是必要的,因為Web services綁定不支持雙向關(guān)系。
由于JPA要求Java Collection類(lèi)型(如java.util.Set)用于多值關(guān)系字段,這引發(fā)了另一個(gè)問(wèn)題。實(shí)踐表明9.1中的Web services不支持Collection類(lèi)型。那么我們如何解決這一問(wèn)題呢?
不知您是否還記得,對象-關(guān)系映射可以在實(shí)體的字段或方法上定義。實(shí)踐表明,JPA的此靈活性非常重要。另一方面,Web services使用類(lèi)的公有屬性accessor方法(getXXX()方法)定義被封送為XML的屬性。通過(guò)使用字段定義持久化映射以及由類(lèi)上的方法定義XML綁定,我們能夠將兩個(gè)映射分離!多值關(guān)系為了持久化在內部使用Set,但是可以通過(guò)屬性訪(fǎng)問(wèn)方法將其暴露為XML綁定能夠處理的數組。
為了具體說(shuō)明,下面是一個(gè)來(lái)自Record類(lèi)的執行此轉換的屬性訪(fǎng)問(wèn)方法示例:
public Prescription[] getPrescriptions() {if (prescriptions == null)return null;return (Prescription[]) prescriptions.toArray(newPrescription[prescriptions.size()]);}此方法在內部訪(fǎng)問(wèn)具有java.util.Set類(lèi)型的持久化處方字段。此Set被轉換為Web services實(shí)現能夠處理的數組。關(guān)于Web services集成的體驗使我們對JPA及其與現有應用程序和基礎架構集成的能力印象深刻。
現在對持久化類(lèi)的討論就結束了??雌饋?lái)似乎需要遵循許多規則和限制,但事實(shí)上大多數JPA只是將好的編碼實(shí)踐具體化了,您可能會(huì )發(fā)現您沒(méi)有過(guò)多考慮就在遵循它們。
現在我們看到了如何使用Spring 2.0和JPA實(shí)現數據訪(fǎng)問(wèn)對象,接下來(lái)讓我們快速考察一下使用DAO的MedRec服務(wù)對象。以下的代碼示例顯示了PatientServiceImpl類(lèi)及其業(yè)務(wù)方法之一,processNewRegistration方法:
public class PatientServiceImplextends BaseService implements PatientService {protected PatientDao patientDao;public void setPatientDao(PatientDao patientDao) {this.patientDao = patientDao;}public void processNewRegistration(Registration registration) {try {User user = registration.getUser();Patient patient = registration.getPatient();patient.setUser(user);user.setStatus(MedRecConstants.USER_NEW);patientDao.save(pPatient);} catch (Exception ex) {...}}...}當新病人使用MedRec Web接口自我注冊時(shí)會(huì )調用processNewRegistration方法。它將保存新病人注冊信息的Registration對象作為參數。ProcessNewRegistration首先從Registration對象獲取Patient和User,并初始化它們之間的關(guān)系。User對象包含病人的用戶(hù)名和密碼信息,還保存被設置為USER_NEW的病人狀態(tài),因為這是位必須由管理員批準的新病人。然后調用Patient DAO將新的病人插入到數據庫。
針對MedRec服務(wù)對象對Spring聲明式事務(wù)進(jìn)行配置,以便在調用業(yè)務(wù)方法(如processNewRegistration)之前由Spring啟動(dòng)事務(wù),并在此方法完成時(shí)提交該事務(wù)。這確保了DAO作為可自動(dòng)提交的現有事務(wù)的一部分而運行。還請注意Spring所使用的、將對DAO對象的引用注入服務(wù)bean中的setPatientDao方法。
到目前為止,我們已經(jīng)考察了一些構成此版本的病歷應用程序的Java代碼。本節將為您介紹Spring和Kodo要求的外部配置。我們從Spring開(kāi)始。
我們使用一組模塊化XML配置文件來(lái)配置Spring ApplicationContext。包含數據訪(fǎng)問(wèn)對象配置的配置文件是applicationContext-orm.xml,它位于MedRec安裝目錄下的src\medrecEar\APP-INF\classes目錄中。以下是applicationContext-orm.xml中聲明Patient DAO的片段:
<bean id="patientDao"class="com.bea.medrec.dao.orm.JpaPatientDao"autowire="byType"/>
Spring的JPA DAO具有需要被注入的單一依賴(lài),即JPA EntityManagerFactory。DAO使用此EntityManagerFactory創(chuàng )建執行實(shí)際持久化工作的JPA EntityManagers。這里使用Spring自動(dòng)綁定特性完成綁定,這就是為什么我們在XML中看不到對EntityManagerFactory的顯式依賴(lài)。自動(dòng)綁定特性自動(dòng)將DAO綁定到基于DAO的EntityManagerFactory屬性(繼承自所有MedRec DAO類(lèi)對其進(jìn)行擴展的Spring JpaDaoSupport類(lèi))類(lèi)型的EntityManagerFactory。
以下代碼是聲明創(chuàng )建EntityManagerFactory的Spring工廠(chǎng)bean的片段。由于這里討論了兩個(gè)工廠(chǎng),可能聽(tīng)起來(lái)有些混亂,但是請記?。篠pring工廠(chǎng)bean的目的就是提供一個(gè)中間層,以便允許我們執行某些自定義代碼以創(chuàng )建EntityManagerFactory。此EntityManagerFactory是DAO對象惟一知道或關(guān)心的工廠(chǎng)。
<bean id="entityManagerFactory"class="com.bea.medrec.utils.KodoEntityManagerFactoryBean"><property name="jndiName"><value>kodo-jpa</value></property></bean>
因為在撰寫(xiě)本文的時(shí)候,WebLogic Server不支持與JPA的標準集成,所以EntityManagerFactory通過(guò)使用自定義工廠(chǎng)bean而創(chuàng )建。EntityManagerFactory需要知道在JNDI中的什么地方查找Kodo資源適配器。該信息被配置為Spring工廠(chǎng)bean上的屬性,后者將其傳送到EntityManagerFactory。請記住,工廠(chǎng)bean的任務(wù)是創(chuàng )建EntityManagerFactory事件并將其返回到Spring,以使Spring能夠將EntityManagerFactory注入DAO bean。用于創(chuàng )建實(shí)體管理器工廠(chǎng)實(shí)例的工廠(chǎng)bean代碼如下所示:
public class KodoEntityManagerFactoryBeanimplements FactoryBean, InitializingBean {private String jndiName = "kodo-jpa";private EntityManagerFactory entityManagerFactory = null;public void setJndiName(String jndiName) {this.jndiName = jndiName;}public Object getObject() throws Exception {return entityManagerFactory;}public Class getObjectType() {return EntityManagerFactory.class;}public boolean isSingleton() {return true;}public void afterPropertiesSet() throws Exception {try {entityManagerFactory =KodoPersistence.createEntityManagerFactory(jndiName, new InitialContext());} catch (Throwable throwable) {throw new RuntimeException(throwable);}}}KodoEntityManagerFactoryBean是一個(gè)相當簡(jiǎn)單的Spring工廠(chǎng)bean。在運行時(shí),Spring首先調用默認的構造函數創(chuàng )建bean實(shí)例。然后,Spring調用setJndiName()方法設置Kodo資源適配器的JNDI名稱(chēng)。一旦所有屬性被注入,Spring就調用創(chuàng )建EntityManagerFactory實(shí)例的afterPropertiesSet()。最后,Spring調用getObject()來(lái)獲取將被注入DAO的EntityManagerFactory。
我們已經(jīng)看到了如何使用Kodo EntityManagerFactory(DAO使用它來(lái)完成所有持久化工作)注入Spring DAO。然后Kodo EntityManagerFactory通過(guò)JNDI連接到Kodo資源適配器。接下來(lái),我們將考察Kodo資源適配器的配置,但是首先需要知道一點(diǎn)關(guān)于Spring配置的其他內容,這將有助于將所有事情聯(lián)系起來(lái)。
Spring需要被告知使用WebLogic JTA事務(wù)管理器來(lái)啟動(dòng)和提交事務(wù)。此操作在以下的XML片段中完成:
<bean id="transactionManager"class="org.springframework.transaction.jta.WebLogicJtaTransactionManager"><property name="transactionManagerName"value="javax.transaction.TransactionManager"/></bean>
這里要明白一件重要的事情,即,需要專(zhuān)門(mén)告訴Spring將事務(wù)工作委托給WebLogic JTA實(shí)現。稍后,我們將看到Kodo也被告知在其配置中使用JTA事務(wù)。這將安排好事情以便MedRec服務(wù)對象啟動(dòng)WebLogic JTA事務(wù),然后在此事務(wù)中調用DAO。隨后DAO調用Kodo,它作為現有WebLogic JTA事務(wù)的一部分執行數據庫讀出和寫(xiě)入。
我們使用Kodo 4.0 EA4,它是Kodo的JPA實(shí)現的早期訪(fǎng)問(wèn)版本。在撰寫(xiě)本文的時(shí)候,定義JPA的EJB 3.0規范尚未完成,所以Kodo支持JPA的pre-final版本。Kodo作為資源適配器被部署到WebLogic Server。此資源適配器在JNDI中進(jìn)行注冊。資源適配器的配置在標準ra.xml描述符文件中進(jìn)行,它位于資源適配器RAR文件的META-INF目錄中。
如果考察ra.xml文件,會(huì )看到Kodo支持多種配置選項(成熟產(chǎn)品的標志)。然而,示例應用程序僅需要我們修改其中的一小部分。讓我們看看示例應用程序需要的屬性:
<config-property><config-property-name>ConnectionFactory2Name</config-property-name><config-property-type>java.lang.String</config-property-type><config-property-value>jdbc/MedRecGlobalDataSource</config-property-value></config-property><config-property><config-property-name>ConnectionFactoryName</config-property-name><config-property-type>java.lang.String</config-property-type><config-property-value>jdbc/MedRecGlobalDataSourceXA</config-property-value></config-property><config-property><config-property-name>TransactionMode</config-property-name><config-property-type>java.lang.String</config-property-type><config-property-value>managed</config-property-value></config-property><config-property><config-property-name>LicenseKey</config-property-name><config-property-type>java.lang.String</config-property-type><config-property-value>XXXXXXXXXXXXXXXXXXXX</config-property-value></config-property><config-property><config-property-name>PersistentClasses</config-property-name><config-property-type>java.lang.String</config-property-type><config-property-value>com.bea.medrec.domain.Patient,com.bea.medrec.domain.Address,com.bea.medrec.domain.User,com.bea.medrec.domain.Physician,com.bea.medrec.domain.Prescription,com.bea.medrec.domain.Record,com.bea.medrec.domain.Group,com.bea.medrec.domain.VitalSigns</config-property-value></config-property>
Kodo需要被告知JDBC數據源的JNDI位置,以便它能夠與數據庫交互。事實(shí)上,Kodo需要知道兩個(gè)數據源:一個(gè)數據源用于處理事務(wù)工作,另一個(gè)用于非事務(wù)工作,因為Kodo有時(shí)訪(fǎng)問(wèn)數據庫執行不屬于全局事務(wù)的工作。您可能已經(jīng)猜到了,ConnectionFactoryName和ConnectionFactory2Name屬性正是用于此目的。每個(gè)屬性的值是WebLogic數據源的JNDI名稱(chēng)。請確保ConnectionFactory2Name引用的數據源不將其連接加入全局JTA事務(wù)。
Kodo還需要知道此應用程序是否正在使用JTA管理的事務(wù)或本地事務(wù)。因為控制事務(wù)的MedRec服務(wù)bean被配置為使用JTA,所以我們將TransactionMode屬性值設為托管。此外,還需要在資源適配器文件中配置Kodo許可,并列出應用程序將使用的持久化類(lèi)。用于此用途的屬性應該是自解釋的。
如果您曾經(jīng)考察過(guò)JPA規范,您可能聽(tīng)說(shuō)過(guò)persistence.xml文件。這是包含與JPA持久化相關(guān)的元數據的標準配置文件。Kodo還支持persistence.xml文件。然而,在應用服務(wù)器環(huán)境中使用Kodo的當前早期訪(fǎng)問(wèn)版本時(shí),persistence.xml文件僅包含資源適配器配置中指定的信息的子集,且只能通過(guò)工具(例如Kodo增強程序)使用。
以下下載包括本文所討論的MedRec版本的所有Java和其他源文件,以及Spring 2.0和Kodo二進(jìn)制文件及其依賴(lài)性:medrec-spring-jpa.zip。您大概需要27MB的磁盤(pán)空間來(lái)存放下載文件。
本文詳細考察了對Spring 2.0中的新增JPA支持的使用,該特性被用于重新實(shí)現MedRec示例應用程序的數據訪(fǎng)問(wèn)層。希望本文為您提供了開(kāi)始使用這些新API所需的信息。如果您希望進(jìn)行更深入地研究,我們建議您考察本文所包含的實(shí)際MedRec代碼。示例應用程序代碼演示了如何以集成方式協(xié)同使用WebLogic 9.1、Spring 2.0和Kodo JPA。
我們已經(jīng)看到新的JPA API易用、直觀(guān)且具有靈活性。Java注釋在降低指定數據庫映射所需的元數據數量方面表現突出。雖然JPA是新增的,但是其強大功能足以允許我們繼續在持久化層、Web層和Web服務(wù)層中的應用程序中使用域模型類(lèi)。此重用大大簡(jiǎn)化了應用架構,并顯著(zhù)降低了開(kāi)發(fā)人員需要編寫(xiě)的Java類(lèi)的數量。
而Spring 2.0方面提供了一個(gè)利用JPA創(chuàng )建數據訪(fǎng)問(wèn)對象的優(yōu)秀工具。Spring的數據訪(fǎng)問(wèn)架構使得我們可以輕松地在不同持久化技術(shù)之間進(jìn)行轉換,而無(wú)需重寫(xiě)其他應用程序代碼。
聯(lián)系客服