當(dāng)前位置:工程項目OA系統(tǒng) > 泛普各地 > 江西OA系統(tǒng) > 南昌OA系統(tǒng) > 南昌OA信息化
用EJB 3.0簡化企業(yè)Java開發(fā)
在分為兩部分的這篇文章中,你將了解到如何使用EJB 3.0中的普通Java對象(POJO)編程模型來開發(fā)更簡單、更健壯的企業(yè)Java應(yīng)用程序。可運行的示例代碼演示了EJB 3.0應(yīng)用程序中的重要元素。本文將在上篇中闡述如何使用EJB 3.0注釋來開發(fā)松散耦合的POJO應(yīng)用程序、為可管理的POJO提供容器服務(wù)。將在下篇中討論EJB 3.0實體bean如何利用POJO和注釋來大大簡化數(shù)據(jù)模型及后端的關(guān)系數(shù)據(jù)庫。
上篇:使用注釋開發(fā)POJO服務(wù)
對開發(fā)服務(wù)器端應(yīng)用程序而言,Java企業(yè)版本即Java EE(以前叫J2EE)是一個功能強大、但又過于復(fù)雜的平臺。很早以來,過于復(fù)雜歷來被認為是阻礙人們采用Java EE的一個重要因素。
但在過去的三年,Java開放源代碼社區(qū)、Java社區(qū)組織(JCP)以及主要的Java EE開發(fā)商都在致力于簡化Java EE。譬如說,實際的應(yīng)用程序使用新的設(shè)計范例來簡化Java EE的開發(fā),譬如普通Java對象(POJO)服務(wù)、服務(wù)攔截器和依賴注入。而諸多新的工具和框架也得到了廣泛采用,用于同樣的目的,譬如Hibernate、面向方面編程(AOP)、Struts、XDoclet和Spring。
這些模式和工具讓剛?cè)腴T的開發(fā)人員更容易上手,同時提高了經(jīng)驗豐富的Java開發(fā)人員的生產(chǎn)力,目前它們正在被JCP集成到下一代Java EE標(biāo)準(zhǔn)(即EJB 3.0)當(dāng)中。Java開發(fā)人員Raghu Kodali最近開展的一項調(diào)查表明,把Sun的Java EE示例應(yīng)用程序RosterApp從EJB 2.1移植到EJB 3.0可以減少50%以上的代碼。
Java注釋是EJB3.0的重要特性,它把POJO服務(wù)、POJO持久性和依賴注入聯(lián)系起來,成為完整的企業(yè)中間件解決方案。本文使用了一個示例應(yīng)用程序:JBoss EJB 3.0 TrailBlazer,以演示開發(fā)添加注釋的輕便型EJB 3.0 POJO應(yīng)用程序。TrailBlazer應(yīng)用程序多次使用EJB 3.0中的不同工具和API,實現(xiàn)了一個投資計算器。示例應(yīng)用程序在JBoss 應(yīng)用服務(wù)器4.0.3里面以非傳統(tǒng)方式運行,完全符合最新的EJB 3.0規(guī)范(公眾預(yù)覽版)。
EJB 3.0的注釋驅(qū)動編程模型
從開發(fā)人員的角度來看,EJB 3.0廣泛使用Java注釋。注釋有兩個重要優(yōu)點:它們?nèi)〈诉^多的XML配置文件,而且不需要嚴格的組件模型。
注釋與XML
基于XML的部署描述符和注釋都可以用來配置Java EE應(yīng)用程序中的服務(wù)相關(guān)屬性。兩者的區(qū)別在于:XML文件與代碼分開處理(往往在運行時);而注釋與代碼一起編譯,而且由編譯器進行檢查。這對開發(fā)人員產(chǎn)生了以下這些重要影響:
● 冗長性:XML配置文件以冗長出名。為了配置代碼,XML文件必須從代碼地方復(fù)制許多信息,譬如類名稱和方法名稱。另一方面,Java注釋卻是代碼的一部分,不需要另外引用代碼,就可以指定配置信息。
● 健壯性:XML配置文件中的復(fù)制代碼信息帶來了多個潛在故障點。譬如說,如果拼錯了XML文件中的方法名稱,應(yīng)用程序會在運行時出錯。換句話說,XML配置文件不如注釋來得健壯。注釋可以由編譯器來檢查,同代碼的其余部分一起處理。
● 靈活性:因為XML文件與代碼分開處理,所以基于XML的配置信息不是“硬編碼”的,以后可以改動。部署時間的靈活性對系統(tǒng)管理員來說是一項很好的特性。
注釋使用簡單,足以滿足大多數(shù)應(yīng)用程序的要求。XML文件比較復(fù)雜,可用來處理更高級的問題。EJB 3.0允許通過注釋來配置大多數(shù)應(yīng)用程序的設(shè)置。EJB 3.0還支持XML文件用于取消默認的注釋值、配置外部資源(如數(shù)據(jù)庫連接)。
POJO與嚴格組件
除了取代及簡化XML描述符外,注釋還可以讓我們棄用曾困擾EJB 1.x和EJB 2.x的嚴格的組件模型。
EJB 組件是容器管理的對象。容器在運行時操縱bean實例的行為和內(nèi)部狀態(tài)。為了讓這種行為出現(xiàn),EJB 2.1規(guī)范定義了bean必須遵守的嚴格的組件模型。每個EJB類必須從為容器提供回調(diào)鉤子(callback hook)的某個抽象類繼承而來。因為Java只支持單一繼承,嚴格的組件模型就限制了開發(fā)人員使用EJB組件創(chuàng)建復(fù)雜對象結(jié)構(gòu)的能力。讀者會在本文下篇分看到,如果映射實體bean中復(fù)雜的應(yīng)用程序數(shù)據(jù),這更是個問題。
在EJB 3.0中,所有容器服務(wù)都可以通過注釋進行配置,并提供給應(yīng)用程序里面的任何POJO。大多數(shù)情況下,不需要特殊的組件類。
開發(fā)松散耦合的服務(wù)對象
Java EE等企業(yè)中間件的最重要的好處之一就是,讓開發(fā)人員可以使用松散耦合的組件來開發(fā)應(yīng)用程序。這些組件僅僅通過已發(fā)布的業(yè)務(wù)接口來進行耦合。因此,可在不改變應(yīng)用程序其余部分的情況下,改變組件實現(xiàn)類。這樣使應(yīng)用程序更健壯、更容易測試,而且更容易移植。EJB 3.0簡化了在POJO中構(gòu)建松散耦合的業(yè)務(wù)組件。
會話bean
在EJB 3.0應(yīng)用程序中,松散耦合的服務(wù)組件通常作為會話bean來實現(xiàn)。會話bean要有一個接口(即業(yè)務(wù)接口),那樣其他應(yīng)用程序的組件就可以通過它使用其服務(wù)。下面的代碼為我們的示例投資計算器服務(wù)提供了業(yè)務(wù)接口。根據(jù)投資者開始投資時及終止投資時的年齡、基金增長率及每月儲蓄額,它只有一個方法來計算總的投資回報。
public interface Calculator {
public double calculate (int start, int end, double growthrate, double saving); }
會話bean類僅僅實現(xiàn)了業(yè)務(wù)接口。必須通過為其添加無狀態(tài)或者有狀態(tài)的注釋,告訴EJB 3.0容器這個POJO類是會話bean。有狀態(tài)的會話bean可以在幾個不同的服務(wù)請求期間保持客戶端狀態(tài)。與之相反,無狀態(tài)的會話bean的請求每次都是由隨機的會話bean實例來處理。其行為與原來EJB 2.1中的有狀態(tài)和無狀態(tài)的會話bean的行為相一致。EJB 3.0容器計算出什么時候為bean對象創(chuàng)建實例,然后通過業(yè)務(wù)接口來提供。下面是會話bean實現(xiàn)類的代碼:
@Stateless
public class CalculatorBean implements Calculator {
public double calculate (int start, int end, double growthrate, double saving) {
double tmp = Math.pow(1. + growthrate / 12., 12. * (end - start) + 1);
return saving * 12. * (tmp - 1) / growthrate; }
}
還可以為一個會話bean指定多個接口-一個用于本地客戶端,一個用于遠程客戶端。只要使用@Local和@Remote注釋,就可以區(qū)別接口。下面的代碼片斷顯示了CalculatorBean會話bean同時實現(xiàn)了本地接口和遠程接口。如果你沒有@Local和@Remote注釋,會話bean接口就是默認的本地接口。
@Stateless
@Local ({Calculator.class})
@Remote ({RemoteCalculator.class})
public class CalculatorBean implements Calculator, RemoteCalculator {
public double calculate (int start, int end, double growthrate, double saving) {
double tmp = Math.pow(1. + growthrate / 12., 12. * (end - start) + 1);
return saving * 12. * (tmp - 1) / growthrate; }
public String getServerInfo () {
return "This is the JBoss EJB 3.0 TrailBlazer"; }
}
會話bean用戶通過Java命令和目錄接口(JNDI)得到bean的存根對象。由容器提供的存根對象實現(xiàn)了會話bean的業(yè)務(wù)接口。針對存根對象的所有調(diào)用都被轉(zhuǎn)向容器,并針對可管理的bean實例進行調(diào)用。至于無狀態(tài)的會話bean,每次進行調(diào)用時,都能獲得新的存根對象。至于有狀態(tài)的會話bean,必須把存根對象緩存在客戶端上,那樣容器就知道以后每次調(diào)用時為你提供相同的的bean實例。下面的代碼片斷顯示如何調(diào)用會話bean。這里介紹獲得bean存根對象的一種更簡單的方法。
InitialContext ctx = new InitialContext();
cal = (Calculator) ctx.lookup(Calculator.class.getName());
double res = cal.calculate(start, end, growthrate, saving);
會話bean的生命周期管理
為了實現(xiàn)松散耦合,應(yīng)用程序把會話bean實例的創(chuàng)建、緩存、銷毀全部交給EJB 3.0容器(即反向控制設(shè)計模式)。而應(yīng)用程序只處理業(yè)務(wù)接口。
但如果應(yīng)用程序需要對會話對象實行粒度更細的控制,該如何呢?譬如說,應(yīng)用程序可能需要在容器創(chuàng)建會話bean時執(zhí)行數(shù)據(jù)庫初始化,或者在銷毀bean時需要關(guān)閉外部連接。只要在bean類中實現(xiàn)生命周期回調(diào)方法,就能實現(xiàn)這些操作。這些方法由容器在bean生命周期的不同階段(如bean創(chuàng)建和銷毀)進行調(diào)用。在EJB 3.0中,可以指定任何bean方法作為回調(diào),只要為其添加下列注釋。不像EJB 2.1里面,所有的回調(diào)方法都必須加以實現(xiàn),即便回調(diào)方法是空的;EJB 3.0 bean可以有好多回調(diào)方法,可以是任何方法名稱。
● @PostConstruct:bean實例創(chuàng)建后,容器立即調(diào)用添加了注釋的方法。這個注釋同時適用于有狀態(tài)和無狀態(tài)的會話bean。
● @PreDestroy:容器從對象池當(dāng)中銷毀閑置或者過期的bean實例之前,調(diào)用添加了注釋的方法。這個注釋同時適用于有狀態(tài)和無狀態(tài)的會話bean。
● @PrePassivate:如果某個有狀態(tài)的會話bean實例閑置時間過長,容器就會將它掛起(passivate),并把其狀態(tài)保存在緩存當(dāng)中。容器將bean實例掛起之前,調(diào)用由這個注釋作以標(biāo)記的方法。這個注釋適用于有狀態(tài)的會話bean。
● @PostActivate:如果客戶端再次使用已被掛起的的有狀態(tài)的會話bean時,新的實例被創(chuàng)建,bean狀態(tài)被恢復(fù)。如果被激活的bean實例準(zhǔn)備就緒,就調(diào)用由該注釋作以標(biāo)記的方法。這個注釋只適用于有狀態(tài)的會話bean。
● @Init:這個注釋為有狀態(tài)的會話bean指定了初始化方法。它有別于@PostConstruct注釋之處在于:在有狀態(tài)的會話bean中,可以用@Init對多個方法作以標(biāo)記。不過,每個bean實例只能有一個@Init方法被調(diào)用。EJB 3.0容器決定調(diào)用哪個@Init方法,具體取決于bean是如何創(chuàng)建的。@PostConstruct方法在@Init方法之后被調(diào)用。
生命周期方法的另一個有用注釋是@Remove,對有狀態(tài)的會話bean來說更是如此。應(yīng)用程序通過存根對象調(diào)用使用@Remove標(biāo)注的方法時,容器就知道在該方法執(zhí)行完畢后,把bean實例從對象池當(dāng)中移走。下面是這些生命周期方法注釋在CalculatorBean中的一個示例:
@Stateful
public class CalculatorBean implements Calculator, Serializable {
@PostConstruct
public void initialize () {
//初始化歷史記錄,并從數(shù)據(jù)庫中裝入必要數(shù)據(jù)。 }
@PreDestroy
public void exit () {
// 若有必要,把歷史記錄保存至數(shù)據(jù)庫中 }
@Remove
public void stopSession () {
// 調(diào)用該方法以通知容器,移除該bean實例、終止會話。方法體可以是空的。}
}
消息驅(qū)動的bean
會話bean服務(wù)通過同步方法調(diào)用來提供。另一種重要的松散耦合的服務(wù)就是,由入站消息觸發(fā)的異步服務(wù),入站消息包括電子郵件或者Java消息服務(wù)(JMS)消息。EJB 3.0消息驅(qū)動的bean(MDB)是為了處理基于消息的服務(wù)請求而設(shè)計的組件。
MDB類必須實現(xiàn)消息監(jiān)聽器(MessageListener)接口。當(dāng)容器檢測到該bean的消息后,就調(diào)用onMessage()方法,并把入站消息作為調(diào)用參數(shù)傳遞。MDB會決定在OnMessage()方法中如何處理消息??梢杂米⑨寔砼渲眠@個MDB監(jiān)控哪些消息隊列。MDB部署后,容器使用注釋里面指定的配置信息。在下面的示例中,當(dāng)容器檢測到queue/mdb JMS隊列中的入站消息后,就會調(diào)用CalculatorBean MDB。MDB會解析消息,并根據(jù)消息內(nèi)容執(zhí)行投資計算。
@MessageDriven(activateConfig =
{
@ActivationConfigProperty(propertyName="destinationType", ropertyValue="javax.jms.Queue"),
@ActivationConfigProperty(propertyName="destination", propertyValue="queue/mdb")
})
public class CalculatorBean implements MessageListener {
public void onMessage (Message msg) {
try {
TextMessage tmsg = (TextMessage) msg;
Timestamp sent = new Timestamp(tmsg.getLongProperty("sent"));
StringTokenizer st = new StringTokenizer(tmsg.getText(), ",");
int start = Integer.parseInt(st.nextToken());
int end = Integer.parseInt(st.nextToken());
double growthrate = Double.parseDouble(st.nextToken());
double saving = Double.parseDouble(st.nextToken());
double result = calculate (start, end, growthrate, saving);
RecordManager.addRecord (sent, result);
} catch (Exception e) {
e.printStackTrace (); }
}
}
依賴注入
在前面一節(jié)中,介紹了如何開發(fā)松散耦合的服務(wù)組件。然而,為了使用這些服務(wù)對象,你需要通過服務(wù)器的JNDI來查詢存根對象(用于會話bean)或者消息隊列(用于MDB)。JNDI查詢是把客戶端從實際實現(xiàn)的服務(wù)對象解除耦合的一個關(guān)鍵步驟。不過,基于字符串名的普通JNDI查詢并不方便。以下是幾個原因:
● 客戶端與服務(wù)端必須就基于字符串的名字達成一致。這不是由編譯器或者任何部署時間檢查所執(zhí)行的契約。
● 已獲取的服務(wù)對象在編譯時不進行檢查,可能會導(dǎo)致運行時出現(xiàn)數(shù)據(jù)類型轉(zhuǎn)換錯誤(casting error)。
● 應(yīng)用程序里面一再出現(xiàn)冗長的查詢代碼,該代碼有自己的try-catch代碼塊。
EJB 3.0采用了一種簡單、便利的方法,把解除耦合的服務(wù)對象和資源提供給任何POJO使用。你使用@EJB注釋,就可以把EJB存根對象注入到EJB 3.0容器管理的任何POJO中。如果對某字段變量標(biāo)以注釋,容器會在第一次訪問之前,為該變量賦予正確的值。下面的示例顯示了如何把CalculatorBean無狀態(tài)會話bean的存根對象注入到CalculatorMDB MDB類中。
public class CalculatorMDB implements MessageListener {
@EJB Calculator cal;
// 使用cal變量
// ... ... }
如果對某個屬性的JavaBean風(fēng)格的設(shè)置方法標(biāo)以注釋,屬性第一次使用之前,容器會自動用正確的參數(shù)調(diào)用屬性設(shè)置方法。下面的代碼片斷演示了工作過程:
public class CalculatorMDB implements MessageListener {
Calculator cal;
@EJB
public void setCal (Calculator cal) {
this.cal = cal; }
// 使用cal變量
// ... ... }
除@EJB注釋外,EJB 3.0還支持@Resource注釋注入來自JNDI的任何資源。下面的例子演示了如何注入服務(wù)器的默認的TimerService和SessionContext對象,并且演示了如何注入來自JNDI的命名數(shù)據(jù)庫和JMS資源。
@Resource
TimerService tms;
@Resource
SessionContext ctx;
@Resource (name="DefaultDS")
DataSource myDb;
@Resource (name="ConnectionFactory")
QueueConnectionFactory factory;
@Resource (name="queue/A")
Queue queue;
此外,你還可以把容器管理的持久性管理器(即實體管理器——類似Hibernate會話對象)注入到EJB 3.0 POJO中。
為POJO提供容器服務(wù)
除了管理松散耦合的服務(wù)對象的生命周期和訪問外,EJB 3.0容器還通過簡單的注釋為可管理的POJO提供運行時服務(wù)。
事務(wù)
最有用的容器服務(wù)可能就是事務(wù)服務(wù):萬一應(yīng)用程序出現(xiàn)錯誤或者異常,它可以保證數(shù)據(jù)庫的完整性。你只要為POJO方法添加注釋,即可聲明事務(wù)屬性。容器可以在適當(dāng)?shù)氖聞?wù)上下文中運行方法。譬如說,下面的代碼聲明:容器應(yīng)當(dāng)創(chuàng)建新的事務(wù)來運行updateExchangeRate()方法。如果該方法存在,事務(wù)就提交。實際上,從updateExchangeRate()里面被調(diào)用的所有方法也都在同樣的事務(wù)上下文中執(zhí)行,除非以其他方式進行顯示聲明。updateExchangeRate()方法中執(zhí)行的數(shù)據(jù)庫操作要么全部成功,要么全部失敗。
@Stateless
public class CalculatorBean implements Calculator {
// ... ...
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void updateExchangeRate (double newrate) throws Exception {
// 在循環(huán)中更新數(shù)據(jù)庫。
// ... ...
//循環(huán)中的操作必須全部成功,否則數(shù)據(jù)庫根本不更新。 }
}
安全
容器還能提供驗證用戶身份的安全服務(wù),并且可以根據(jù)用戶角色,限制對可管理的POJO的訪問。對每個POJO類而言,你可以使用@SecurityDomain注釋指定安全域,它能告訴容器到哪里去找密碼和用戶角色列表。JBoss里面的other域表明文件是類路徑中的users.propertes和roles.properties文件。然后,對于每個方法,你可以使用安全限制注釋來指定誰可以運行這個方法。譬如在下面例子中,容器對所有試圖執(zhí)行addFund()方法的用戶進行驗證,只允許角色是AdminUser的用戶才能實際運行。如果你沒有登錄,或者不是以管理員的身份登錄,就會引發(fā)安全異常。
@Stateless
@SecurityDomain("other")
public class CalculatorBean implements Calculator {
@RolesAllowed({"AdminUser"})
public void addFund (String name, double growthrate) {
// ... ... }
@RolesAllowed({"AdminUser"})
public void addInvestor (String name, int start, int end) {
// ... ... }
@PermitAll
public Collection < Fund > getFunds () {
// ... ... }
// ... ...
@RolesAllowed({"RegularUser"})
public double calculate (int fundId, int investorId, double saving) {
// ... ... }
}
通用攔截器
事務(wù)服務(wù)和安全服務(wù)都可以被看成是由容器管理的運行時攔截器。容器攔截來自EJB存根對象的方法調(diào)用后,為調(diào)用添加事務(wù)上下文或者安全限制。
在EJB 3.0中,你可以自己編寫攔截器來擴展容器服務(wù)。使用@AroundInvoke注釋,就可以把任何bean方法指定為在其他任何bean方法運行前后執(zhí)行的攔截器方法。在下面例子中,log()方法是分析及記錄其他bean方法的執(zhí)行時間的攔截器:
@Stateful
public class CalculatorBean implements Calculator {
//被“l(fā)og()”攔截的bean方法
// ... ...
@AroundInvoke
public Object log (InvocationContext ctx) throws Exception {
String className = ctx.getBean().getClass().getName();
String methodName = ctx.getMethod().getName();
String target = className + "." + methodName + "()";
long start = System.currentTimeMillis();
System.out.println ("Invoking " + target);
try {
return ctx.proceed();
} catch(Exception e) {
throw e;
} finally {
System.out.println("Exiting " + target);
cal.setTrace(cal.getTrace() + "" +"Exiting " + target);
long time = System.currentTimeMillis() - start;
System.out.println("This method takes " + time + "ms to execute");
}
}
}
下篇:可管理的POJO持久性
在Java虛擬機(JVM)里面,所有數(shù)據(jù)都被建模,并且被封裝在樹結(jié)構(gòu)的類和對象中。然而,在后端關(guān)系數(shù)據(jù)庫中,數(shù)據(jù)被建模成關(guān)系表,它們通過共享的鍵字段相互關(guān)聯(lián)起來。同一數(shù)據(jù)卻有兩個不同的視圖,這給企業(yè)Java的開發(fā)人員帶來了挑戰(zhàn):如果你要把數(shù)據(jù)保存到持久性數(shù)據(jù)存儲區(qū),或者從持久性數(shù)據(jù)存儲區(qū)獲取數(shù)據(jù),就必須在對象和關(guān)系表示之間來回轉(zhuǎn)換數(shù)據(jù),這個過程就叫作對象-關(guān)系映射(ORM)。在Java EE(Java企業(yè)版,以前叫J2EE)中,可以通過兩個方法來完成對象-關(guān)系映射。
● 人工方法:使用Java數(shù)據(jù)庫連接性(JDBC)直接處理持久性——這個簡單的解決方法適用于簡單的應(yīng)用程序。JDBC API的類緊密地按照關(guān)系數(shù)據(jù)庫里面的表、行和列進行建模。但必須在應(yīng)用程序的內(nèi)部對象模型和JDBC對象模型之間進行人工轉(zhuǎn)換,如果應(yīng)用程序的內(nèi)部模型已經(jīng)類似二維關(guān)系表,采用JDBC是最佳方法。
● 自動方法:可以把ORM任務(wù)交給框架去處理??蚣芡ǔL峁┝丝梢蕴幚砣魏螖?shù)據(jù)對象的API。通過這個API,可以保存、獲取及查找數(shù)據(jù)庫??蚣茉诤笈_完成對象-關(guān)系的轉(zhuǎn)換。因為針對特定關(guān)系的SQL查詢不適合對象接口,ORM框架通常定義了自己的查詢語言,可以為當(dāng)前的關(guān)系數(shù)據(jù)庫自動生成正確的SQL語句。對數(shù)據(jù)模型復(fù)雜的應(yīng)用程序而言,基于框架的方法可以節(jié)省許多時間,并且減少出錯。
ORM 框架
EJB 實體bean是Java EE中的“官方”O(jiān)RM解決方案。不過在EJB1.x和2.x中,實體bean使用起來非常困難,這有兩個原因:
● EJB 1.x和2.x實體bean必須符合嚴格的組件模型。每個bean類必須實現(xiàn)本地接口和業(yè)務(wù)接口。它們必須從某些抽象類繼承而來,還要實現(xiàn)所有方法,即便許多方法是空的。有了這樣一種嚴格的組件模型,就不可能利用EJB 1.x和2.x實體bean來構(gòu)建面向?qū)ο蟮臄?shù)據(jù)模型。
● EJB 1.x和2.x容器需要極其冗長的XML配置文件把實體bean映射到關(guān)系數(shù)據(jù)庫里面的表。那些文件非常冗長,還容易出錯。
簡而言之,EJB 1.x和2.x實體bean是一種設(shè)計拙劣的ORM框架,既滿足不了Java數(shù)據(jù)對象模型的需求,也滿足不了關(guān)系表數(shù)據(jù)模型的需求。出于對EJB 1.x和2.x實體bean的不滿,開發(fā)人員尋求ORM的其他方案。在實際環(huán)境中,采用開放源代碼的Hibernate(由JBoss公司開發(fā))和Oracle公司的TopLink是兩個最成功的Java ORM框架。Hibernate和TopLink都基于POJO:它們不依賴任何預(yù)定義的組件模型。相反,它們獲得POJO數(shù)據(jù)對象(采用簡單的JavaBean格式)后,會自動解釋如何把這些數(shù)據(jù)對象以及它們之間的關(guān)系映射到關(guān)系數(shù)據(jù)庫。通常,一個JavaBean類映射到一張數(shù)據(jù)庫表,類之間的關(guān)系通過表里面的外來鍵字段進行映射??梢栽诤唵?、直觀的XML配置文件里面指定ORM元數(shù)據(jù),譬如與JavaBean類相對應(yīng)的表名以及與屬性相對應(yīng)的列名??梢酝ㄟ^框架中的工具類(如Hibernate中的Session類)來操作這些POJO(譬如保存、獲取及查找)。
EJB 3.0建立在 Hibernate和TopLink的思想和成功這一基礎(chǔ)上。它為Java EE提供了標(biāo)準(zhǔn)的POJO ORM框架。另外,較之現(xiàn)有的POJO持久性解決方案,EJB 3.0有兩項重要創(chuàng)新:
● EJB 3.0讓開發(fā)人員可以直接在POJO代碼中注釋映射信息,而不是使用XML文件來指定ORM元數(shù)據(jù)。譬如說,你可以用注釋來指定與每個JavaBean屬性相對應(yīng)的關(guān)系列名。讀者會在本文后面看到更多的示例。注釋使得映射更直觀,也更容易維護。
● EJB 3.0為實體bean定義了新的存檔格式。每個存檔定義了持久性上下文,后端數(shù)據(jù)庫和ORM行為各使用獨立的一組配置。本文會在后面討論持久性上下文。
現(xiàn)在,我們不妨通過幾個簡單的示例來看一下EJB 3.0是如何實現(xiàn)POJO ORM的。
映射簡單對象
在EJB 3.0中,每個實體bean都是JavaBean樣式的簡單類。為了告訴EJB 3.0容器這個類應(yīng)當(dāng)進行映象以實現(xiàn)持久性,應(yīng)當(dāng)用@Entity來注釋這個類。
每個實體bean類映射到關(guān)系數(shù)據(jù)庫表。默認情況下,表名與類名相對應(yīng)。可以使用@Table注釋,為該類指定另一個表名。bean類的每個JavaBean屬性映射到表中的列。默認情況下,列名就是屬性名。可以通過為屬性的設(shè)置方法添加@Column注釋,來改變這種默認關(guān)系。下面是EJB 3.0實體bean類的簡單示例:
@Entity
// @Table (name="AlternativeTableName")
public class Person implements Serializable {
protected int id;
protected String name;
protected Date dateOfBirth;
public void setId (int id) {
this.id = id; }
@Id(generate = GeneratorType.AUTO)
public int getId () {
return id; }
public void setName (String name) {
this.name = name; }
// @Column (name="AlternativeColumnName")
public String getName () {
return name; }
public void setDateOfBirth (Date dateOfBirth) {
this.dateOfBirth = dateOfBirth; }
public Date getDateOfBirth () {
return dateOfBirth; }
}
容器把Person類映射到Person SQL數(shù)據(jù)庫表后,每個Person實例就是表中的一行數(shù)據(jù)。
映射簡單的JavaBean類很容易。但需要映射相互關(guān)聯(lián)的對象時,自動ORM框架的優(yōu)點才會真正體現(xiàn)出來。本文會在后面介紹EJB 3.0是如何處理對象關(guān)系的。
關(guān)系
在數(shù)據(jù)模型中,類與類之間通常有著關(guān)系。譬如,Person對象可以與Resume對象聯(lián)系起來,反之亦然(一對一關(guān)系);Person對象可以與多個CreditCard對象聯(lián)系起來,而CreditCard對象只與一個Person對象相對應(yīng)(一對多關(guān)系)。多個Person對象可以與一個Address對象聯(lián)系起來,而一個Address對象只對應(yīng)于一個Person對象(多對一關(guān)系)。
在對象模型中,對象引用負責(zé)處理這些關(guān)系。譬如說,Person對象可以有一個屬性(即字段)來引用Resume對象,還有另一個屬性是CreditCard對象的集合體。為了把對象之間的關(guān)系告訴EJB 3.0容器,只要在POJO中注釋這些JavaBean屬性。
@Entity
public class Person implements Serializable {
// ... ...
protected Resume resume;
protected CreditCard [] cards;
protected Address addr;
// ... ...
@OneToOne
public Resume getResume () {
return resume; }
// ... ...
@ManyToOne
// @JoinColumn (name="MyCustomId")
public Address getAddr () {
return addr; }
// ... ...
@OneToMany
public Collection < CreditCard > getCards () {
return cards; }
}
在關(guān)系數(shù)據(jù)庫中,這些關(guān)系由EJB 3.0容器使用外來鍵字段自動重新構(gòu)建。譬如說,Person表有一個外來鍵字段,里面包含了Resume表中相應(yīng)行的主鍵。運行時,EJB 3.0容器執(zhí)行一對一的關(guān)系:它保證了Resume鍵值對Person表中的每一行來說是惟一的。為了實現(xiàn)Resume表到Person表的雙向查詢,也可以在Resume表中定義Person屬性,并為其添加@OneToOne注釋。
Person表中還有一個外來鍵字段,里面包含了Address表中相應(yīng)行的主鍵。這種情況下,同一個Address主鍵可出現(xiàn)在多個Person行中,因為這是多對一的關(guān)系。至于一對多的關(guān)系,映射起來要復(fù)雜一點,因為外來鍵的列是在多對一表中的數(shù)據(jù)源里面定義的。所以在CreditCard類中,必須用@ManyToOne注釋來定義Person屬性。
上面討論的相互關(guān)系只是實體bean關(guān)系的一種類型,實體bean類之間的另一種重要關(guān)系是繼承。
繼承
面向?qū)ο笤O(shè)計的一個重要概念就是繼承。使用繼承,你可以為對象創(chuàng)建復(fù)雜的樹結(jié)構(gòu),而不需要重復(fù)代碼。譬如說,顧問(Consultant)是提供有償咨詢服務(wù)的人。因而在我們的數(shù)據(jù)模型中,Consultant類從具有另外收費屬性的Person類繼承而來。遺憾的是,關(guān)系數(shù)據(jù)庫里面沒有繼承這個概念。ORM框架主要依靠這兩種方法來模仿這種行為:
● 框架可以為每個類生成單獨的表。子類的表從超類的表當(dāng)中復(fù)制了所有的列。子類和超類的實例被保存在相應(yīng)的表中。
● 框架可以使用包含所有子類屬性列的一張表。兩種類的實例保存在同一張表中——超類對象的行把該類沒有的列里面的值設(shè)為空值。為了讓繼承映射更健壯,表還有“區(qū)別”列,它里面存放的標(biāo)記表明每行映射到哪個類。
EJB 3.0實體bean支持上述兩種映射策略,默認情況下采用一張表映射策略。只要注釋指明超類,即可指定繼承策略和區(qū)別列的名字。下面是Consultant類的示例,它從Person類繼承而來:
@Entity
@Inheritance(discriminatorValue="C")
@DiscriminatorColumn(name="person_type")
public class Consultant extends Person {
protected double rate;
public void setRate (double rate) {
this.rate = rate; }
public double getRate () {
return rate; }
}
在上面的例子中,容器使用默認策略將Consultant類映射到同一張表中的Person類。如果表中的person_type列的值為C,當(dāng)前行就代表Consultant對象。否則,當(dāng)前行代表普通的Person對象。
持久性檔案
鑒于數(shù)據(jù)模型有一組添加了注釋的EJB 3.0實體bean類,就可以把它們捆綁起來,部署到服務(wù)器環(huán)境中。EJB 3.0為實體bean定義了特殊的存檔文件格式,名為持久性存檔(文件后綴名為.par)。
.par文件是包括諸多實體bean類組成的一個jar文件,另外加上一個簡單的配置文件:META-INF/persistence.xml。persistence.xml文件定義了持久性上下文的名稱,它告訴EJB 3.0使用哪個后端數(shù)據(jù)庫(數(shù)據(jù)源)用于這一組實體bean。persistence.xml還包含了針對特定實現(xiàn)的配置屬性。譬如說,JBoss EJB 3.0在Hibernate 3.0上面實現(xiàn)。所以你可以傳遞persistence.xml文件中的任何Hibernate配置選項。下面是作為示例的persistence.xml文件,專門針對JBoss和Hibernate的配置屬性有關(guān)于SQL方言和二級緩存。
< entity-manager >
< name >cal< /name >
< jta-data-source >java:/DefaultDS< /jta-data-source >
< properties >
< property name="hibernate.dialect"
value="org.hibernate.dialect.MySQLDialect" />
< property name="hibernate.cache.provider_class"
value="org.jboss.ejb3.entity.TreeCacheProviderHook"/>
< property name="hibernate.treecache.mbean.object_name"
value="jboss.cache:service=EJB3EntityTreeCache"/>
< /properties >
< /entity-manager >
實體管理器
一旦部署了實體bean, 必須通過EJB 3.0實體管理器API來訪問及操縱它們。EJB 3.0容器為每個已部署的持久性上下文(即.par文件)提供了一個實體管理器對象??梢詮腅JB 3.0會話bean POJO,通過@PersistenceContext注釋注入實體管理器對象,并傳入上下文的名字。
@Stateless
public class ManagerBean implements Manager {
@PersistenceContext (unitName="cal")
protected EntityManager em;
// 使用“em”
// ... ...}
基本操作
為了創(chuàng)建新的數(shù)據(jù)對象,并把它保存到數(shù)據(jù)庫中,只要使用Java的new關(guān)鍵字來創(chuàng)建POJO,并把它傳遞給EntityManager.persist()方法。
Person p = new Person ();
p.setName ("A new baby");
p.setDateOfBirth (new Date ());
em.persist (p);
為了從數(shù)據(jù)庫中獲取對象,可以使用EJB 3.0查詢語言來搜索數(shù)據(jù)庫。下面的示例演示了如何讓Person數(shù)據(jù)庫表中的所有行作為Person Java對象的集合體來返回。
// 得到所有人
Collection < Person > persons = (Collection < Person >)em.createQuery("from Person p").getResultList();
可管理的POJO
由實體管理器保存及獲取的對象在持久性上下文中加以管理。這意味著,如果對象后來發(fā)生改變,這種改變會被自動檢測到,并且被賦予持久性、發(fā)送到數(shù)據(jù)庫中。在下面的示例中,我們更新了可管理的POJO的屬性。改變會被EJB 3.0容器自動檢測到,并發(fā)送到數(shù)據(jù)庫中。
Person p = em.find(Person.class, personId);
p.setName ("Another Name");
//當(dāng)前事務(wù)結(jié)束后,p被自動更新到數(shù)據(jù)庫中。
// 沒用額外的API調(diào)用。
因為EJB 3.0實體只是POJO,它們可以進行序列化處理,并通過網(wǎng)絡(luò)傳遞。如果某個對象不是由容器創(chuàng)建(譬如它從網(wǎng)絡(luò)連接傳遞過來,或者是遠程過程調(diào)用的返回值),持久性上下文就不會管理它。可以通過調(diào)用EntityManager.merge()方法,把一個非管理的POJO合并到持久性上下文中。下面是把經(jīng)過反序列化的POJO合并到當(dāng)前持久性上下文中的示例。
InputStream in;
// 初始化輸入流
Person p = Util.deserialize (in);
// ... ...
em.merge (p);
// p現(xiàn)在是個可管理的對象了。p的任何改變會被自動檢測到,并被賦予持久性。
p.setName ("Another Name");
數(shù)據(jù)庫同步
實體管理器對象用于會話bean時,它與服務(wù)器的事務(wù)上下文綁在一起。服務(wù)器的事務(wù)在提交時,實體管理器提交,并且把同步內(nèi)容發(fā)送給數(shù)據(jù)庫。在會話bean中,默認情況下,服務(wù)器的事務(wù)在調(diào)用堆棧末尾處提交。當(dāng)然,也可以通過注釋為每個業(yè)務(wù)方法指定詳細的事務(wù)屬性。下面的示例演示了如何為會話bean方法聲明新的事務(wù)。
@TransactionAttribute(TransactionAttributeType.REQUIRESNEW)
public void update () {
//用這個方法更新Person對象;該方法結(jié)束后,所有更新被提交,并被刷新到數(shù)據(jù)庫中。
}
如果需要在事務(wù)提交前把更新內(nèi)容刷新到數(shù)據(jù)庫中,可以通過顯式方式調(diào)用EntityManager.flush()方法?;蛘呖梢詾榉椒ㄌ砑覢FlushMode(FlushModeType.NEVER)注釋,那樣事務(wù)管理器不會在這個方法結(jié)束時(即事務(wù)結(jié)束時)把更新內(nèi)容刷新到數(shù)據(jù)庫中。這種情況下,可以手動刷新所有的數(shù)據(jù)庫更新,以實現(xiàn)最大程度的控制。
EJB 3.0 提供了一種簡單、有效的框架,用于把Java POJO映射到SQL數(shù)據(jù)庫中的關(guān)系表。它所采用的明智的默認映射策略基于Java類的結(jié)構(gòu)和命名。不過也可以改動任何默認設(shè)置,使用簡單的一組注釋來處理復(fù)雜的對象關(guān)系。
EJB 3.0實體管理器提供了簡單的API來查找及搜索來自數(shù)據(jù)庫的對象,并為其賦予持久性。每個實體管理器對象與一組映射的POJO相關(guān)聯(lián),并有自己的數(shù)據(jù)庫設(shè)置。它還可以自動與服務(wù)器的事務(wù)管理器綁在一起。
- 1存款準(zhǔn)備金率結(jié)構(gòu)性調(diào)整還有一個優(yōu)勢
- 2IE 6中存在的安全隱患
- 3OA選型中的舍與得
- 4OA軟件:無差異 不未來
- 5家庭網(wǎng)絡(luò)構(gòu)建實用攻略
- 62013年值得關(guān)注的五大技術(shù)趨勢 微軟的創(chuàng)新
- 7選OA系統(tǒng):小投入,大改變
- 8OA選型如挑衣衫:要好看的還是合適的
- 9簡述RAID級別
- 10OA觀察:決戰(zhàn)周邊化 殺回工作流
- 11無線局域網(wǎng)站點測量
- 12對數(shù)據(jù)倉庫探討
- 13動態(tài)VPN技術(shù)
- 142013年OA市場競爭熱點分析
- 15南昌OA系統(tǒng)采購申請單與ERP數(shù)據(jù)對接
- 16在線OA系統(tǒng)提高了考試管理的信息化水平
- 17企業(yè)服務(wù)器雙機容錯使用方法
- 18設(shè)置VLAN提高數(shù)據(jù)效率
- 19內(nèi)網(wǎng)安全的典型的誤區(qū)
- 20信息安全沒有在十全十美的方案
- 21網(wǎng)絡(luò)騙術(shù)分析
- 22SIP的生產(chǎn)力
- 23OA軟件為單位實現(xiàn)強而有力的企業(yè)管控
- 24安全的數(shù)據(jù)隔離與交換系統(tǒng)
- 25同步網(wǎng)狀網(wǎng)絡(luò)提供可伸縮性
- 26四招保障企業(yè)數(shù)據(jù)安全
- 27OA選型的三大陷阱
- 28網(wǎng)絡(luò)改造重視什么
- 29遠程訪問不再頭疼
- 3030秒清除Windows系統(tǒng)所有垃圾
成都公司:成都市成華區(qū)建設(shè)南路160號1層9號
重慶公司:重慶市江北區(qū)紅旗河溝華創(chuàng)商務(wù)大廈18樓