什么是JPA?Java持續(xù)性介紹
?本文將了解基于 Hibernate 的 Java 持久化標(biāo)準(zhǔn),學(xué)習(xí)如何使用 JPA 在關(guān)系數(shù)據(jù)庫或 NoSQL 數(shù)據(jù)庫中存儲和管理 Java 對象。
作為一種規(guī)范,Jakarta Persistence API(以前稱為 Java Persistence API)更關(guān)注持久性,這大概意味著 Java 對象比創(chuàng)建它們的應(yīng)用程序進(jìn)程存活更久。
并非所有 Java 對象都需要持久化,但大多數(shù)應(yīng)用程序都會持久化關(guān)鍵業(yè)務(wù)對象。JPA 規(guī)范允許您定義應(yīng)保留哪些對象,以及它們在 Java 應(yīng)用程序中的保留方式。
JPA 本身不是工具或框架;相反,它定義了一組指導(dǎo)實施者的概念。雖然 JPA 的對象關(guān)系映射 (ORM) 模型最初是基于 Hibernate,但后來有所發(fā)展。同樣,雖然 JPA 最初旨在與關(guān)系數(shù)據(jù)庫一起使用,但一些 JPA 實現(xiàn)已擴展為與 NoSQL 數(shù)據(jù)存儲一起使用。支持 JPA 和 NoSQL 的流行框架是 EclipseLink,它是 JPA 3 的參考實現(xiàn)。
與 JDBC 不同,JPA 背后的核心思想是,在大多數(shù)情況下,JPA避免需要讓您“從關(guān)系上思考”。在 JPA 中,您在 Java 代碼和對象領(lǐng)域定義持久性規(guī)則,而 JDBC 需要您手動將代碼轉(zhuǎn)換為關(guān)系表,然后再轉(zhuǎn)換回來。
JPA 3 在 Jakarta EE
Java Persistence API 最初是作為 Java EE 5 中的 Enterprise JavaBeans 3.0 規(guī)范 (JSR 220) 的子集發(fā)布的。從 Java EE 6 (JSR 317) 中的 JPA 2.0 發(fā)布開始,它已經(jīng)發(fā)展成為自己的規(guī)范。JPA 于 2019 年被采納為 Jakarta EE 的獨立項目。
- 截至撰寫本文時,當(dāng)前版本為 JPA 3.1。
流行的 JPA 實現(xiàn),如 Hibernate 和 EclipseLink 現(xiàn)在支持 JPA 3。從 JPA 2 遷移到 JPA 3 涉及到一些名稱空間更改,但除此之外,這些更改是底層性能提升。
JPA 和 Hibernate
由于它們相互交織的歷史,Hibernate 和 JPA 經(jīng)常被混為一談。但是,與 Java Servlet 規(guī)范一樣,JPA 催生了許多兼容的工具和框架。Hibernate 只是眾多 JPA 工具中的一種。
Hibernate 由 Gavin King 開發(fā)并于 2002 年初首次發(fā)布,是一個用于 Java 的 ORM 庫。King 開發(fā)了 Hibernate 作為實體 bean 的替代品以實現(xiàn)持久性。該框架非常流行,當(dāng)時非常需要,以至于它的許多想法都被采納并編入了第一個 JPA 規(guī)范。
今天,Hibernate ORM 是最成熟的 JPA 實現(xiàn)之一,并且仍然是 Java 中 ORM 的流行選擇。
- 撰寫本文時的最新版本 Hibernate ORM 6 實現(xiàn)了 JPA 2.2。
其他 Hibernate 工具包括 Hibernate Search、Hibernate Validator 和 Hibernate OGM,后者支持 NoSQL 的域模型持久性。
JPA 和 EJB
如前所述,JPA 是作為 Enterprise JavaBeans (EJB) 3.0 的一個子集引入的,但后來發(fā)展成為它自己的規(guī)范。EJB 是一種與 JPA 側(cè)重點不同的規(guī)范,它是在 EJB 容器中實現(xiàn)的。每個 EJB 容器都包含一個由 JPA 規(guī)范定義的持久層。
什么是Java ORM?
雖然它們在執(zhí)行上有所不同,但每個 JPA 實現(xiàn)都提供某種 ORM 層。為了理解 JPA 和 JPA 兼容工具,您需要很好地掌握 ORM。
對象關(guān)系映射是一項任務(wù)——開發(fā)人員有充分的理由避免手動執(zhí)行。像 Hibernate ORM 或 EclipseLink 這樣的框架將該任務(wù)編入一個庫或框架,一個 ORM 層。作為應(yīng)用程序架構(gòu)的一部分,ORM 層負(fù)責(zé)管理軟件對象與關(guān)系數(shù)據(jù)庫中的表和列交互的轉(zhuǎn)換。在 Java 中,ORM 層將 Java 類和對象進(jìn)行轉(zhuǎn)換,以便它們可以在關(guān)系數(shù)據(jù)庫中存儲和管理。
默認(rèn)情況下,被持久化的對象的名稱成為表的名稱,字段成為列。設(shè)置好表格后,每個表格行對應(yīng)于應(yīng)用程序中的一個對象。對象映射是可配置的,但默認(rèn)值往往有效,并且通過堅持使用默認(rèn)值,您可以避免維護(hù)配置元數(shù)據(jù)。
JPA 與 NoSQL
直到最近,非關(guān)系數(shù)據(jù)庫才引起人們的好奇。NoSQL 運動改變了這一切,現(xiàn)在 Java 開發(fā)人員可以使用各種 NoSQL 數(shù)據(jù)庫。一些 JPA 實現(xiàn)已經(jīng)發(fā)展到包含 NoSQL,包括 Hibernate OGM 和 EclipseLink。
圖1
圖 1 說明了 JPA 和 ORM 層在應(yīng)用程序開發(fā)中的作用。
配置 Java ORM 層
當(dāng)您設(shè)置新項目以使用 JPA 時,您將需要配置數(shù)據(jù)存儲和 JPA 提供程序。您將配置一個數(shù)據(jù)存儲連接器以連接到您選擇的數(shù)據(jù)庫(SQL 或 NoSQL)。您還將包含并配置 JPA 提供程序,它是一個框架,例如 Hibernate 或 EclipseLink。雖然您可以手動配置 JPA,但許多開發(fā)人員選擇使用 Spring 的開箱即用支持。我們將很快查看手動和基于 Spring 的 JPA 安裝和設(shè)置。
Java 數(shù)據(jù)對象
Java 數(shù)據(jù)對象 (JDO) 是一種標(biāo)準(zhǔn)化的持久性框架,它與 JPA 的主要區(qū)別在于支持對象中的持久性邏輯,以及它對使用非關(guān)系數(shù)據(jù)存儲的長期支持。JPA 和 JDO 非常相似,JDO 提供者通常也支持 JPA。請參閱 Apache JDO 項目以了解有關(guān) JDO 與其他持久性標(biāo)準(zhǔn)(如 JPA 和 JDBC)的關(guān)系的更多信息。
Java 中的數(shù)據(jù)持久化
從編程的角度來看,ORM 層是一個適配器層:它使對象圖的語言適應(yīng) SQL 和關(guān)系表的語言。ORM 層允許面向?qū)ο蟮拈_發(fā)人員構(gòu)建能夠持久保存數(shù)據(jù)的軟件,而無需離開面向?qū)ο蟮姆独?/p>
使用 JPA 時,您創(chuàng)建了一個從數(shù)據(jù)存儲到應(yīng)用程序數(shù)據(jù)模型對象的映射。您無需定義對象的保存和檢索方式,而是定義對象與數(shù)據(jù)庫之間的映射,然后調(diào)用 JPA 來持久化它們。如果您使用的是關(guān)系數(shù)據(jù)庫,那么您的應(yīng)用程序代碼和數(shù)據(jù)庫之間的大部分實際連接將由 JDBC 處理。
作為規(guī)范,JPA 提供了元數(shù)據(jù)注釋,您可以使用它來定義對象和數(shù)據(jù)庫之間的映射。每個 JPA 實現(xiàn)都為 JPA 注釋提供了自己的引擎。JPA 規(guī)范還提供了 PersistanceManager 或 EntityManager,它們是與 JPA 系統(tǒng)的關(guān)鍵聯(lián)系點(其中您的業(yè)務(wù)邏輯代碼告訴系統(tǒng)如何處理映射的對象)。
為了使所有這些更加具體,請考慮清單 1,這是一個用于為音樂家建模的簡單數(shù)據(jù)類。
清單 1.Java 中的一個簡單數(shù)據(jù)類
清單 1 中的 Musician 類用于保存數(shù)據(jù)。它可以包含原始數(shù)據(jù),例如名稱字段。它還可以保持與其他類的關(guān)系,例如 mainInstrument 和 performances。
音樂家存在的理由是包含數(shù)據(jù)。這種類型的類有時稱為 DTO,即數(shù)據(jù)傳輸對象。DTO 是軟件開發(fā)的一個共同特征。雖然它們包含多種數(shù)據(jù),但它們不包含任何業(yè)務(wù)邏輯。持久化數(shù)據(jù)對象是軟件開發(fā)中普遍存在的挑戰(zhàn)。
使用 JDBC 的數(shù)據(jù)持久化
將 Musician 類的實例保存到關(guān)系數(shù)據(jù)庫的一種方法是使用 JDBC 庫。JDBC 是一個抽象層,它允許應(yīng)用程序在不考慮底層數(shù)據(jù)庫實現(xiàn)的情況下發(fā)出 SQL 命令。
清單 2 展示了如何使用 JDBC 持久化 Musician 類。
清單 2. JDBC 插入一條記錄。
清單 2 中的代碼是相當(dāng)自我記錄的。georgeHarrison 對象可以來自任何地方(前端提交、外部服務(wù)等),并設(shè)置了其 ID 和名稱字段。對象上的字段然后用于提供 SQL 插入語句的值。(PreparedStatement 類是 JDBC 的一部分,提供了一種安全地將值應(yīng)用于 SQL 查詢的方法。)
雖然 JDBC 提供了手動配置附帶的控件,但與 JPA 相比,它很麻煩。要修改數(shù)據(jù)庫,您首先需要創(chuàng)建一個從 Java 對象映射到關(guān)系數(shù)據(jù)庫中的表的 SQL 查詢。然后,只要對象簽名發(fā)生更改,您就必須修改 SQL。使用 JDBC,維護(hù) SQL 本身就成為一項任務(wù)。
使用 JPA 的數(shù)據(jù)持久化
現(xiàn)在考慮清單 3,我們在其中使用 JPA 持久化 Musician 類。
清單 3. 使用 JPA 持久化 George Harrison。
清單 3 用一行 entityManager.save() 替換了清單 2 中的手動 SQL,它指示 JPA 保留對象。從那時起,該框架將處理 SQL 轉(zhuǎn)換,因此您永遠(yuǎn)不必離開面向?qū)ο蟮姆独?/p>
JPA 中的元數(shù)據(jù)注釋
清單 3 中的魔法是配置的結(jié)果,它是使用 JPA 的注釋創(chuàng)建的。開發(fā)人員使用注釋來通知 JPA 哪些對象應(yīng)該被持久化,以及它們應(yīng)該如何被持久化。
清單 4 顯示了帶有單個 JPA 注釋的 Musician 類。
清單 4. JPA 的 @Entity 注釋
持久對象有時稱為實體。將 @Entity 附加到像 Musician 這樣的類會通知 JPA 該類及其對象應(yīng)該保留。
XML 與基于注解的配置
JPA 還允許您使用外部 XML 文件來定義類元數(shù)據(jù),而不是注釋。但你為什么要這樣對自己?
配置 JPA
與大多數(shù)現(xiàn)代框架一樣,JPA 采用約定編碼(也稱為約定優(yōu)于配置),其中框架提供基于行業(yè)最佳實踐的默認(rèn)配置。例如,默認(rèn)情況下,名為 Musician 的類將映射到名為 Musician 的數(shù)據(jù)庫表。
常規(guī)配置可以節(jié)省時間,并且在許多情況下效果很好。也可以自定義 JPA 配置。例如,您可以使用 JPA 的 @Table 注釋來指定應(yīng)該存儲 Musician 類的表。
清單 5. JPA 的 @Table 注釋
清單 5 告訴 JPA 將實體(Musician 類)持久保存到 Musician 表。
主鍵
在 JPA 中,主鍵是用來唯一標(biāo)識數(shù)據(jù)庫中每個對象的字段。主鍵對于引用對象和將對象關(guān)聯(lián)到其他實體很有用。每當(dāng)您將對象存儲在表中時,您還將指定要用作其主鍵的字段。
在清單 6 中,我們告訴 JPA 使用哪個字段作為 Musician 的主鍵。
清單 6. 指定主鍵:
在本例中,我們使用 JPA 的 @Id 注釋將 id 字段指定為 Musician 的主鍵。默認(rèn)情況下,此配置假定主鍵將由數(shù)據(jù)庫設(shè)置——例如,當(dāng)字段設(shè)置為表上的自動遞增時。
JPA 支持用于生成對象主鍵的其他策略。它還具有用于更改單個字段名稱的注釋。通常,JPA 足夠靈活以適應(yīng)您可能需要的任何持久性映射。
增刪改查操作
一旦您將一個類映射到數(shù)據(jù)庫表并建立了它的主鍵,您就擁有了在數(shù)據(jù)庫中創(chuàng)建、檢索、刪除和更新該類所需的一切。調(diào)用 entityManager.save() 將創(chuàng)建或更新指定的類,具體取決于主鍵字段是 null 還是應(yīng)用于現(xiàn)有實體。調(diào)用 entityManager.remove() 將刪除指定的類。
實體關(guān)系
簡單地持久化具有原始字段的對象只是等式的一半。JPA 還允許您管理相互關(guān)聯(lián)的實體。表和對象中可能存在四種實體關(guān)系:
- 一對多
- 多對一
- 多對多
- 一對一
每種類型的關(guān)系都描述了一個實體如何與其他實體相關(guān)。例如,Musician 實體可能與 Performance 具有一對多關(guān)系,Performance 是一個由集合(如 List 或 Set)表示的實體。
如果 Musician 包含 Band 字段,則這些實體之間的關(guān)系可能是多對一的,這意味著單個 Band 類上的 Musician 集合。(假設(shè)每個音樂家只在一個樂隊中表演。)
如果 Musician 包含 BandMates 字段,則它可以表示與其他 Musician 實體的多對多關(guān)系。
最后,Musician 可能與 Quote 實體存在一對一關(guān)系,用于表示名言:Quote famousQuote = new Quote()。
定義關(guān)系類型
JPA 對其每個關(guān)系映射類型都有注釋。清單 7 展示了如何注釋 Musician 和 Performances 之間的一對多關(guān)系。
清單 7. 注釋一對多關(guān)系
需要注意的一件事是 @JoinColumn 告訴 JPA Performance 表上的哪一列將映射到 Musician 實體。
每場表演都將與一位音樂家相關(guān)聯(lián),該音樂家由此列進(jìn)行跟蹤。當(dāng) JPA 將 Musician 或 Performance 加載到數(shù)據(jù)庫中時,它將使用此信息來重構(gòu)對象圖。
實體狀態(tài)和分離的實體
實體是對象的總稱,其持久性與 ORM 映射。正在運行的應(yīng)用程序中的實體將始終處于四種狀態(tài)之一:瞬態(tài)、托管、分離和刪除。
您將在 JPA 中遇到的一種情況是分離的實體。這僅僅意味著您正在處理的對象已經(jīng)脫離了支持它們的數(shù)據(jù)存儲中的內(nèi)容,并且支持它們的會話已經(jīng)關(guān)閉。換句話說,JPA 希望使對象保持最新狀態(tài),但它做不到。您可以通過在實體上調(diào)用 entityManager.merge() 來重新附加分離的實體。
任何不持久的對象都是瞬態(tài)的。該對象此時只是一個潛在的實體。一旦調(diào)用了 entityManager.persist() ,它就變成了一個持久實體。
托管對象是一個持久實體。
當(dāng)一個實體已從數(shù)據(jù)存儲中刪除,但仍作為活動對象存在時,我們稱其處于已刪除狀態(tài)。
Vlad Mihalcea 寫了一篇關(guān)于實體狀態(tài)的精彩討論,以及 JPA 的 EntityManager 和 Hibernate 的 Session 類之間用于管理它們的細(xì)微差別。
EntityManager.flush() 有什么用?
許多剛接觸 JPA 的開發(fā)人員想知道 EntityManager.flush() 方法的用途。JPA 管理器將緩存保持實體的持久狀態(tài)與數(shù)據(jù)庫一致所需的操作,并對它們進(jìn)行批處理以提高效率。
但有時,您需要手動使 JPA 框架執(zhí)行將實體推送到數(shù)據(jù)庫所需的操作。例如,這可能會導(dǎo)致執(zhí)行數(shù)據(jù)庫觸發(fā)器。在這種情況下,您可以使用 flush() 方法,所有尚未持久化的實體狀態(tài)將立即發(fā)送到數(shù)據(jù)庫。
獲取策略
除了知道將相關(guān)實體放在數(shù)據(jù)庫中的什么位置之外,JPA 還需要知道您希望如何加載它們。獲取策略告訴 JPA 如何加載相關(guān)實體。加載和保存對象時,JPA 框架必須提供微調(diào)對象圖處理方式的能力。例如,如果 Musician 類有一個 bandMate 字段(如清單 7 所示),加載 george 可能會導(dǎo)致從數(shù)據(jù)庫中加載整個 Musician 表!
您需要能夠定義相關(guān)實體的延遲加載——當(dāng)然要認(rèn)識到 JPA 中的關(guān)系可以是急切的或延遲的。您可以使用注釋來自定義您的抓取策略,但 JPA 的默認(rèn)配置通常開箱即用,無需更改:
一對多:惰性
多對一:Eager
多對多:惰性
一對一:Eager
JPA 中的事務(wù)
雖然不在本簡短介紹的范圍內(nèi),但事務(wù)允許開發(fā)人員寫入數(shù)據(jù)庫。在清單 1 中,我們使用以下行實現(xiàn)了一個簡單的事務(wù):em.getTransaction().commit();。事務(wù)可以通過多種方式定義,從通過 API 的顯式交互,到使用注釋來定義事務(wù)邊界,再到使用 Spring AOP 來定義事務(wù)。
JPA 安裝和設(shè)置
最后,我們將快速瀏覽一下為您的 Java 應(yīng)用程序安裝和設(shè)置 JPA。對于這個演示,我將使用 JPA 參考實現(xiàn) EclipseLink。
安裝 JPA 的常用方法是將 JPA 提供程序包含到您的項目中。清單 8 展示了如何將 EclipseLink 作為依賴項包含在 Maven pom.xml 文件中。
清單 8. 將 EclipseLink 作為 Maven 依賴項包含在內(nèi):
您還需要包含數(shù)據(jù)庫的驅(qū)動程序,如清單 9 所示。
清單 9. MySql 連接器的 Maven 依賴項:
接下來,您需要將您的數(shù)據(jù)庫和提供商告知系統(tǒng)。這是在 persistence.xml 文件中完成的,如清單 10 所示。
清單 10.Persistence.xml:
還有其他方法可以向系統(tǒng)提供此信息,包括以編程方式。我建議使用 persistence.xml 文件,因為以這種方式存儲依賴項可以很容易地更新您的應(yīng)用程序而無需修改代碼。
JPA 的 Spring 配置
使用 Spring 將極大地簡化 JPA 到您的應(yīng)用程序的集成。例如,在您的應(yīng)用程序標(biāo)頭中放置 @SpringBootApplication 注釋會指示 Spring 根據(jù)您指定的配置自動掃描類并根據(jù)需要注入 EntityManager。
清單 11 顯示了如果您希望 Spring 的 JPA 支持您的應(yīng)用程序需要包含的依賴項。
清單 11. 在 Maven 中添加 Spring JPA 支持:
何時使用 JPA
在設(shè)計 Java 應(yīng)用程序時,是否使用 JPA 的問題是分析癱瘓的常見來源。尤其是在嘗試做出前期技術(shù)決策時,您不希望數(shù)據(jù)持久性(一個重要的長期因素)出錯。
要打破這種癱瘓狀態(tài),記住應(yīng)用程序可以演變?yōu)槭褂?JPA 是很有用的。您可以使用 JDBC 或 NoSQL 庫構(gòu)建探索性代碼或原型代碼,然后開始添加 JPA。這些解決方案沒有理由不能共存。
在因優(yōu)柔寡斷而癱瘓之后,下一個最糟糕的事情就是采用 JPA,因為它意味著額外的努力將阻止項目向前推進(jìn)。JPA 可以成為整體系統(tǒng)穩(wěn)定性和可維護(hù)性的勝利,而這些對于更成熟的項目來說是極好的目標(biāo);然而,有時越簡單越好,尤其是在項目開始時。
如果您的團隊沒有能力預(yù)先采用 JPA,請考慮將其列入未來的路線圖。
結(jié)論
每個處理數(shù)據(jù)庫的應(yīng)用程序都應(yīng)該定義一個應(yīng)用程序?qū)?,其唯一目的是隔離持久性代碼。正如您在本文中所見,Jakarta Persistence API 引入了一系列功能并支持 Java 對象持久性。簡單的應(yīng)用程序可能不需要 JPA 的所有功能,并且在某些情況下配置框架的開銷可能不值得。然而,隨著應(yīng)用程序的增長,JPA 的結(jié)構(gòu)和封裝確實得到了保留。使用 JPA 可以使您的目標(biāo)代碼保持簡單,并為訪問 Java 應(yīng)用程序中的數(shù)據(jù)提供一個常規(guī)框架。
了解有關(guān) Jakarta Persistence API 和相關(guān)技術(shù)的更多信息:
- 什么是 JDBC?Java 數(shù)據(jù)庫連接簡介:使用 JDBC 連接到數(shù)據(jù)庫、處理 SQL 查詢等的概述和指南。
- 使用 JPA 和 Hibernate 的 Java 持久性,第 1 部分:建模實體和關(guān)系。
- 使用 JPA 和 Hibernate 的 Java 持久性,第 2 部分:多對多關(guān)系和繼承關(guān)系。
- 如果你的下一個項目使用 JPA?:可以幫助您做出決定的關(guān)鍵問題。
這個故事“什么是 JPA?Java 持久性簡介”最初由 JavaWorld 發(fā)布。