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