日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

Java 和微服務(wù)系列第 4 部分 處理數(shù)據(jù)

 xujin3 2018-08-18

在微服務(wù)系統(tǒng)中,具有混合持久性的環(huán)境時(shí),有必要讓數(shù)據(jù)處理保持可管理。為了解釋如何實(shí)現(xiàn)此目標(biāo),本章會(huì)簡(jiǎn)要介紹微服務(wù)在數(shù)據(jù)處理方面的特征,然后介紹如何使用基于 Java 的微服務(wù)實(shí)現(xiàn)此目的。

微服務(wù)特定于數(shù)據(jù)的特征

識(shí)別數(shù)據(jù)的一種方式是自上而下的方法,數(shù)據(jù)必須存儲(chǔ)在您的微服務(wù)的數(shù)據(jù)存儲(chǔ)中。從業(yè)務(wù)級(jí)別開(kāi)始建模您的數(shù)據(jù)。以下各節(jié)將介紹如何識(shí)別此數(shù)據(jù),如何處理它,以及如何與其他微服務(wù)的其他數(shù)據(jù)存儲(chǔ)共享它。有關(guān)自上而下方法的描述,請(qǐng)?jiān)L問(wèn)網(wǎng)站。

領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)導(dǎo)致產(chǎn)生各種實(shí)體

如果采用領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)中的方法,您會(huì)獲得以下對(duì)象(和其他對(duì)象):

  • 實(shí)體

'一種不通過(guò)屬性定義,而通過(guò)連續(xù)性和身份定義的對(duì)象。'

  • 價(jià)值對(duì)象

'一種包含屬性,但沒(méi)有概念實(shí)體的對(duì)象。它們應(yīng)視為不可變。'

  • 聚合

'一種通過(guò)根實(shí)體(也稱(chēng)為聚合根)綁定到一起的對(duì)象集合。聚合根通過(guò)禁止外部對(duì)象持有其成員的引用,確保在聚合內(nèi)執(zhí)行的更改的一致性。'

  • 存儲(chǔ)庫(kù)

'檢索領(lǐng)域?qū)ο蟮姆椒☉?yīng)委托給專(zhuān)門(mén)的存儲(chǔ)庫(kù)對(duì)象,以便能夠輕松地交換備選的存儲(chǔ)實(shí)現(xiàn)。'

這些引語(yǔ)出自來(lái)源:點(diǎn)此網(wǎng)址。

這些對(duì)象應(yīng)映射到您的持久存儲(chǔ)(僅在實(shí)體具有相同的生命周期時(shí)才能聚合它們)。這種領(lǐng)域模型是邏輯數(shù)據(jù)模型和物理數(shù)據(jù)模型的基礎(chǔ)。

有關(guān)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)術(shù)語(yǔ)的更多信息,請(qǐng)?jiān)L問(wèn)這個(gè)網(wǎng)站。

每個(gè)微服務(wù)都有單獨(dú)的數(shù)據(jù)存儲(chǔ)

每個(gè)微服務(wù)都應(yīng)擁有自己的數(shù)據(jù)存儲(chǔ)(如圖 1),而且與其他微服務(wù)及其數(shù)據(jù)分離。第二個(gè)微服務(wù)不能直接訪問(wèn)第一個(gè)微服務(wù)的數(shù)據(jù)存儲(chǔ)。

采用此特征的原因是:

  • 如果兩個(gè)微服務(wù)共享一個(gè)數(shù)據(jù)存儲(chǔ),它們將緊密耦合。如果更改一個(gè)微服務(wù)的數(shù)據(jù)存儲(chǔ)(比如表)的結(jié)構(gòu),則可能導(dǎo)致另一個(gè)微服務(wù)出現(xiàn)問(wèn)題。如果微服務(wù)以這種方式耦合,則必須協(xié)調(diào)新版本的部署,而這是我們必須避免的。

  • 每個(gè)微服務(wù)都應(yīng)該采用最能滿(mǎn)足其需求的數(shù)據(jù)庫(kù)類(lèi)型(混合持久性,請(qǐng)參閱小節(jié)'混合持久性'了解更多的細(xì)節(jié))。在選擇數(shù)據(jù)庫(kù)系統(tǒng)時(shí),不需要在不同微服務(wù)之間進(jìn)行權(quán)衡。這種配置會(huì)為每個(gè)微服務(wù)提供一個(gè)單獨(dú)的數(shù)據(jù)存儲(chǔ)。

  • 從性能角度講,每個(gè)微服務(wù)都有自己的數(shù)據(jù)存儲(chǔ)可能很有用,因?yàn)閿U(kuò)展會(huì)變得更容易。數(shù)據(jù)存儲(chǔ)可托管在自己的服務(wù)器上。

圖 1 每個(gè)微服務(wù)都有單獨(dú)的數(shù)據(jù)存儲(chǔ)

對(duì)于關(guān)系數(shù)據(jù)庫(kù),可通過(guò)以下方式之一實(shí)現(xiàn)數(shù)據(jù)存儲(chǔ)的分離:

  • 每個(gè)微服務(wù)一種模式

每個(gè)服務(wù)在數(shù)據(jù)庫(kù)中都有自己的模式(如圖 2)。其他服務(wù)可使用同一個(gè)數(shù)據(jù)庫(kù),但必須采用不同的模式??赏ㄟ^(guò)數(shù)據(jù)庫(kù)訪問(wèn)機(jī)制實(shí)現(xiàn)這種配置(為連接的數(shù)據(jù)庫(kù)用戶(hù)使用權(quán)限),因?yàn)槊媾R時(shí)間壓力的開(kāi)發(fā)人員傾向于使用快捷方式,而且能直接訪問(wèn)其他模式。

  • 每個(gè)微服務(wù)一個(gè)數(shù)據(jù)庫(kù)

每個(gè)微服務(wù)都可以有自己的數(shù)據(jù)庫(kù),但與其他微服務(wù)共享數(shù)據(jù)庫(kù)服務(wù)器(如圖 2)。使用不同的數(shù)據(jù)庫(kù),用戶(hù)可以連接到數(shù)據(jù)庫(kù)服務(wù)器,而且實(shí)現(xiàn)了很好的數(shù)據(jù)庫(kù)分離。

圖 2 每個(gè)微服務(wù)一種模式和每個(gè)微服務(wù)一個(gè)數(shù)據(jù)庫(kù)

  • 每個(gè)微服務(wù)一個(gè)數(shù)據(jù)庫(kù)服務(wù)器

這是最高程度的分離。舉例而言,在需要解決性能方面的問(wèn)題時(shí),這可能很有用(如圖 3)。

圖 3 每個(gè)微服務(wù)一個(gè)數(shù)據(jù)庫(kù)服務(wù)器

混合持久性

每個(gè)微服務(wù)應(yīng)使用自己的數(shù)據(jù)存儲(chǔ),這意味著該服務(wù)也可以采用不同的數(shù)據(jù)存儲(chǔ)技術(shù)。NoSQL 運(yùn)動(dòng)催生了許多新的數(shù)據(jù)存儲(chǔ)技術(shù),它們可與傳統(tǒng)關(guān)系數(shù)據(jù)庫(kù)一起使用。基于應(yīng)用程序必須滿(mǎn)足的需求,可選擇不同類(lèi)型的技術(shù)來(lái)存儲(chǔ)數(shù)據(jù)。一個(gè)應(yīng)用程序中擁有各種各樣的數(shù)據(jù)存儲(chǔ)技術(shù),這被稱(chēng)為混合持久性。

對(duì)于一些微服務(wù),最好將其數(shù)據(jù)存儲(chǔ)在關(guān)系數(shù)據(jù)庫(kù)中。對(duì)于其他具有不同類(lèi)型的數(shù)據(jù)(非結(jié)構(gòu)化、復(fù)雜和面向圖形的數(shù)據(jù))的服務(wù),可將它們的數(shù)據(jù)存儲(chǔ)在一些 NoSQL 數(shù)據(jù)庫(kù)中。

多語(yǔ)言編程這個(gè)詞的含義是,應(yīng)用程序應(yīng)該使用最適合它必須解決的挑戰(zhàn)的編程語(yǔ)言來(lái)編寫(xiě)。

Martin Fowler 網(wǎng)站提供了這兩個(gè)術(shù)語(yǔ)的更詳細(xì)的描述。

跨微服務(wù)的數(shù)據(jù)共享

在一些情況下,微服務(wù)應(yīng)用程序的用戶(hù)可能希望請(qǐng)求不同的服務(wù)所擁有的數(shù)據(jù)。例如,用戶(hù)可能想查看他的所有支付和支付的相應(yīng)狀態(tài)。因此,用戶(hù)需要在服務(wù)中查詢(xún)他的帳戶(hù),隨后在服務(wù)中查詢(xún)他的支付情況。微服務(wù)會(huì)使用數(shù)據(jù)庫(kù)合并數(shù)據(jù),不是一種最佳實(shí)踐。要處理此業(yè)務(wù)案例,您必須實(shí)現(xiàn)一個(gè)適配器服務(wù)(如圖 4),使用它查詢(xún)帳戶(hù)服務(wù)和支付服務(wù),然后將收集到的數(shù)據(jù)返回給用戶(hù)。適配器服務(wù)還負(fù)責(zé)對(duì)它收集到的數(shù)據(jù)執(zhí)行必要的數(shù)據(jù)轉(zhuǎn)換。

