時間:2006-05-16 作者:Seth White 瀏覽次數(shù): 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)引起開發(fā)人員廣泛的興趣。本文將考察如何將Spring 2.0和JPA與BEA WebLogic Server一起使用。具體來說,我們將使用Spring和JPA對WebLogic Server的病歷示例應用程序進行更新。本文將展示Spring和JPA這個功能強大的組合如何形成基于POJO的簡化應用架構(gòu)的基礎(chǔ)。使用的技術(shù)主要包括WebLogic Server 9.1、Spring 2.0和Kodo JPA。
病歷示例應用程序(MedRec)是一個綜合應用程序,它演示了如何在WebLogic Server中使用多種技術(shù)。這些技術(shù)包括開源技術(shù)(如Spring和Struts)以及WebLogic Server所使用的技術(shù)(如Web services、JSP、消息驅(qū)動bean和JDBC)。
本文描述在MedRec更新版本中Java Persistence API(JPA)是如何與Spring Framework配合使用的。本文的一個重要目的是向開發(fā)人員展示Spring 2.0、WebLogic Server 9.1和Kodo 4.0如何一起使用。通過閱讀本文,開發(fā)人員將獲得對有關(guān)使用JPA和Spring 2.0版本中新增的JPA支持的了解。本文還將討論在企業(yè)應用的多個層次中重用JavaBean(POJO)時可能出現(xiàn)的挑戰(zhàn)。重用是基于Spring和JPA的應用架構(gòu)所帶來的重要獲益之一。
對于不熟悉Java Persistence API的人來說,JPA是一個指定如何將Java對象保存在數(shù)據(jù)庫中的新增簡化API。JPA正在作為EJB 3.0(JSR 220)的一部分而被開發(fā),因為它將取代EJB 2.x實體bean,但是它在J2EE和J2SE應用程序中均可使用。JPA的一個最重要的特性是:它是基于POJO的。JPA還使用Java 5.0注釋來簡化指定從Java對象到關(guān)系數(shù)據(jù)庫的映射的方式。BEA已經(jīng)宣告建立OpenJPA(一個很快就可用的基于Kodo的開源項目),但是您現(xiàn)在就可以開始使用Kodo 4.0的早期訪問版本。
本文將從對Spring中的POJO和數(shù)據(jù)訪問的概述開始。接下來概述MedRec的架構(gòu),并對MedRec數(shù)據(jù)訪問層進行更詳細的描述。然后我們將詳細考察JPA persistent類,并討論它們需要遵循的設(shè)計模式。最后,我們將介紹Spring和Kodo JPA的XML配置,以便將所有內(nèi)容聯(lián)系起來。MedRec的完整源代碼可隨本文下載。
Spring Framework最著名的優(yōu)點可能是簡化了的業(yè)務組件開發(fā)。熟悉Spring的人都知道,通過使用反向控制(IoC)和面向方面編程(AOP),Spring允許開發(fā)人員編寫功能強大的業(yè)務組件,這些業(yè)務組件仍然是常規(guī)JavaBean或POJO (Plain Old Java Object)——正如它們通常被稱作的那樣。
對于需要訪問數(shù)據(jù)庫的企業(yè)應用程序,Spring也提供了一個框架來簡化對封裝持久化數(shù)據(jù)訪問的數(shù)據(jù)訪問對象(DAO)的創(chuàng)建。Spring的POJO功能還涉及數(shù)據(jù)訪問領(lǐng)域,因為它們查詢和修改的數(shù)據(jù)訪問對象和域模型對象均可以是POJO。
Spring對POJO模式的使用具有一些重要的實際優(yōu)點。首先,就業(yè)務組件而言,POJO減輕了開發(fā)人員為實現(xiàn)應用程序而必須做的工作。它不僅減少了代碼行,而且由于這些代碼本身就是標準Java,所以它們更簡單。其次,使用POJO意味著應用程序調(diào)用數(shù)據(jù)訪問對象的其余代碼不依賴于特定的持久化技術(shù)。例如,如果應用程序正在使用原始JDBC或JDO,那么使用POJO則會使轉(zhuǎn)換到使用JPA作為持久化解決方案的行為相對容易。
第三個優(yōu)點涉及域模型對象本身。在MedRec中,域模型對象代表諸如病人、醫(yī)生和處方之類的實體。由于這些類是POJO,而且不像傳統(tǒng)的EJB 2.x實體bean那樣依賴于特定的持久化環(huán)境,所以這些類可以在多個應用程序代碼需要訪問域模型的層次中重用。在MedRec中,這包括JSP使用域模型對象呈現(xiàn)用戶界面的Web層,以及使用域模型類作為參數(shù)并將類型返回給Web服務方法的Web服務層。
如果希望全面概覽MedRec應用程序的架構(gòu)及其使用Spring的方式,請閱讀Spring Integration with WebLogic Server(中文版,dev2dev,2006年3月)。這篇文章很好地描述了Spring和WebLogic Server之間的常規(guī)集成。這里我們將簡要討論MedRec架構(gòu),以說明JPA是如何用于MedRec環(huán)境的。
圖1展示了MedRec的整體架構(gòu)。在最高層,MedRec實際上是兩個獨立的J2EE應用(EAR文件):MedRec應用程序和Physician應用程序。所有的數(shù)據(jù)訪問都由MedRec部分完成,所以我們將關(guān)注這部分。
如圖1所示,MedRec應用程序分為幾個層:一個包含Web服務的表示層、一個服務層、一個包括數(shù)據(jù)訪問的集成層以及數(shù)據(jù)庫層。作為集成層一部分的Spring數(shù)據(jù)訪問對象(DAO)主要由服務層的業(yè)務對象調(diào)用。DAO和服務對象都是由Spring應用程序上下文配置的Spring bean。服務對象使用Spring的聲明式事務支持來控制事務。
Medical Records應用程序使用4種不同的DAO,每一種對應于一個需要保存的域模型類——Patient、Prescription、Record和User。DAO實現(xiàn)類分別被稱為JpaPatientDAO、JpaPrescriptionDAO、JpaRecordDAO和JpaUserDAO,可以在com.bea.medrec.dao.orm包中找到。
讓我們更詳細地考察Patient DAO,以便弄清楚它是如何工作的。以下是用于處理病歷應用程序中病人數(shù)據(jù)的檢索和更新的數(shù)據(jù)訪問對象接口:
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)。它不擴展或?qū)肴魏翁囟ㄓ谒褂玫某志没夹g(shù)(本例中是JPA)的Java類型。大多數(shù)Patient DAO方法執(zhí)行查詢并返回病人列表或單個病人對的。最后兩個方法分別用于在數(shù)據(jù)庫中保存新病人的信息或者更新現(xiàn)有病人的信息。
該接口上的每個方法都聲明了一個DataAccessException,它是由Spring framework定義的運行時異常。關(guān)于DataAccessException,有兩件事需要注意。首先,它是運行時異常,所以使用數(shù)據(jù)訪問對象的應用程序代碼不需要像在JDBC和EJB 2.x實體bean的情況下那樣使用try-catch塊包裝每次調(diào)用。第二,DataAccessException是有用的,因為它包裝底層持久化技術(shù)所使用的特定異常類,從而使應用程序的其他部分獨立于持久化層。
關(guān)于接口就說這么多了。接下來,讓我們考察一下Patient DAO的實現(xiàn)。以下是基類BaseDAO,MedRec中的所有數(shù)據(jù)訪問對象都對它進行擴展:
package com.bea.medrec.dao.orm;import org.springframework.orm.jpa.support.JpaDaoSupport;public abstract class BaseDao extends JpaDaoSupport {...}
在我們的例子中,這是一個非常簡單的類,但是一般來說,它可用于在不同數(shù)據(jù)訪問對象實現(xiàn)上所共有的任何代碼。BaseDAO擴展了Spring的JpaDaoSupport類。這是必須的,因為它為我們的DAO實現(xiàn)類提供到Spring的特定于JPA的API的訪問。
Patient DAO實現(xiàn)類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;}...}
此代碼示例還展示了兩個示范查詢方法實現(xiàn)。第一個是getById(),它通過病人的惟一標識字段查找病人。這是通過調(diào)用Spring的JpaTemplate上的專用find方法完成的,它將Patient類和惟一的id作為參數(shù),并返回具有此id的Patient對象。getByLastName()方法返回具有相似的姓的病人列表。getByLastName()使用JpaTemplate.find()的替代版本,它將后跟查詢參數(shù)的EJB QL查詢作為參數(shù),并返回匹配查詢的對象列表。以下是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的保存和更新方法的實現(xiàn)中,save()方法用于將新病人的記錄插入數(shù)據(jù)庫,而update()則修改現(xiàn)有的病人記錄。每個方法返回一個對新的或更新后的Patient對象的引用。重點是要理解保存和合并不會立即導致數(shù)據(jù)庫更新。更改通常由JPA緩存直到當前事務提交。然后更改被批量發(fā)送到數(shù)據(jù)庫,但是只要有查詢在運行,未決更改也可以常常被刷新。
請注意一件有趣的事,我們的Patient DAO對象對JPA API的依賴很少,因為Spring模板被用于所有數(shù)據(jù)訪問操作,它又內(nèi)部委托給JPA。事實上,到目前為止,我們看到的對JPA的惟一直接依賴是getByLastName()方法使用的EJB QL查詢字符串。還請注意,雖然早些時候我們將Spring DAO稱作POJO,但是這并不完全正確,因為DAO需要擴展Spring的JpaDaoSupport類。然而,重要的是,暴露給應用程序其他部分的接口DAO是不依賴于JPA或Spring類型的plain old Java interface。
到目前為止,我們已經(jīng)看到一些處理查詢和更新的示例數(shù)據(jù)訪問代碼,那么持久化類又是什么呢?想想POJO和元數(shù)據(jù)。但是,持久化POJO需要遵循許多規(guī)則,或者說是設(shè)計模式。這些規(guī)則部分由JPA指定,而其他的是根據(jù)整個應用架構(gòu)的要求而產(chǎn)生的,正如我們下面將看到的。
首先是一條關(guān)于元數(shù)據(jù)的注意事項。JPA為開發(fā)人員提供了幾種選項來指定與持久化和對象-關(guān)系映射相關(guān)的JPA元數(shù)據(jù):在外部XML文件中指定、在Java 5.0注釋中指定,或者使用這二者的組合。病歷應用程序使用JPA元數(shù)據(jù)的Java注釋。這樣做的好處是:元數(shù)據(jù)與應用該元數(shù)據(jù)的Java類、字段和方法一起配置,這樣可以幫助理解。如果想使元數(shù)據(jù)與代碼分離,可以使用XML來代替。
讓我們考察一下Patient類及其字段聲明,看看示例注釋JPA類是什么樣的:
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類使用Entity注釋進行注釋。這是使用注釋的所有持久化類必須遵循的。Entity注釋的存在告訴持久化引擎該類是JPA實體。通常不要求Entity類是公有的,但是MedRec的持久化類需要是公有的,以便Web和Web服務層可以使用它們。
實體類還需要實現(xiàn)Serializable,但是由于Patient類由MedRec struts操作置于HTTP會話中,所以它需要是可串行化的。這樣應用程序可以在集群環(huán)境中工作,其中會話狀態(tài)可以在集群中各節(jié)點之間進行串行化。關(guān)于實體類的其他要求是:它必須是頂級類且不是final。請參閱JPA規(guī)范(JSR220)以獲得完整的規(guī)則列表。
JPA實現(xiàn)需要在運行時讀取和寫入實體類的狀態(tài)(持久化字段)。JPA規(guī)范允許實現(xiàn)或者通過直接訪問實體字段的方式或者通過調(diào)用JavaBean風格的accessor方法(getter/setter)的方式來進行此操作。使用的訪問方式由持久化注釋的位置決定。例如,如果注釋位于類的字段上,則使用字段訪問。
您可能要問:為什么JPA支持字段和方法兩種訪問方式呢?答案是靈活性。一些類在它們的公有accessor方法中執(zhí)行驗證。如果由于數(shù)據(jù)庫中的值溢出而拋出異常,這可能導致JPA實現(xiàn)出現(xiàn)問題。如果類在其setter中執(zhí)行驗證,則最好對字段進行注釋。另一方面,如果希望支持虛擬持久化屬性之類的功能,那么就注釋accessor方法。
在JPA實體中,所有未使用Transient注釋(由JPA定義)進行注釋的非瞬態(tài)字段都是持久化字段。“持久化”意味著字段被映射到數(shù)據(jù)庫中的列。請注意,Patient類中的一些持久化字段沒有注釋。這是由于JPA定義的默認值(如默認的列名稱)對于這些字段來說是正確的。如果字段名稱與其映射的數(shù)據(jù)庫列名稱不同,則必須使用Column注釋來為數(shù)據(jù)庫列指定一個不同的列名稱。
每個實體必須具有主鍵。JPA支持簡單(單字段)和復合(多字段)的主鍵。如果可以,最好一直使用簡單主鍵,并讓數(shù)據(jù)庫生成主鍵值。這是因為,一旦對象被保存,JPA就不允許更改主鍵值。由于此原因,病歷應用程序中的大多數(shù)持久化類(包括Patient類)都使用簡單的生成主鍵。關(guān)于具有復合主鍵的類的示例,請參考Group類。Id注釋用于標記主鍵字段或標識字段。
GeneratedValue注釋用于表示在插入新的Patient時主鍵值是由數(shù)據(jù)庫生成的。數(shù)據(jù)庫可以以數(shù)種方式生成主鍵值,可使用strategy屬性來選擇其中一個。Patient類使用標識策略,這意味著id字段應被映射到數(shù)據(jù)庫自動-增量或標識列。
在使用標識策略時,需要注意這對類的equals和hashcode方法所產(chǎn)生的影響。例如,如果equals方法比較id字段的值,請確保使用對象的方式不會導致equals方法在進行數(shù)據(jù)庫插入操作之前(通常在事務提交時)被調(diào)用,因為主鍵值直到這時才被分配。避免這種情況的一種方法是,(如果類有的話)在equals方法中使用自然鍵而不是生成鍵。例如,Patient Social Security number是可用于Patient equals方法中的很好的自然鍵。
大多數(shù)實體具有與域模型中其他的單個或多個實體的關(guān)系,Patient類也不例外。圖2展示了Patient類及其關(guān)系圖。
JPA使用一對一、多對一、一對多和多對多注釋來指定關(guān)系及其基數(shù)。每個關(guān)系字段必須使用這些注釋中的一個進行注釋。例如,Patient具有與Address的多對一關(guān)系。這意味著許多病人可能具有相同的地址。Patient與User類具有一對一關(guān)系,該類包含病人的用戶名和密碼信息。這意味著每個Patient與單個的User相關(guān)。
Patient具有與Prescriptions和Records的一對多關(guān)系,因為特定的病人可以有多個處方,并可能多次看醫(yī)生(這將導致創(chuàng)建病歷)。相反,每個Prescription或Record只可以與一個Patient關(guān)聯(lián)。
JPA關(guān)系的一個有趣特性是操作的級聯(lián)。您可能已經(jīng)熟悉了大多數(shù)關(guān)系數(shù)據(jù)庫所支持的“級聯(lián)刪除”特性。JPA采用了類似于刪除的級聯(lián)操作的理念,并將其應用于其他操作(例如插入和更新)。Patient類將插入和關(guān)系操作級聯(lián)到相關(guān)的Address和User。在JPA中插入被視為持久化,因為數(shù)據(jù)庫插入是在對對象持久化時執(zhí)行的。更新被視作合并,因為更新將對象中的更改合并到當前JPA持久化上下文中。這看上去有些混亂,但是請別擔心;只需記?。涸谙M乱呀?jīng)被保存的對象時,要調(diào)用合并;當要在數(shù)據(jù)庫中插入新的持久化對象時,要調(diào)用持久化。
確定是否對特定操作(例如持久化、合并或移除)進行級聯(lián)是需要技巧的。在Patient類的情況中,持久化被級聯(lián)到Address和User,因為應用程序也需要在插入Patient的地方插入新的Address和User。類似地,在更新Patient的時候,Address和User可能也需要更新。Prescriptions和Records則不同。應用程序具有顯式持久化(以及更新)Prescription或Record的操作,所以不需要將這些操作從Patient級聯(lián)到這些類。
您可能還記得前一節(jié)中我們曾說多個Patient被允許引用相同的Address——從Patient到Address的關(guān)系是多對一。但是,我們剛剛也說了Patient將插入級聯(lián)到Address,這導致為數(shù)據(jù)庫中的每位病人創(chuàng)建一個新地址。這是怎么回事呢?您在這里看到的是在Patient和Address之間使用生成的主鍵和級聯(lián)插入的有趣效果。因為用于地址的主鍵由數(shù)據(jù)庫自動生成,所以每個Address將有惟一的主鍵,指向相同實際地址的兩個不同Address對象有可能都被插入到數(shù)據(jù)庫中。這對于許多應用程序(包括MedRec)來說沒什么問題,但是如果您希望阻止復制這樣的地址,則DAO需要執(zhí)行一些附加的邏輯。
JPA將MedRec的實體類映射到數(shù)據(jù)庫模式,而WebLogic Server 9.1 Web services實現(xiàn)將其映射到XML,這引發(fā)了一些影響實體類設(shè)計的其他問題。
JPA支持單向和雙向關(guān)系,但是如果考察MedRec中的實體類,會看到所有關(guān)系都是單向的。這是必要的,因為Web services綁定不支持雙向關(guān)系。
由于JPA要求Java Collection類型(如java.util.Set)用于多值關(guān)系字段,這引發(fā)了另一個問題。實踐表明9.1中的Web services不支持Collection類型。那么我們?nèi)绾谓鉀Q這一問題呢?
不知您是否還記得,對象-關(guān)系映射可以在實體的字段或方法上定義。實踐表明,JPA的此靈活性非常重要。另一方面,Web services使用類的公有屬性accessor方法(getXXX()方法)定義被封送為XML的屬性。通過使用字段定義持久化映射以及由類上的方法定義XML綁定,我們能夠?qū)蓚€映射分離!多值關(guān)系為了持久化在內(nèi)部使用Set,但是可以通過屬性訪問方法將其暴露為XML綁定能夠處理的數(shù)組。
為了具體說明,下面是一個來自Record類的執(zhí)行此轉(zhuǎn)換的屬性訪問方法示例:
public Prescription[] getPrescriptions() {if (prescriptions == null)return null;return (Prescription[]) prescriptions.toArray(newPrescription[prescriptions.size()]);}
此方法在內(nèi)部訪問具有java.util.Set類型的持久化處方字段。此Set被轉(zhuǎn)換為Web services實現(xiàn)能夠處理的數(shù)組。關(guān)于Web services集成的體驗使我們對JPA及其與現(xiàn)有應用程序和基礎(chǔ)架構(gòu)集成的能力印象深刻。
現(xiàn)在對持久化類的討論就結(jié)束了。看起來似乎需要遵循許多規(guī)則和限制,但事實上大多數(shù)JPA只是將好的編碼實踐具體化了,您可能會發(fā)現(xiàn)您沒有過多考慮就在遵循它們。
現(xiàn)在我們看到了如何使用Spring 2.0和JPA實現(xiàn)數(shù)據(jù)訪問對象,接下來讓我們快速考察一下使用DAO的MedRec服務對象。以下的代碼示例顯示了PatientServiceImpl類及其業(yè)務方法之一,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接口自我注冊時會調(diào)用processNewRegistration方法。它將保存新病人注冊信息的Registration對象作為參數(shù)。ProcessNewRegistration首先從Registration對象獲取Patient和User,并初始化它們之間的關(guān)系。User對象包含病人的用戶名和密碼信息,還保存被設(shè)置為USER_NEW的病人狀態(tài),因為這是位必須由管理員批準的新病人。然后調(diào)用Patient DAO將新的病人插入到數(shù)據(jù)庫。
針對MedRec服務對象對Spring聲明式事務進行配置,以便在調(diào)用業(yè)務方法(如processNewRegistration)之前由Spring啟動事務,并在此方法完成時提交該事務。這確保了DAO作為可自動提交的現(xiàn)有事務的一部分而運行。還請注意Spring所使用的、將對DAO對象的引用注入服務bean中的setPatientDao方法。
到目前為止,我們已經(jīng)考察了一些構(gòu)成此版本的病歷應用程序的Java代碼。本節(jié)將為您介紹Spring和Kodo要求的外部配置。我們從Spring開始。
我們使用一組模塊化XML配置文件來配置Spring ApplicationContext。包含數(shù)據(jù)訪問對象配置的配置文件是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具有需要被注入的單一依賴,即JPA EntityManagerFactory。DAO使用此EntityManagerFactory創(chuàng)建執(zhí)行實際持久化工作的JPA EntityManagers。這里使用Spring自動綁定特性完成綁定,這就是為什么我們在XML中看不到對EntityManagerFactory的顯式依賴。自動綁定特性自動將DAO綁定到基于DAO的EntityManagerFactory屬性(繼承自所有MedRec DAO類對其進行擴展的Spring JpaDaoSupport類)類型的EntityManagerFactory。
以下代碼是聲明創(chuàng)建EntityManagerFactory的Spring工廠bean的片段。由于這里討論了兩個工廠,可能聽起來有些混亂,但是請記?。篠pring工廠bean的目的就是提供一個中間層,以便允許我們執(zhí)行某些自定義代碼以創(chuàng)建EntityManagerFactory。此EntityManagerFactory是DAO對象惟一知道或關(guān)心的工廠。
<bean id="entityManagerFactory"class="com.bea.medrec.utils.KodoEntityManagerFactoryBean"><property name="jndiName"><value>kodo-jpa</value></property></bean>
因為在撰寫本文的時候,WebLogic Server不支持與JPA的標準集成,所以EntityManagerFactory通過使用自定義工廠bean而創(chuàng)建。EntityManagerFactory需要知道在JNDI中的什么地方查找Kodo資源適配器。該信息被配置為Spring工廠bean上的屬性,后者將其傳送到EntityManagerFactory。請記住,工廠bean的任務是創(chuàng)建EntityManagerFactory事件并將其返回到Spring,以使Spring能夠?qū)ntityManagerFactory注入DAO bean。用于創(chuà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是一個相當簡單的Spring工廠bean。在運行時,Spring首先調(diào)用默認的構(gòu)造函數(shù)創(chuàng)建bean實例。然后,Spring調(diào)用setJndiName()方法設(shè)置Kodo資源適配器的JNDI名稱。一旦所有屬性被注入,Spring就調(diào)用創(chuàng)建EntityManagerFactory實例的afterPropertiesSet()。最后,Spring調(diào)用getObject()來獲取將被注入DAO的EntityManagerFactory。
我們已經(jīng)看到了如何使用Kodo EntityManagerFactory(DAO使用它來完成所有持久化工作)注入Spring DAO。然后Kodo EntityManagerFactory通過JNDI連接到Kodo資源適配器。接下來,我們將考察Kodo資源適配器的配置,但是首先需要知道一點關(guān)于Spring配置的其他內(nèi)容,這將有助于將所有事情聯(lián)系起來。
Spring需要被告知使用WebLogic JTA事務管理器來啟動和提交事務。此操作在以下的XML片段中完成:
<bean id="transactionManager"class="org.springframework.transaction.jta.WebLogicJtaTransactionManager"><property name="transactionManagerName"value="javax.transaction.TransactionManager"/></bean>
這里要明白一件重要的事情,即,需要專門告訴Spring將事務工作委托給WebLogic JTA實現(xiàn)。稍后,我們將看到Kodo也被告知在其配置中使用JTA事務。這將安排好事情以便MedRec服務對象啟動WebLogic JTA事務,然后在此事務中調(diào)用DAO。隨后DAO調(diào)用Kodo,它作為現(xiàn)有WebLogic JTA事務的一部分執(zhí)行數(shù)據(jù)庫讀出和寫入。
我們使用Kodo 4.0 EA4,它是Kodo的JPA實現(xiàn)的早期訪問版本。在撰寫本文的時候,定義JPA的EJB 3.0規(guī)范尚未完成,所以Kodo支持JPA的pre-final版本。Kodo作為資源適配器被部署到WebLogic Server。此資源適配器在JNDI中進行注冊。資源適配器的配置在標準ra.xml描述符文件中進行,它位于資源適配器RAR文件的META-INF目錄中。
如果考察ra.xml文件,會看到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數(shù)據(jù)源的JNDI位置,以便它能夠與數(shù)據(jù)庫交互。事實上,Kodo需要知道兩個數(shù)據(jù)源:一個數(shù)據(jù)源用于處理事務工作,另一個用于非事務工作,因為Kodo有時訪問數(shù)據(jù)庫執(zhí)行不屬于全局事務的工作。您可能已經(jīng)猜到了,ConnectionFactoryName和ConnectionFactory2Name屬性正是用于此目的。每個屬性的值是WebLogic數(shù)據(jù)源的JNDI名稱。請確保ConnectionFactory2Name引用的數(shù)據(jù)源不將其連接加入全局JTA事務。
Kodo還需要知道此應用程序是否正在使用JTA管理的事務或本地事務。因為控制事務的MedRec服務bean被配置為使用JTA,所以我們將TransactionMode屬性值設(shè)為托管。此外,還需要在資源適配器文件中配置Kodo許可,并列出應用程序?qū)⑹褂玫某志没?。用于此用途的屬性應該是自解釋的?/p>
如果您曾經(jīng)考察過JPA規(guī)范,您可能聽說過persistence.xml文件。這是包含與JPA持久化相關(guān)的元數(shù)據(jù)的標準配置文件。Kodo還支持persistence.xml文件。然而,在應用服務器環(huán)境中使用Kodo的當前早期訪問版本時,persistence.xml文件僅包含資源適配器配置中指定的信息的子集,且只能通過工具(例如Kodo增強程序)使用。
以下下載包括本文所討論的MedRec版本的所有Java和其他源文件,以及Spring 2.0和Kodo二進制文件及其依賴性:medrec-spring-jpa.zip。您大概需要27MB的磁盤空間來存放下載文件。
本文詳細考察了對Spring 2.0中的新增JPA支持的使用,該特性被用于重新實現(xiàn)MedRec示例應用程序的數(shù)據(jù)訪問層。希望本文為您提供了開始使用這些新API所需的信息。如果您希望進行更深入地研究,我們建議您考察本文所包含的實際MedRec代碼。示例應用程序代碼演示了如何以集成方式協(xié)同使用WebLogic 9.1、Spring 2.0和Kodo JPA。
我們已經(jīng)看到新的JPA API易用、直觀且具有靈活性。Java注釋在降低指定數(shù)據(jù)庫映射所需的元數(shù)據(jù)數(shù)量方面表現(xiàn)突出。雖然JPA是新增的,但是其強大功能足以允許我們繼續(xù)在持久化層、Web層和Web服務層中的應用程序中使用域模型類。此重用大大簡化了應用架構(gòu),并顯著降低了開發(fā)人員需要編寫的Java類的數(shù)量。
而Spring 2.0方面提供了一個利用JPA創(chuàng)建數(shù)據(jù)訪問對象的優(yōu)秀工具。Spring的數(shù)據(jù)訪問架構(gòu)使得我們可以輕松地在不同持久化技術(shù)之間進行轉(zhuǎn)換,而無需重寫其他應用程序代碼。