圖 4 適配器微服務(wù)

更改數(shù)據(jù)可能變得更復(fù)雜。在微服務(wù)系統(tǒng)中,一些業(yè)務(wù)事務(wù)可能涵蓋多個(gè)微服務(wù)。舉例而言,在零售店的微服務(wù)應(yīng)用程序中,可能有一個(gè)下單服務(wù)和一個(gè)支付服務(wù)。

因此,如果客戶(hù)想在您的商店購(gòu)買(mǎi)商品并付款,那么業(yè)務(wù)事務(wù)需要包含兩個(gè)微服務(wù)。每個(gè)微服務(wù)都有自己的數(shù)據(jù)存儲(chǔ),所以對(duì)于包含兩個(gè)或更多微服務(wù)的業(yè)務(wù)事務(wù),涉及到兩個(gè)或更多個(gè)數(shù)據(jù)存儲(chǔ)。本節(jié)將介紹如何實(shí)現(xiàn)這些業(yè)務(wù)事務(wù)。

事件驅(qū)動(dòng)架構(gòu)

您需要一種方法來(lái)確保包含兩個(gè)或多個(gè)微服務(wù)的業(yè)務(wù)事務(wù)中涉及的數(shù)據(jù)的一致性。一種方法是采用分布式事務(wù),但眾多原因表明,不應(yīng)在微服務(wù)應(yīng)用程序中采用這種方法。主要原因是,分布式事務(wù)中涉及的微服務(wù)是緊密耦合的。如果一個(gè)分布式事務(wù)涉及兩個(gè)微服務(wù),而且一個(gè)服務(wù)失敗或出現(xiàn)性能問(wèn)題,那么另一個(gè)服務(wù)必須等到超時(shí)后回滾事務(wù)。

讓一個(gè)業(yè)務(wù)事務(wù)涵蓋多個(gè)微服務(wù)的最佳方式是,使用事件驅(qū)動(dòng)架構(gòu)。要更改數(shù)據(jù),第一個(gè)服務(wù)負(fù)責(zé)更新它的數(shù)據(jù),并在同一個(gè)(內(nèi)部)事務(wù)中發(fā)布一個(gè)事件。第二個(gè)微服務(wù)(它已訂閱此事件)收到此事件,并對(duì)它的數(shù)據(jù)執(zhí)行更改。通過(guò)使用發(fā)布/訂閱通信模型,兩個(gè)微服務(wù)是松散耦合的。僅在它們交換的消息上存在耦合。該技術(shù)使微服務(wù)系統(tǒng)能在所有微服務(wù)之間保持?jǐn)?shù)據(jù)一致性,而無(wú)需使用分布式事務(wù)。

如果微服務(wù)應(yīng)用程序中的微服務(wù)相互發(fā)送許多消息,那么它們可能非常適合合并到一個(gè)服務(wù)中。但是需要謹(jǐn)慎一些,因?yàn)榇伺渲每赡芷茐奈⒎?wù)的領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)。執(zhí)行復(fù)雜更新(涵蓋多個(gè)服務(wù))的適配器服務(wù)也可以使用事件實(shí)現(xiàn)。

事件驅(qū)動(dòng)架構(gòu)的編程模型更為復(fù)雜,但 Java 有助于讓該復(fù)雜性處于可管理狀態(tài)下。

最終一致性

在事件驅(qū)動(dòng)架構(gòu)中,將消息發(fā)送給其他微服務(wù)會(huì)導(dǎo)致所謂的'最終一致性'問(wèn)題。這通常是以下情況下導(dǎo)致的運(yùn)行時(shí)問(wèn)題:微服務(wù) A 更改其數(shù)據(jù)存儲(chǔ)中的數(shù)據(jù),并在同一個(gè)內(nèi)部事務(wù)中向微服務(wù) B 發(fā)送一條消息。經(jīng)過(guò)較短時(shí)間后,微服務(wù) B 收到消息,并更改其數(shù)據(jù)存儲(chǔ)中的數(shù)據(jù)。在這個(gè)通常很短的時(shí)間段內(nèi),兩個(gè)數(shù)據(jù)存儲(chǔ)中的數(shù)據(jù)不一致。例如:服務(wù) A 更新其數(shù)據(jù)存儲(chǔ)中的訂單數(shù)據(jù),并向服務(wù) B 發(fā)送一條消息以進(jìn)行支付。在處理支付之前,有一個(gè)未支付的訂單。當(dāng)消息接收者無(wú)法處理消息時(shí),情況就會(huì)變?cè)?。在這種情況下,消息系統(tǒng)或接收微服務(wù)必須采取一些策略來(lái)處理此問(wèn)題。

在微服務(wù)應(yīng)用程序中,每個(gè)微服務(wù)擁有自己的數(shù)據(jù)庫(kù)。一次涵蓋多個(gè)微服務(wù)的業(yè)務(wù)事務(wù)會(huì)導(dǎo)致'最終一致性'問(wèn)題,因?yàn)榉植际绞聞?wù)會(huì)阻礙此問(wèn)題的解決。處理這種業(yè)務(wù)事務(wù)的一種方法如圖 5 所示。訂單微服務(wù)將訂單保存到它的數(shù)據(jù)存儲(chǔ)中,并將一個(gè)事件(例如 OrderCreated)發(fā)送給支付微服務(wù)。在訂單微服務(wù)未從支付微服務(wù)收到支付確認(rèn)期間,訂單處于未支付狀態(tài)。

支付服務(wù)訂閱了 OrderCreated 事件,所以它會(huì)處理該事件,并在其數(shù)據(jù)存儲(chǔ)中執(zhí)行支付。如果支付成功,它會(huì)發(fā)布一個(gè)被訂單微服務(wù)訂閱的 PaymentApproved 事件。處理 PaymentApproved 事件后,訂單狀態(tài)從'未支付'更改為'已批準(zhǔn)'。如果客戶(hù)查詢(xún)其訂單狀態(tài),就會(huì)獲得以下兩種響應(yīng)之一:訂單未支付或訂單已批準(zhǔn)。

圖 5 微服務(wù)之間的事件消息

在數(shù)據(jù)不可用的情況下,服務(wù)可能向客戶(hù)發(fā)送類(lèi)似下面這樣的消息:'很抱歉,請(qǐng)稍后重試'。

數(shù)據(jù)復(fù)制

數(shù)據(jù)存儲(chǔ)與從不同數(shù)據(jù)存儲(chǔ)獲取數(shù)據(jù)的需求分離,可能導(dǎo)致使用數(shù)據(jù)庫(kù)系統(tǒng)的數(shù)據(jù)復(fù)制機(jī)制可以解決問(wèn)題的想法。舉例而言,跟與多個(gè)微服務(wù)共享數(shù)據(jù)庫(kù)相比,使用數(shù)據(jù)庫(kù)觸發(fā)器、計(jì)時(shí)存儲(chǔ)過(guò)程或其他流程來(lái)解決此問(wèn)題同樣存在缺點(diǎn)。更改復(fù)制管道的一端的數(shù)據(jù)結(jié)構(gòu)會(huì)給復(fù)制流程帶來(lái)問(wèn)題。在部署服務(wù)的新版本時(shí),必須調(diào)整該流程。這也是一種緊密耦合形式,必須避免。

前面已經(jīng)提到,基于事件的處理可將兩個(gè)數(shù)據(jù)存儲(chǔ)分離。如果需要的話,處理事件的服務(wù)可執(zhí)行適當(dāng)?shù)臄?shù)據(jù)轉(zhuǎn)換,將數(shù)據(jù)存儲(chǔ)在它們自己的數(shù)據(jù)存儲(chǔ)中。

事件尋源和命令查詢(xún)職責(zé)分離

在事件驅(qū)動(dòng)架構(gòu)中,可以考慮命令查詢(xún)職責(zé)分離 (CQRS) 和事件尋源??山Y(jié)合這兩種架構(gòu)模式來(lái)處理流經(jīng)您的微服務(wù)應(yīng)用程序的事件。

CQRS 將對(duì)數(shù)據(jù)存儲(chǔ)的訪問(wèn)拆分為兩個(gè)不同部分:一部分包含讀取操作,另一部分包含寫(xiě)入操作。讀取操作不會(huì)更改系統(tǒng)的狀態(tài)。它們僅返回狀態(tài)。寫(xiě)入操作(命令)會(huì)更改系統(tǒng)的狀態(tài),但不會(huì)返回值。事件尋源存儲(chǔ)在數(shù)據(jù)發(fā)生更改時(shí)發(fā)生的事件序列。圖 6 顯示了一個(gè)使用 CQRS 的示例。

圖 6 使用 CQRS 的示例

如圖 6 所示,事件按順序存儲(chǔ)在事件存儲(chǔ)中。查詢(xún)模型中的數(shù)據(jù)與來(lái)自事件存儲(chǔ)的數(shù)據(jù)同步。要支持事件存儲(chǔ)或查詢(xún)模型,可以使用專(zhuān)門(mén)的系統(tǒng)(例如 Elastic Search)來(lái)支持微服務(wù)的查詢(xún)。

此架構(gòu)也可用于處理微服務(wù)應(yīng)用程序中的事件。

消息系統(tǒng)

可以使用消息系統(tǒng)或面向消息的中間件來(lái)支持事件驅(qū)動(dòng)架構(gòu)。

'面向消息的中間件 (MOM) 是一種軟件或硬件基礎(chǔ)架構(gòu),用于支持在分布式系統(tǒng)之間發(fā)送和接收消息。MOM 允許應(yīng)用程序模塊分布在異構(gòu)的平臺(tái)上,這降低了開(kāi)發(fā)涵蓋多個(gè)操作系統(tǒng)和網(wǎng)絡(luò)協(xié)議的應(yīng)用程序的復(fù)雜性。中間件創(chuàng)建了一個(gè)分布式通信層,這一層將應(yīng)用程序開(kāi)發(fā)人員與各種操作系統(tǒng)和網(wǎng)絡(luò)接口的細(xì)節(jié)隔離??缍鄻踊钠脚_(tái)和網(wǎng)絡(luò)進(jìn)行擴(kuò)展的 API 通常由 MOM 提供。MOM 提供的軟件元素位于客戶(hù)端/服務(wù)器架構(gòu)的所有通信組件中,通常支持客戶(hù)端與服務(wù)器應(yīng)用程序之間的異步調(diào)用。MOM 減少了客戶(hù)端/服務(wù)器機(jī)制的主從性質(zhì)的復(fù)雜性與應(yīng)用程序開(kāi)發(fā)人員的關(guān)聯(lián)。'

要與面向消息的中間件進(jìn)行通信,可以采用不同的協(xié)議。以下是最常用的協(xié)議:

  • 高級(jí)消息隊(duì)列協(xié)議 (AMQP)

AMQP'規(guī)定消息提供者和客戶(hù)端的行為,以便使來(lái)自不同供應(yīng)商的實(shí)現(xiàn)可互操作,就像 SMTP、HTTP、FTP 等創(chuàng)建可互操作的系統(tǒng)一樣。'

  • MQ 遙測(cè)傳輸 (MQTT)

MQTT 是'基于發(fā)布-訂閱的輕量型'消息協(xié)議,用在 TCP/IP 協(xié)議之上。它專(zhuān)為與需要'小代碼體積'或網(wǎng)絡(luò)帶寬有限的遠(yuǎn)程位置建立連接而設(shè)計(jì)。'它主要用在物聯(lián)網(wǎng) (IoT) 環(huán)境中。

在 Java 世界中,有一個(gè) API 可與面向消息的中間件通信:

Java Message Service (JMS),它包含在 Java EE 規(guī)范中。具體的版本是 JMS 2.0。

由于 JMS 的悠久歷史(可追溯到 2001 年),有許多 JMS 消息代理可用作 MOM 系統(tǒng)。不過(guò)也有一些實(shí)現(xiàn) AMQP 的消息系統(tǒng):

– RabbitMQ

– Apache Qpid

– Red Hat Enterprise MRG

所有這些系統(tǒng)都提供了 Java API,所以它們可用在基于 Java 的微服務(wù)中。

分布式事務(wù)

大部分(不是所有)消息系統(tǒng)都支持事務(wù)。在將消息發(fā)送給消息系統(tǒng)和更改事務(wù)數(shù)據(jù)存儲(chǔ)中的數(shù)據(jù)時(shí),也可以使用分布式事務(wù)。

可在微服務(wù)與它的后備存儲(chǔ)之間使用分布式事務(wù)和兩階段提交,但不要在微服務(wù)之間使用它們??紤]到微服務(wù)的獨(dú)立性,特定服務(wù)實(shí)例之間不得存在關(guān)聯(lián),而兩階段提交事務(wù)需要這種關(guān)聯(lián)。

對(duì)于涵蓋多個(gè)服務(wù)的交互,必須添加補(bǔ)救和調(diào)解邏輯來(lái)保證一致性。

Java 中的支持

在混合持久性環(huán)境中,用于實(shí)現(xiàn)微服務(wù)的編程語(yǔ)言必須處理不同的持久性技術(shù)。您的編程語(yǔ)言必須能支持每種持久保存數(shù)據(jù)的不同方式。作為編程語(yǔ)言,Java 擁有許多 API 和框架,可幫助開(kāi)發(fā)人員處理不同的持久性技術(shù)。

Java Persistence API

'Java Persistence API (JPA) 是一種在 Java 對(duì)象/類(lèi)與關(guān)系數(shù)據(jù)庫(kù)之間訪問(wèn)、持久化和管理數(shù)據(jù)的 Java 規(guī)范。EJB 3.0 規(guī)范中將 JPA 定義為取代 EJB 2 CMP Entity Beans 規(guī)范的一種規(guī)范。現(xiàn)在,在 Java 行業(yè)中,JPA 被視為對(duì)象關(guān)系映射 (ORM) 的標(biāo)準(zhǔn)行業(yè)方法。

JPA 本身只是一個(gè)規(guī)范,不是產(chǎn)品;它本身無(wú)法無(wú)法執(zhí)行持久化或任何其他操作。JPA 只是一組接口,需要一種實(shí)現(xiàn)。有開(kāi)源和商用的 JPA 實(shí)現(xiàn)可供選擇,而且所有 Java EE 5 應(yīng)用服務(wù)器都應(yīng)支持使用它。JPA 還需要一個(gè)數(shù)據(jù)庫(kù)來(lái)實(shí)現(xiàn)持久化。'

Java Enterprise Edition 7 (Java EE 7) 包含 Java Persistence 2.1 (JSR 338)。

發(fā)明 JPA 主要是為了得到一種對(duì)象關(guān)系映射器,以便在關(guān)系數(shù)據(jù)庫(kù)中持久保存 Java 對(duì)象。API 背后的實(shí)現(xiàn)(持久化提供程序)可由不同的開(kāi)源項(xiàng)目或供應(yīng)商實(shí)現(xiàn)。使用 JPA 的另一個(gè)好處是,您的持久化邏輯更容易移植。

JPA 定義了自己的查詢(xún)語(yǔ)言(Java 持久化查詢(xún)語(yǔ)言 (JPQL)),可為不同數(shù)據(jù)庫(kù)供應(yīng)商生成查詢(xún)。Java 類(lèi)被映射到數(shù)據(jù)庫(kù)中的表,也可以使用類(lèi)之間的關(guān)系來(lái)建立對(duì)應(yīng)的表之間的關(guān)系。可使用這些關(guān)系建立級(jí)聯(lián)操作,所以一個(gè)類(lèi)上的操作可能導(dǎo)致其他類(lèi)的數(shù)據(jù)上的操作。JPA 2.0 引入了 Criteria API,可幫助在運(yùn)行時(shí)和編譯時(shí)獲得正確的查詢(xún)結(jié)果。在應(yīng)用程序中實(shí)現(xiàn)的所有查詢(xún)都有一個(gè)名稱(chēng),可以通過(guò)這個(gè)名稱(chēng)找到它們。這種配置使得在完成編程幾個(gè)星期后更容易知道查詢(xún)的功能。

JPA 持久化提供程序?qū)崿F(xiàn)數(shù)據(jù)庫(kù)訪問(wèn)。以下是最常用的提供程序:

  • Hibernate

  • EclipseLink

  • Apache OpenJPA

Liberty for Java EE 7 的默認(rèn) JPA 提供程序是 EclipseLink。

JPA 概述

下面的簡(jiǎn)要介紹和代碼段展示了 JPA 的特性。開(kāi)始使用 JPA 的最佳方式是創(chuàng)建實(shí)體類(lèi)來(lái)持有數(shù)據(jù)(示例 1)。

示例 1 持有實(shí)體數(shù)據(jù)的 JPA 類(lèi)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Entity
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String description;
@Temporal(TemporalType.DATE)
private Date orderDate;
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Date getOrderDate() {
return orderDate;
}
public void setOrderDate(Date orderDate) {
this.orderDate = orderDate;
}
……
}

每個(gè)實(shí)體類(lèi)都需要一個(gè) @Entity 注釋?zhuān)缓蟛拍芡ㄟ^(guò)持久化提供程序管理。實(shí)體類(lèi)根據(jù)名稱(chēng)映射到數(shù)據(jù)庫(kù)中的對(duì)應(yīng)表(約定優(yōu)于配置)。也可應(yīng)用不同的映射。類(lèi)的屬性根據(jù)名稱(chēng)映射到基礎(chǔ)表的列。也可覆蓋屬性的自動(dòng)映射 (@Column)。每個(gè)實(shí)體類(lèi)必須有一個(gè)實(shí)體(參見(jiàn)第2 部分'領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)'中的'將領(lǐng)域元素映射到服務(wù)')。持久化提供程序必須使用一個(gè)或多個(gè)身份列(使用 @Id 標(biāo)注)來(lái)將對(duì)象的值映射到表中的數(shù)據(jù)行。數(shù)據(jù)庫(kù)或持久化提供程序可通過(guò)不同方式生成身份列的值。實(shí)體類(lèi)的一些屬性必須以特殊方式轉(zhuǎn)換,才能存儲(chǔ)在數(shù)據(jù)庫(kù)中。例如,數(shù)據(jù)庫(kù)列 DATE 必須使用注釋 @Temporal 映射到實(shí)體類(lèi)中。

用于查詢(xún)數(shù)據(jù)庫(kù)的主要 JPA 接口是 EntityManager。它包含從數(shù)據(jù)庫(kù)創(chuàng)建、讀取、更新和刪除數(shù)據(jù)的方法??赏ㄟ^(guò)不同方式獲取 EntityManager 的引用,具體取決于應(yīng)用程序運(yùn)行的環(huán)境。在非托管環(huán)境中(沒(méi)有 servlet、EJB 或 CDI 容器),必須使用類(lèi)的工廠方法 EntityManagerFactory,如示例 2 所示。

示例 2 如何在 Java SE 環(huán)境中獲取 EntityManager

1
2
3
4
EntityManagerFactory entityManagerFactory =
Persistence.createEntityManagerFactory('OrderDB');
EntityManager entityManager =
entityManagerFactory.createEntityManager();

字符串 OrderDB 是為其創(chuàng)建 EntityManager 的持久化單元的名稱(chēng)。持久化單元用于對(duì)實(shí)體類(lèi)和相關(guān)屬性進(jìn)行邏輯分組,以配置持久化提供程序(persistence.xml 文件中的配置)。

在托管環(huán)境中,情況更簡(jiǎn)單??蓮娜萜髯⑷?EntityManager,如示例 3 所示。

示例 3 如何在 Java EE 環(huán)境中獲取 EntityManager

1
2
@PersistenceContext
EntityManager em;

如果注入持久化上下文時(shí)未使用 unitName,也就是配置文件 (persistence.xml) 中配置的持久化單元的名稱(chēng),則會(huì)使用默認(rèn)值。如果只有一個(gè)持久化單元,那么該值就是 JPA 使用的默認(rèn)值。

以下各節(jié)將介紹如何使用來(lái)自 EntityManager 的方法實(shí)現(xiàn)一些簡(jiǎn)單的創(chuàng)建、檢索、更新和刪除方法,如示例 4 所示。

示例 4 JPA 創(chuàng)建操作

1
2
3
4
5
6
7
@PersistenceContext
EntityManager em;
...
public Order createOrder(Order order) {
em.persist(order);
return order;
}

EntityManager 的 persist 方法將執(zhí)行兩個(gè)操作。首先,EntityManager 管理該對(duì)象,這意味著它在其持久化上下文中保存該對(duì)象??蓪⒊志没舷挛囊暈橐环N緩存,在其中保存與數(shù)據(jù)庫(kù)中的行相關(guān)的對(duì)象。這種關(guān)系是使用數(shù)據(jù)庫(kù)事務(wù)來(lái)建立的。其次,該對(duì)象被持久存儲(chǔ)在數(shù)據(jù)庫(kù)中。如果 Order 實(shí)體類(lèi)有一個(gè) ID 屬性,該屬性的值由數(shù)據(jù)庫(kù)生成,那么該屬性的值將由 EntityManager 在將對(duì)象插入數(shù)據(jù)庫(kù)中后設(shè)置。正因如此,createOrder 方法的返回值為對(duì)象本身(示例 5)。

示例 5 使用 find 方法的 JPA 讀取操作

1
2
3
4
5
6
7
@PersistenceContext
EntityManager em;
...
public Order readOrder(Long orderID) {
Order order = em.find(Order.class, orderID);
return order;
}

EntityManager 方法 find 在表中搜索以參數(shù)形式 (orderID) 提供主鍵的行。結(jié)果被轉(zhuǎn)換為一個(gè) Order 類(lèi)型的 Java 類(lèi)(示例 6)。

示例 6 使用 JPQL 的 JPA 讀取操作

1
2
3
4
5
6
7
8
9
10
11
@PersistenceContext
EntityManager em;
...
public Order readOrder(Long orderID) {
TypedQueryOrder> query =
em.createQuery( 'Select o from Order o ' +
'where o.id = :id', Order.class );
query.setParameter('id', orderID);
Order order = query.getSingleResult();
return order;
}

示例 6 通過(guò)使用 JPQL 和一個(gè)參數(shù)顯示了 find 方法的功能。從 JPQL 字符串 Select o from Order o where o.id = :id 開(kāi)始,生成一個(gè) TypedQuery。有了 TypedQuery,您就可以在生成結(jié)果對(duì)象后省略 Java 轉(zhuǎn)換(參見(jiàn)參數(shù) Order.class)。JPQL 中的參數(shù)可按名稱(chēng) (id) 進(jìn)行查找,該名稱(chēng)使得開(kāi)發(fā)人員很容易理解它。方法 getSingleResult 確保僅在數(shù)據(jù)庫(kù)中找到一行。如果有多個(gè)行與 SQL 查詢(xún)對(duì)應(yīng),則拋出一個(gè) RuntimeException。

merge 方法在數(shù)據(jù)庫(kù)中執(zhí)行更新(示例 7)。參數(shù) order 是一個(gè)游離對(duì)象,這意味著它不在持久化上下文中。在數(shù)據(jù)庫(kù)中更新后,EntityManger 返回一個(gè)附加的(現(xiàn)在包含在持久化上下文中)order 對(duì)象。

示例 7 JPA 更新操作

1
2
3
4
public Order updateOrder(Order order, String newDesc) {
order.setDescription(newDesc);
return em.merge(order);
}

要?jiǎng)h除數(shù)據(jù)庫(kù)中的一行數(shù)據(jù),需要一個(gè)附加對(duì)象(示例 8)。要將該對(duì)象附加到持久化上下文中,可以運(yùn)行 find 方法。如果該對(duì)象已附加,則不需要運(yùn)行 find 方法。通過(guò)使用 remove 方法和附加對(duì)象的參數(shù),可以在數(shù)據(jù)庫(kù)中刪除該對(duì)象。

示例 8 JPA 刪除操作

1
2
3
4
public void removeOrder(Long orderId) {
Order order = em.find(Order.class, orderId);
em.remove(order);
}

前面已經(jīng)提到,必須使用某種配置來(lái)告訴持久化提供程序,在何處查找數(shù)據(jù)庫(kù)和如何處理數(shù)據(jù)庫(kù)。這在名為 persistence.xml 的配置文件中完成。該配置文件需要位于您的服務(wù)的類(lèi)路徑中。

依賴(lài)于您的環(huán)境(是否是 Java 容器),必須通過(guò)兩種方式之一完成配置(示例 9)。

示例 9 Java SE 環(huán)境中的 Persistence.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
persistence>
persistence-unit name='OrderDB'
transaction-type='RESOURCE_LOCAL'>
class>com.service.Orderclass>
properties>
property name='javax.persistence.jdbc.url'
value='<> />
property name='javax.persistence.jdbc.user'
value='user1' />
property name='javax.persistence.jdbc.password'
value='password1' />
property name='javax.persistence.jdbc.driver'
value='.DriverClass>' />
properties>
persistence-unit>
persistence>

要配置持久化提供程序,必須做的第一件事就是定義持久化單元 (OrderDB)。持久化單元的一個(gè)屬性是 transaction-type??稍O(shè)置兩個(gè)值:RESOURCE_LOCAL 和 JTA。第一個(gè)選項(xiàng)讓開(kāi)發(fā)人員負(fù)責(zé)在其代碼中執(zhí)行事務(wù)處理。如果您的環(huán)境中沒(méi)有事務(wù)管理器,那么可以使用該選項(xiàng)。第二個(gè)選項(xiàng)是 JTA,它表示 Java Transaction API,包含在 Java Community Process (JCP) 中。此選項(xiàng)告訴持久化提供程序,將事務(wù)處理委托給運(yùn)行時(shí)環(huán)境中存在的事務(wù)管理器。

在 XML 標(biāo)記 之間,可以列出要在這個(gè)持久化單元中使用的實(shí)體類(lèi)。

在該文件的 properties 部分,可以設(shè)置值來(lái)配置持久化提供程序?qū)⑻幚頂?shù)據(jù)庫(kù)的方式。以 javax.persistence.jdbc 開(kāi)頭的屬性名稱(chēng)由 JPA 標(biāo)準(zhǔn)定義。示例 9 展示了如何設(shè)置數(shù)據(jù)庫(kù) URL(用于建立數(shù)據(jù)庫(kù)連接)、用戶(hù)名和密碼。javax.persistence.jdbc.driver 屬性告訴持久化提供程序應(yīng)該使用哪個(gè) JDBC 驅(qū)動(dòng)程序類(lèi)。

Java EE 環(huán)境的配置文件中的主要區(qū)別如示例 10 所示。

示例 10 Java EE 環(huán)境中的 Persistence.xml

1
2
3
4
5
6
7
persistence>
persistence-unit name='OrderDB'>
jta-data-source>jdbc/OrderDBjta-data-source>
class>com.widgets.Orderclass>
...
persistence-unit>
persistence>

JPA 中的默認(rèn)事務(wù)處理方法是使用 JTA,所以您不需要在配置文件中設(shè)置它。jta-data-source 屬性指向 Java EE 環(huán)境的應(yīng)用服務(wù)器中配置的數(shù)據(jù)源的 JNDI-Name。

要在非 Java EE 環(huán)境中執(zhí)行事務(wù)管理,可使用 EntityManager 的一些方法,如示例 5-11 所示。

示例 11 非 Java EE 環(huán)境中的事務(wù)管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
EntityManagerFactory emf =
Persistence.createEntityManagerFactory('OrderDB');
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
em.persist(yourEntity);
em.merge(anotherEntity);
tx.commit();
} finally {
if (tx.isActive()) {
tx.rollback();
}
}

示例 11 給出了非 Java EE 環(huán)境中的事務(wù)處理過(guò)程。請(qǐng)避免自行在 Java EE 環(huán)境中執(zhí)行事務(wù)管理。還有更好的管理方式,如第小節(jié)'Enterprise JavaBeans'所述。

要分離微服務(wù)的數(shù)據(jù)存儲(chǔ),可以使用示例 12 中的配置文件來(lái)設(shè)置關(guān)系數(shù)據(jù)庫(kù)的默認(rèn)模式。

示例 12 在 my-orm.xml 中設(shè)置默認(rèn)模式

1
2
3
4
5
6
7
8
9
10
11
<>xml version='1.0' encoding='UTF-8'?>
entity-mappings xmlns='http://java./xml/ns/persistence/orm'
xmlns:xsi='http://www./2001/XMLSchema-instance'
xsi:schemaLocation='http://java./xml/ns/persistence/orm orm_2_0.xsd'
version='2.0'>
persistence-unit-metadata>
persistence-unit-defaults>
schema>ORDERschema>
persistence-unit-defaults>
persistence-unit-metadata>
entity-mappings>

必須在 JPA 配置文件 persistence.xml 中引用此文件(示例 13)。

示例 13 persistence.xml 中引用一個(gè)映射文件的代碼段

1
2
persistence-unit name='OrderDB'>
mapping-file>custom-orm.xmlmapping-file>

此配置將模式名稱(chēng)被設(shè)置為所有 JPA 類(lèi)的 my-orm.xml 映射文件中提供的名稱(chēng),在本例中為 OrderDB。它可確保您僅使用此模式中的表。

結(jié)合使用 JPA 與 NoSQL 數(shù)據(jù)庫(kù)

EclipseLink 是開(kāi)始支持 NoSQL 數(shù)據(jù)庫(kù)的 JPA 提供程序之一(從 2.4 版開(kāi)始)。從此版本開(kāi)始,它們開(kāi)始支持 MongoDB 和 Oracle NoSQL。預(yù)計(jì)在未來(lái)的版本中還會(huì)支持其他 NoSQL 數(shù)據(jù)庫(kù)。

MongoDB 是一個(gè)面向文檔的 NoSQL 數(shù)據(jù)庫(kù)。它的數(shù)據(jù)結(jié)構(gòu)有利于具有動(dòng)態(tài)模式的 JSON 式文檔。MongoDB 擁有一個(gè)專(zhuān)門(mén)的 JSON 格式版本,名為 BSON。

EclipseLink 2.5 版的EclipseLink 解決方案指南給出了一個(gè)訪問(wèn) MongoDB 的示例。

在決定使用 JPA(像 EclipseLink 一樣)作為 MongoDB 的提供程序之前,請(qǐng)考慮以下幾點(diǎn):

  1. SQL 是一種經(jīng)過(guò)多次修訂的特定語(yǔ)言。數(shù)據(jù)庫(kù)供應(yīng)商已實(shí)現(xiàn)此標(biāo)準(zhǔn),但他們還向 SQL 添加了一些未標(biāo)準(zhǔn)化的特性。JPA 提供了對(duì) SQL 的良好支持,但不是 MongoDB 公開(kāi)的所有特性都受到支持。如果微服務(wù)只需要其中某些特性,那么您使用 JPA 所獲得的好處將會(huì)更少。

  2. JPA 有許多在面向?qū)ο蟮臄?shù)據(jù)庫(kù)中沒(méi)有意義的特性,但 EntityManager 擁有處理這些特性的方法。所以您必須定義要在服務(wù)中使用哪些方法。

如果您熟悉 JPA,而且只需要使用一些簡(jiǎn)單功能將數(shù)據(jù)存儲(chǔ)在 NoSQL 數(shù)據(jù)庫(kù)中,那么可以開(kāi)始使用 JPA 提供程序?qū)崿F(xiàn)此目的。如果數(shù)據(jù)訪問(wèn)變得更加復(fù)雜,那么最好使用來(lái)自 NoSQL 數(shù)據(jù)庫(kù)的 Java 驅(qū)動(dòng)程序。JPA 并不真的適合 NoSQL 數(shù)據(jù)庫(kù),但它是您的實(shí)現(xiàn)的不錯(cuò)起點(diǎn)。有關(guān)如何使用 MongoDB 的原生 Java 驅(qū)動(dòng)程序的示例,請(qǐng)?jiān)L問(wèn):

http://docs./getting-started/java/

要更充分地利用 JPA 提供程序,使用 Spring Data JPA 可能很有幫助。除了 JPA 提供程序之外,Spring Data JPA 還在 JPA 提供程序之上添加了一個(gè)額外層:

http://projects./spring-data-jpa/

JPA 對(duì)微服務(wù)中的數(shù)據(jù)處理的適合性

下面列出了為什么 JPA 對(duì)微服務(wù)中的數(shù)據(jù)處理很有用的一些理由:

  • 從領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)角度定義微服務(wù),這使得大部分微服務(wù)只需簡(jiǎn)單查詢(xún)即可持久化其實(shí)體(簡(jiǎn)單的創(chuàng)建、檢索、更新和刪除操作)。JPA 的 EntityManager 包含您所需的創(chuàng)建、檢索、更新和刪除方法:persist、find、merge、delete。要調(diào)用這些方法,無(wú)需完成太多編程工作。

  • 在一些情況下,查詢(xún)變得更為復(fù)雜。這些查詢(xún)可使用 JPA 中定義的查詢(xún)語(yǔ)言 JPQL 來(lái)完成。對(duì)復(fù)雜查詢(xún)的需求應(yīng)是一種例外情況。具有實(shí)體數(shù)據(jù)分層結(jié)構(gòu)的 JSON 文檔應(yīng)分開(kāi)存儲(chǔ)。這使得 ID 和查詢(xún)變得很簡(jiǎn)單。

  • JPA 已經(jīng)過(guò)標(biāo)準(zhǔn)化,擁有一個(gè)優(yōu)秀的社區(qū)來(lái)為您的微服務(wù)開(kāi)發(fā)提供支持。所有 Java EE 服務(wù)器都必須支持 JPA。

  • 要實(shí)現(xiàn)不在 Java EE 容器中運(yùn)行的微服務(wù),也可以使用 JPA。

  • 從數(shù)據(jù)庫(kù)生成實(shí)體類(lèi)(逆向工程),可減少必須自行編寫(xiě)的代碼行數(shù)。

  • 對(duì)于混合持久性,JPA 支持關(guān)系數(shù)據(jù)存儲(chǔ)和面向文檔的數(shù)據(jù)存儲(chǔ) (EclipseLink)。

  • JPA 是關(guān)系數(shù)據(jù)庫(kù)的一種抽象,允許您在需要時(shí)交換您的關(guān)系數(shù)據(jù)存儲(chǔ)與另一個(gè)關(guān)系數(shù)據(jù)存儲(chǔ)。這種可移植性消除了微服務(wù)的供應(yīng)商鎖定。

  • 為了實(shí)現(xiàn)該策略,每個(gè)微服務(wù)都應(yīng)在關(guān)系數(shù)據(jù)庫(kù)中擁有自己的模式,您可以在 JPA 配置文件 persistence.xml 中為您的服務(wù)設(shè)置默認(rèn)模式。

Enterprise JavaBeans

Enterprise JavaBeans 3.2 (EJB)(在 JSR 345 中指定)包含在 Java EE 規(guī)范中。EJB 并不是普通的 Java 類(lèi),原因如下:

  • 它們擁有生命周期。

  • 它們由一個(gè) EJB 容器(EJB 的運(yùn)行時(shí)環(huán)境)管理。

  • 它們擁有更多很有幫助的特性。

EJB 是服務(wù)器端軟件組件。從 EJB 3.0 開(kāi)始,它不再使用部署描述符。EJB 的所有聲明都可在 EJB 類(lèi)自身中使用注釋完成。與 CDI 管理 bean 的實(shí)現(xiàn)一樣,在 EJB 容器內(nèi)處理 EJB 的實(shí)現(xiàn)也是輕量級(jí)的。EJB 的線程處理由 EJB 容器完成(類(lèi)似于 servlet 容器中的線程處理)。EJB 的一個(gè)附加特性是,它們可與 Java EE Security 結(jié)合使用。

EJB 可分為以下類(lèi)型:

  • 無(wú)狀態(tài)

  • 有狀態(tài)

  • Singleton

  • 消息驅(qū)動(dòng) bean (MDB)

無(wú)狀態(tài) EJB 無(wú)法擁有任何狀態(tài),但有狀態(tài) EJB 可以。由于微服務(wù)的特征,微服務(wù)中不應(yīng)使用有狀態(tài) EJB。一個(gè) Singleton Bean 僅在一個(gè) Java EE 服務(wù)器中存在一次。異步消息處理中會(huì)結(jié)合使用 MDB 和 JMS 提供程序。

EJB 可實(shí)現(xiàn)多個(gè)業(yè)務(wù)視圖,必須相應(yīng)地注釋這些視圖:

  • 本地接口 (@Local)

此接口中的方法只能由同一個(gè) Java 虛擬機(jī) (JVM) 中的客戶(hù)端調(diào)用。

  • 遠(yuǎn)程接口 (@Remote)

此接口中列出的方法可由 JVM 外部的客戶(hù)端調(diào)用。

  • 無(wú)接口 (@LocalBean)

與本地接口大體相同,EJB 類(lèi)的所有公共方法都向客戶(hù)端公開(kāi)。

在輕量型架構(gòu)中(微服務(wù)應(yīng)擁有這種架構(gòu)),將 EJB 實(shí)現(xiàn)為無(wú)接口 EJB 會(huì)很有用。

EJB 提供的主要好處之一是自動(dòng)事務(wù)處理。每次調(diào)用一個(gè)業(yè)務(wù)方法時(shí),都會(huì)調(diào)用 EJB 容器的事務(wù)管理器(例外:顯式關(guān)閉了事務(wù)支持的 EJB)。所以,很容易將 EJB 與事務(wù)數(shù)據(jù)存儲(chǔ)結(jié)合使用。將 EJB 與 JPA 相集成也很容易。

示例 14 中的代碼段給出了一個(gè)將 EJB 與 JPA 框架結(jié)合的示例。

示例 14 帶 PersistenceContext 的無(wú)狀態(tài)(無(wú)接口)EJB

1
2
3
4
5
6
7
8
9
10
11
12
13
@Stateless
@LocalBean
public class OrderEJB {
@PersistenceContext
private EntityManager entityManager;
public void addOrder(Order order) {
entityManager.persist(order);
}
public void deleteOrder(Order order) {
entityManager.remove(order);
}
...
}

根據(jù)小節(jié)'Java Persistence API'中的介紹,注入 EntityManager。

EJB 可擁有以下事務(wù)屬性之一來(lái)處理事務(wù)數(shù)據(jù)存儲(chǔ)(必須由 EJB 容器實(shí)現(xiàn)):

REQUIRED(默認(rèn))

MANDATORY

NEVER

NOT_SUPPORTED

REQUIRES_NEW

SUPPORTS

有關(guān)這些屬性的更多信息,請(qǐng)?jiān)L問(wèn)網(wǎng)站。

這些所謂的容器管理事務(wù) (CMT) 可用在 EJB 的任何業(yè)務(wù)方法上。應(yīng)避免 Bean 管理事務(wù) (BMT),它們也可用在 EJB 中。可在類(lèi)級(jí)別上設(shè)置注釋 TransactionAttribute,使該類(lèi)的每個(gè)業(yè)務(wù)方法都有自己的事務(wù)屬性(示例 15)。如果不執(zhí)行任何設(shè)置,那么所有方法都將擁有默認(rèn)事務(wù)級(jí)別 (REQUIRED)。方法級(jí)別的事務(wù)屬性會(huì)覆蓋類(lèi)屬性。

示例 15 在 EJB 中顯式設(shè)置事務(wù)屬性

1
2
3
4
5
6
7
8
9
10
@TransactionAttribute(REQUIRED)
@Stateless
@LocalBean
public class OrderEJB {
...
@TransactionAttribute(REQUIRES_NEW)
public void methodA() {...}
@TransactionAttribute(REQUIRED)
public void methodB() {...}
}

未實(shí)現(xiàn)為 EJB 的 REST 端點(diǎn)

在事務(wù)管理其決定提交時(shí),會(huì)執(zhí)行一些數(shù)據(jù)庫(kù)更改或驗(yàn)證。在某些情況下,驗(yàn)證數(shù)據(jù)庫(kù)中的檢查約束是提交事務(wù)之前的最后一步。如果驗(yàn)證失敗,JPA 提供程序?qū)伋鲆粋€(gè) RuntimeException,因?yàn)?JPA 使用運(yùn)行時(shí)異常來(lái)報(bào)告錯(cuò)誤。如果使用 EJB 執(zhí)行事故管理,則捕獲 RuntimeException 的位置位于 EJB 的存根代碼中,EJB 容器將在這里執(zhí)行事故管理。存根代碼由 EJB 容器生成。因此,您無(wú)法處理此 RuntimeException,該異常被進(jìn)一步拋出到它會(huì)被捕獲到的地方。

如果將 REST 端點(diǎn)實(shí)現(xiàn)為 EJB,就像一些人喜歡的那樣,則必須在 REST 提供程序中捕獲該異常。REST 提供程序擁有異常映射器,可將異常轉(zhuǎn)換為 HTTP 錯(cuò)誤代碼。但是,當(dāng)在數(shù)據(jù)庫(kù)提交期間拋出 RuntimeException 時(shí),這些異常映射器不會(huì)進(jìn)行干預(yù)。因此,REST 客戶(hù)端會(huì)收到 RuntimeException,應(yīng)該避免這種情況。

處理這些問(wèn)題的最佳方式是,將 REST 端點(diǎn)實(shí)現(xiàn)為 CDI 管理的請(qǐng)求范圍的 bean。在這個(gè) CDI bean 中,可以使用與 EJB 內(nèi)相同的注入方式。所以很容易將 EJB 注入 CDI 管理的 bean 中(示例 16)。

示例 16 實(shí)現(xiàn)為 CDI 管理的 bean 且注入 EJB 的 REST 端點(diǎn)

1
2
3
4
5
6
7
@RequestScoped
@Path('/Order')
public class OrderREST {
@EJB
private OrderEJB orderEJB;
...
}

也可以將 EJB 與 Spring 集成(Enterprise JavaBeans (EJB) 集成 - Spring),而且如果您愿意的話,可以使用 Transaction Management Spring 執(zhí)行事務(wù)管理。但是,在 Java EE 領(lǐng)域,將事務(wù)管理委托給服務(wù)器會(huì)更好一些。

有關(guān) Spring 的更多信息,請(qǐng)?jiān)L問(wèn)這個(gè)網(wǎng)站。

BeanValidation

BeanValidation 也包含在 Java EE 7 規(guī)范中:Bean Validation 1.1 JSR 349。Bean Validation 的用途是在 bean 數(shù)據(jù)上輕松地定義和執(zhí)行驗(yàn)證約束。在 Java EE 環(huán)境中,Bean 驗(yàn)證由不同的 Java EE 容器自動(dòng)完成。開(kāi)發(fā)人員只需要在屬性、方法或類(lèi)上以注釋形式設(shè)置約束條件。驗(yàn)證會(huì)在調(diào)用這些元素時(shí)自動(dòng)完成(如果已配置)。驗(yàn)證也可以在源代碼中顯式完成。有關(guān) BeanValidation 的更多信息,請(qǐng)?jiān)L問(wèn)網(wǎng)站。

javax.validation.constraints 包中的內(nèi)置約束示例如示例 17 所示。

示例 17 BeanValidation 中的默認(rèn)內(nèi)置約束條件

1
2
3
4
5
private String username; // username must not be null
@Pattern(regexp='\\(\\d{3}\\)\\d{3}-\\d{4}')
private String phoneNumber; // phoneNumber must match the regular expression
@Size(min=2, max=40)
String briefMessage; // briefMessage netween 2 and 40 characters

也可以組合使用多個(gè)約束條件,如示例 18 所示。

示例 18 約束條件的組合

1
2
3
@NotNull
@Size(min=1, max=16)
private String firstname;

還可以擴(kuò)展約束條件(自定義約束條件)。示例 19 展示了如何自行執(zhí)行驗(yàn)證。

示例 19 以編程方式進(jìn)行驗(yàn)證

1
2
3
4
5
6
7
8
9
10
Order order = new Order( null, 'This is a description', null );
ValidatorFactory factory =
Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
SetConstraintViolation> constraintViolations = validator.validate(order);
assertEquals( 2, constraintViolations.size() );
assertEquals( 'Id may not be null',
constraintViolations.iterator().next().getMessage() );
assertEquals( 'Order date may not be null',
constraintViolations.iterator().next().getMessage() );

可以通過(guò)配置 JPA 來(lái)自動(dòng)執(zhí)行 Bean 驗(yàn)證。JPA 規(guī)范要求,持久化提供程序必須驗(yàn)證所謂的托管類(lèi)(參見(jiàn)示例 20)。從 JPA 的意義上講,托管類(lèi)是實(shí)體類(lèi)。用于 JPA 編程的所有其他類(lèi)也必須驗(yàn)證(例如嵌入式類(lèi)、超類(lèi))。此過(guò)程必須在這些托管類(lèi)參與的生命周期事件中完成。

示例20 開(kāi)啟了 Bean 驗(yàn)證的 Persistence.xml

1
2
3
4
5
6
7
8
9
10
11
12
persistence>
persistence-unit name='OrderDB'>
provider>
org.eclipse.persistence.jpa.PersistenceProvider
provider>
class> ...class>
properties>
property name='javax.persistence.validation.mode'
value='AUTO' />
properties>
persistence-unit>
persistence>

使用的來(lái)自 Java EE 產(chǎn)品棧的所有其他框架都可用于自動(dòng)驗(yàn)證 bean(例如 JAX-RS、CDI),也可以使用 EJB,如示例 21 所示。

示例 21 EJB 中的 Bean 驗(yàn)證

1
2
3
4
5
6
7
@Stateless
@LocalBean
public class OrderEJB {
public String setDescription(@Max(80) String newDescription){
...
}
}

JPA 和 BeanValidation 的架構(gòu)分層方面

據(jù)微服務(wù)中實(shí)現(xiàn)的層,需要解決一些方面的問(wèn)題。

在僅包含少量層的服務(wù)中,使用 JPA 實(shí)體類(lèi)作為數(shù)據(jù)傳輸對(duì)象 (DTO) 也更容易。將 JPA 對(duì)象與它的持久化上下文分離后,可以將它用作簡(jiǎn)單 Java 對(duì)象 (POJO)。還可以將這個(gè) POJO 用作 DTO,以便將數(shù)據(jù)傳輸?shù)?REST 端點(diǎn)。以這種方式傳輸數(shù)據(jù)有一些缺點(diǎn)。BeanValidation 注釋與 JPA 注釋混合在一起,這可能導(dǎo)致 Java 類(lèi)包含大量注釋?zhuān)夷?REST 端點(diǎn)與數(shù)據(jù)庫(kù)的關(guān)系更緊密。

如果微服務(wù)稍大一點(diǎn)或需要處理更復(fù)雜的數(shù)據(jù)模型,那么最好使用一個(gè)單獨(dú)層來(lái)訪問(wèn)數(shù)據(jù)庫(kù)。這個(gè)額外層基于 JPA 類(lèi)來(lái)實(shí)現(xiàn)所有數(shù)據(jù)訪問(wèn)方法。此層中的類(lèi)是數(shù)據(jù)訪問(wèn)對(duì)象 (DAO)??梢允褂?DAO 類(lèi)為 REST 端點(diǎn)生成 DTO 類(lèi),一方面關(guān)注數(shù)據(jù)模型 (DAO),另一方面關(guān)注客戶(hù)端 (DTO)。這種模式的缺點(diǎn)是,必須將 DAO 層中處理的 JPA 類(lèi)轉(zhuǎn)換為 DTO,并轉(zhuǎn)換回來(lái)。為了避免創(chuàng)建大量樣板代碼來(lái)執(zhí)行此任務(wù),可以使用一些框架來(lái)幫助轉(zhuǎn)換??梢允褂靡韵驴蚣軄?lái)轉(zhuǎn)換 Java 對(duì)象:

  •  ModelMapper

  • MapStruct

要增加 BeanValidation 帶來(lái)的可能性,使用 Spring 獲得額外的特性可能很有用。有關(guān)的更多信息,請(qǐng)參見(jiàn)網(wǎng)頁(yè)上的'Validation, Data Binding, and Type Conversion'。

上下文和依賴(lài)注入

如果您的微服務(wù)不打算將數(shù)據(jù)存儲(chǔ)在事務(wù)數(shù)據(jù)存儲(chǔ)中,可以考慮使用 CDI 管理 bean 代理 EJB。CDI 1.1 是在 JSR 346 中指定的,包含在 Java EE 7 規(guī)范中。CDI 管理 bean 可能是這些情況下的不錯(cuò)替代方案。

有關(guān) CDI 管理 bean 的更多信息,請(qǐng)?jiān)L問(wèn)下面這個(gè)網(wǎng)站:

http://docs.oracle.com/javaee/6/tutorial/doc/giwhl.html

與 EJB 相比,CDI 本身沒(méi)有 Java EE 安全機(jī)制,沒(méi)有注入持久化上下文。此過(guò)程必須由開(kāi)發(fā)人員自己完成,或使用其他框架完成。舉例而言,Apache DeltaSpike 有許多模塊可用于擴(kuò)展 CDI 的功能。有關(guān) Apache DeltaSpike 的更多信息,請(qǐng)?jiān)L問(wèn):

http://deltaspike./index.html

可使用其他框架來(lái)擴(kuò)展 CDI 管理 bean 的功能。EJB 有一個(gè)可在應(yīng)用服務(wù)器中管理的線程池。CDI 目前沒(méi)有與此功能相對(duì)應(yīng)的功能。能夠配置線程池,這在高負(fù)載的環(huán)境中很有幫助。

為了實(shí)現(xiàn)不在 Java EE 應(yīng)用服務(wù)器中運(yùn)行的微服務(wù),CDI 和其他模塊提供了許多對(duì)這些環(huán)境有用的功能。

Java Message Service API

為了在 Java 領(lǐng)域?qū)崿F(xiàn)事件驅(qū)動(dòng)架構(gòu),JMS API 提供了相關(guān)支持,Java Message Service 2.0 JSR 343 中也指定了該 API。JMS 用于與必須通過(guò)面向消息的中間件 (MOM) 實(shí)現(xiàn)的消息提供程序通信。

當(dāng) JMS 從 1.1 版更新到 2.0 版(包含在 Java EE 7 規(guī)范中)時(shí),進(jìn)行了大量返工來(lái)讓 API 更容易使用。JMS 2.0 兼容更低的版本,所以您可以對(duì)新微服務(wù)使用現(xiàn)有代碼或使用新的簡(jiǎn)化 API。下一個(gè)版本不會(huì)棄用舊 API。

依據(jù)智能端點(diǎn)和啞管道方法,基于 Java 的微服務(wù)必須將 JSON 消息發(fā)送到 JMS 提供程序托管的端點(diǎn)。消息的發(fā)送方被稱(chēng)為生成者,消息的接收方被稱(chēng)為使用者。這些端點(diǎn)可具有以下類(lèi)型:

  • 隊(duì)列

一個(gè)隊(duì)列中的消息僅由一個(gè)使用者使用。隊(duì)列中的消息序列可按不同的順序使用。隊(duì)列采用端到端的語(yǔ)義使用。

  • 主題

這些消息可供多個(gè)使用者使用。這是發(fā)布/訂閱語(yǔ)義的實(shí)現(xiàn)。

在基于 REST 的微服務(wù)中(其中基于 JSON 的請(qǐng)求由客戶(hù)端發(fā)出),對(duì)消息系統(tǒng)也采用 JSON 格式是一個(gè)不錯(cuò)的主意。其他消息應(yīng)用程序使用了 XML。如果您的微服務(wù)系統(tǒng)中僅有一種格式,則更容易實(shí)現(xiàn) JSON。

示例 22 生成者使用 EJB 將消息發(fā)送到 JMS 隊(duì)列

1
2
3
4
5
6
7
8
9
10
11
@Stateless
@LocalBean
public class OrderEJB {
@Inject
@JMSConnectionFactory('jms/myConnectionFactory')
JMSContext jmsContext;
@Resource(mappedName = 'jms/PaymentQueue')
Queue queue;
public void sendMessage(String message) {
jmsContext.createProducer().send(queue, message);
}

需要一個(gè) JMSContext 和一個(gè) Queue 才能發(fā)送消息(示例 22)。如果消息發(fā)送方在 Java EE 應(yīng)用服務(wù)器中運(yùn)行,則會(huì)注入這些對(duì)象。示例 22 使用一個(gè) EJB,所以注入了這些資源。必須在應(yīng)用服務(wù)器中對(duì)注入的對(duì)象進(jìn)行配置。如果發(fā)生異常,則會(huì)拋出一個(gè)運(yùn)行時(shí)異常 JMSRuntimeException。

注入的 JMSContext 在 JTA 事務(wù)中的范圍為 transaction。所以如果您的消息生成者是 EJB,您的消息將傳送到事務(wù)的上下文中,這樣做可以避免松散的消息。

要使用來(lái)自隊(duì)列的消息,使用消息驅(qū)動(dòng) EJB (MDB) 就能輕松實(shí)現(xiàn),如示例 23 所示。使用 MDB 的另一個(gè)優(yōu)勢(shì)是,消息的使用在事務(wù)中完成,所以不會(huì)丟失消息。

示例 23 使用者 – 負(fù)責(zé)處理來(lái)自 JMS 隊(duì)列的消息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@MessageDriven(
name='PaymentMDB',
activationConfig = {
@ActivationConfigProperty(
propertyName='messagingType',
propertyValue='javax.jms.MessageListener'),
@ActivationConfigProperty(
propertyName = 'destinationType',
propertyValue = 'javax.jms.Queue'),
@ActivationConfigProperty(
propertyName = 'destination',
propertyValue = 'PaymentQueue'),
@ActivationConfigProperty(
propertyName = 'useJNDI',
propertyValue = 'true'),
}
)
public class PaymentMDB implements MessageListener {
@TransactionAttribute(
value = TransactionAttributeType.REQUIRED)
public void onMessage(Message message) {
if (message instanceof TextMessage) {
TextMessage textMessage = (TextMessage) message;
String text = message.getText();
...
}
}
}

必須使用 @MessageDriven 注釋將 EJB 類(lèi)型聲明為消息驅(qū)動(dòng) EJB。在此注釋內(nèi),可以設(shè)置 MDB 的名稱(chēng)和一些激活配置。激活配置的屬性將 MDB 與處理隊(duì)列或主題的 JMS 消息系統(tǒng)相關(guān)聯(lián)。在托管您的 MDB 的應(yīng)用服務(wù)器環(huán)境中,很容易配置這些元素。MDB 本身會(huì)實(shí)現(xiàn)一個(gè) MessageListener 接口,該接口只有一個(gè)方法:onMessage。每當(dāng) JMS 提供程序有消息要處理時(shí),它就會(huì)調(diào)用此方法。使用一個(gè)事務(wù)屬性注釋該方法,以表明它是在一個(gè)事務(wù)內(nèi)調(diào)用的。MDB 的默認(rèn)事務(wù)屬性是 TransactionAttributeType.REQUIRED。在該方法內(nèi),必須轉(zhuǎn)換消息對(duì)象,而且消息可以提取為字符串。也可使用其他消息類(lèi)型。

僅使用 MDB 處理消息是一種不錯(cuò)的做法。將 MDB 保持為一個(gè)技術(shù)類(lèi)。您的剩余業(yè)務(wù)代碼應(yīng)在 MDB 調(diào)用的 Java POJO 中實(shí)現(xiàn)。此配置使業(yè)務(wù)代碼更容易在 JUnits 中測(cè)試。

前面已經(jīng)提到過(guò),每個(gè) MDB 在一個(gè)事務(wù)內(nèi)運(yùn)行,所以不會(huì)丟失消息。如果在處理消息期間發(fā)生錯(cuò)誤,而且 EJB(處理 MDB)收到此運(yùn)行時(shí)異常,那么該消息會(huì)重新傳送到 MDB(錯(cuò)誤循環(huán))??稍?JMS 提供程序中配置重試次數(shù),它指定了發(fā)生此錯(cuò)誤的頻率。在達(dá)到重試上線次數(shù)后,消息通常被放入一個(gè)錯(cuò)誤隊(duì)列中。錯(cuò)誤隊(duì)列中的消息必須單獨(dú)處理。

如果一個(gè)業(yè)務(wù)事務(wù)涵蓋多個(gè)微服務(wù),可使用事件驅(qū)動(dòng)架構(gòu)(參見(jiàn)小節(jié)'跨微服務(wù)的數(shù)據(jù)共享')。這意味著發(fā)送事件的微服務(wù)必須執(zhí)行以下任務(wù):

  • 更改其數(shù)據(jù)存儲(chǔ)中的數(shù)據(jù)

  • 將消息發(fā)送到第二個(gè)微服務(wù)

接收微服務(wù)必須執(zhí)行以下任務(wù):

  • 從隊(duì)列接收消息

  • 更改它的數(shù)據(jù)存儲(chǔ)中的數(shù)據(jù)

為了保持一致,如果數(shù)據(jù)存儲(chǔ)是事務(wù)性的,這兩個(gè)操作必須在一個(gè)事務(wù)中完成。對(duì)于消息系統(tǒng)的生成者和使用者,也要滿(mǎn)足此要求。在這些情況下,必須使用分布式事務(wù)。事務(wù)伙伴是數(shù)據(jù)存儲(chǔ)和 JMS 提供程序(不是兩個(gè)微服務(wù)的兩個(gè)數(shù)據(jù)存儲(chǔ))。

要跟蹤生成和使用的消息,使用關(guān)聯(lián) ID 很有用。消息生成者指定的關(guān)聯(lián) ID 與使用者使用的消息相關(guān)聯(lián)。這個(gè)關(guān)聯(lián) ID 也可用在微服務(wù)的日志記錄中,以便獲得微服務(wù)調(diào)用的完整通信路徑。

Java 提供了一個(gè)類(lèi)來(lái)生成唯一 Id:UUID。這個(gè)類(lèi)可用于生成關(guān)聯(lián) ID。示例 24 展示了如何設(shè)置關(guān)聯(lián) ID。

示例 24 在 JMS 消息中設(shè)置關(guān)聯(lián) ID

1
2
3
4
// JMSContext injected as before
JMSProducer producer = jmsContext.createProducer();
producer.setJMSCorrelationID(UUID.randomUUID().toString());
producer.send(queue, message);

示例 25 展示了如何獲取關(guān)聯(lián) ID。

示例 25 從 JMS 消息獲取關(guān)聯(lián) ID

1
2
// message received as before
String correlationId = message.getJMSCorrelationID();

有關(guān) UUID 的更多信息,請(qǐng)?jiān)L問(wèn)下面這個(gè)網(wǎng)站:

http://docs.oracle.com/javase/7/docs/api/java/util/UUID.html

如果使用非 JMS 提供程序作為面向消息的中間件,JMS 可能不是向此系統(tǒng)發(fā)送消息的正確方法??山Y(jié)合使用 RabbitMQ(一個(gè) AMQP 代理)和 JMS,因?yàn)?Pivotal 為 RabbitMQ 實(shí)現(xiàn)了一個(gè) JMS 適配器。有關(guān) RabbitMQ 的更多信息,請(qǐng)?jiān)L問(wèn)下面這個(gè)網(wǎng)站:

http://www./

Apache Qpid 也為 AMQP 協(xié)議實(shí)現(xiàn)了一個(gè) JMS 客戶(hù)端。這些只是一些示例,表明使用 JMS 與非 JMS 提供程序通信也很有用。但是,根據(jù)您的需求,使用消息系統(tǒng)的原生 Java 驅(qū)動(dòng)程序有可能會(huì)更好一些。有關(guān) Apache Qpid 的更多信息,請(qǐng)?jiān)L問(wèn)下面這個(gè)網(wǎng)站:

http://qpid./index.html

Spring 對(duì) JMS 消息提供程序的支持是處理 JMS 消息的另一種方案。有關(guān) Spring 的更多信息,請(qǐng)?jiān)L問(wèn)下面這個(gè)網(wǎng)站:

http://docs./spring/docs/current/spring-framework-reference/html/jms.html

Java 和其他消息協(xié)議

根據(jù)您需要的面向消息的中間件中的特性,您可能需要一個(gè)非 JMS 消息提供程序系統(tǒng)。MQTT 是一種最適合物聯(lián)網(wǎng) (IoT) 和 AMQP 的需求的消息協(xié)議。它是為實(shí)現(xiàn)跨供應(yīng)商移植而開(kāi)發(fā)的。

JMS 不能用于與這些系統(tǒng)通信。通過(guò)使用它們提供的客戶(hù)端,可以使用它們提供的所有特殊功能。有關(guān) MQTT 的更多信息,請(qǐng)?jiān)L問(wèn)下面這個(gè)網(wǎng)站:

http:///

Apache Kafka 是一個(gè)非 JMS 提供程序,但它提供了一個(gè) Java 驅(qū)動(dòng)程序。人們提出了一種增強(qiáng)請(qǐng)求,希望實(shí)現(xiàn)一個(gè)適配器,讓客戶(hù)端能與 Apache Kafka 傳輸 JSM 消息,但此問(wèn)題仍待解決。所以最好使用 Kafka 提供的 Java 驅(qū)動(dòng)程序。有關(guān) Kafka 的更多信息,請(qǐng)?jiān)L問(wèn)下面這個(gè)網(wǎng)站:

http://kafka./

RabbitMQ 是一個(gè)消息代理系統(tǒng),它實(shí)現(xiàn)了 AMQP 協(xié)議,而且還提供了一個(gè) Java 客戶(hù)端。有關(guān) RabbitMQ 的更多信息,請(qǐng)?jiān)L問(wèn)下面這個(gè)網(wǎng)站:

https://www./

Spring 有一個(gè)與基于 AMQP 的消息系統(tǒng)通信的庫(kù)。Spring 還提供了對(duì) MQTT 協(xié)議的支持。有關(guān) Spring 的更多信息,請(qǐng)?jiān)L問(wèn)下面這個(gè)網(wǎng)站:

http://projects./spring-amqp/

可以看到,支持結(jié)合使用 Java 和非 JMS 消息系統(tǒng)。

總結(jié)

本文重點(diǎn)介紹了如何使用基于 Java 的微服務(wù)實(shí)現(xiàn)微服務(wù)在數(shù)據(jù)處理方面保持可管理。下一部分我們將回到第一部分中講到的演化策略,將介紹可在實(shí)踐中考慮和應(yīng)用的可能戰(zhàn)略。好了,學(xué)習(xí)愉快,下次再見(jiàn)!

出處:https://www.ibm.com/developerworks/cn/java/j-cn-java-and-microservice-4/index.html?ca=drs-

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶(hù)發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶(hù) 評(píng)論公約

    類(lèi)似文章 更多