jhipster入門疑問之路二

在疑問之路一種猜測了一種想法,剛好有查到一篇大神寫的文章。驗證了這種猜想,對spring的崇拜之情一男一言表,切該作者字裏行間亦言盡內心波瀾。線摘抄如下,供自學習。全文共分四部首先是最原始的jpa其次是spring的支持jpa 第三部分是jhipster中用到的jpa 。可以直接看第三部分

從一個簡單的 JPA 示例開始

本文主要講述 Spring Data JPA,但是爲了不至於給 JPA 和 Spring 的初學者造成較大的學習曲線,我們首先從 JPA 開始,簡單介紹一個 JPA 示例;接着重構該示例,並引入 Spring 框架,這兩部分不會涉及過多的篇幅,如果希望能夠深入學習 Spring 和 JPA,可以根據本文最後提供的參考資料進一步學習。
自 JPA 伴隨 Java EE 5 發佈以來,受到了各大廠商及開源社區的追捧,各種商用的和開源的 JPA 框架如雨後春筍般出現,爲開發者提供了豐富的選擇。它一改之前 EJB 2.x 中實體 Bean 笨重且難以使用的形象,充分吸收了在開源社區已經相對成熟的 ORM 思想。另外,它並不依賴於 EJB 容器,可以作爲一個獨立的持久層技術而存在。目前比較成熟的 JPA 框架主要包括 Jboss 的 Hibernate EntityManager、Oracle 捐獻給 Eclipse 社區的 EclipseLink、Apache 的 OpenJPA 等。
本文的示例代碼基於 Hibernate EntityManager 開發,但是讀者幾乎不用修改任何代碼,便可以非常容易地切換到其他 JPA 框架,因爲代碼中使用到的都是 JPA 規範提供的接口 / 類,並沒有使用到框架本身的私有特性。示例主要涉及七個文件,但是很清晰:業務層包含一個接口和一個實現;持久層包含一個接口、一個實現、一個實體類;另外加上一個 JPA 配置文件和一個測試類。相關類 / 接口代碼如下:
清單 1. 實體類 AccountInfo.java

@Entity 這裏寫圖片描述
這裏寫圖片描述

簡述 Spring 框架對 JPA 的支持

接下來我們引入 Spring,以展示 Spring 框架對 JPA 的支持。業務層接口 UserService 保持不變,UserServiceImpl 中增加了三個註解,以讓 Spring 完成依賴注入,因此不再需要使用 new 操作符創建 UserDaoImpl 對象了。同時我們還使用了 Spring 的聲明式事務:
清單 8. 配置爲 Spring Bean 的業務層實現
這裏寫圖片描述
通過對比重構前後的代碼,可以發現 Spring 對 JPA 的簡化已經非常出色了,我們可以大致總結一下 Spring 框架對 JPA 提供的支持主要體現在如下幾個方面:
首先,它使得 JPA 配置變得更加靈活。JPA 規範要求,配置文件必須命名爲 persistence.xml,並存在於類路徑下的 META-INF 目錄中。該文件通常包含了初始化 JPA 引擎所需的全部信息。Spring 提供的 LocalContainerEntityManagerFactoryBean 提供了非常靈活的配置,persistence.xml 中的信息都可以在此以屬性注入的方式提供。
其次,Spring 實現了部分在 EJB 容器環境下才具有的功能,比如對 @PersistenceContext、@PersistenceUnit 的容器注入支持。
第三,也是最具意義的,Spring 將 EntityManager 的創建與銷燬、事務管理等代碼抽取出來,並由其統一管理,開發者不需要關心這些,如前面的代碼所示,業務方法中只剩下操作領域對象的代碼,事務管理和 EntityManager 創建、銷燬的代碼都不再需要開發者關心了。

更進一步:Spring Data JPA 讓一切近乎完美

通過前面的分析可以看出,Spring 對 JPA 的支持已經非常強大,開發者只需關心核心業務邏輯的實現代碼,無需過多關注 EntityManager 的創建、事務處理等 JPA 相關的處理,這基本上也是作爲一個開發框架而言所能做到的極限了。然而,Spring 開發小組並沒有止步,他們再接再厲,於最近推出了 Spring Data JPA 框架,主要針對的就是 Spring 唯一沒有簡化到的業務邏輯代碼,至此,開發者連僅剩的實現持久層業務邏輯的工作都省了,唯一要做的,就只是聲明持久層的接口,其他都交給 Spring Data JPA 來幫你完成!
至此,讀者可能會存在一個疑問,框架怎麼可能代替開發者實現業務邏輯呢?畢竟,每一個應用的持久層業務甚至領域對象都不盡相同,框架是怎麼做到的呢?其實這背後的思想並不複雜,比如,當你看到 UserDao.findUserById() 這樣一個方法聲明,大致應該能判斷出這是根據給定條件的 ID 查詢出滿足條件的 User 對象。Spring Data JPA 做的便是規範方法的名字,根據符合規範的名字來確定方法需要實現什麼樣的邏輯。
接下來我們針對前面的例子進行改造,讓 Spring Data JPA 來幫助我們完成業務邏輯。在着手寫代碼之前,開發者需要先 下載Spring Data JPA 的發佈包(需要同時下載 Spring Data Commons 和 Spring Data JPA 兩個發佈包,Commons 是 Spring Data 的公共基礎包),並把相關的依賴 JAR 文件加入到 CLASSPATH 中。
首先,讓持久層接口 UserDao 繼承 Repository 接口。該接口使用了泛型,需要爲其提供兩個類型:第一個爲該接口處理的域對象類型,第二個爲該域對象的主鍵類型。修改後的 UserDao 如下:

這裏寫圖片描述

下面總結一下使用 Spring Data JPA 進行持久層開發大致需要的三個步驟:

聲明持久層的接口,該接口繼承 Repository,Repository 是一個標記型接口,它不包含任何方法,當然如果有需要,Spring Data 也提供了若干 Repository 子接口,其中定義了一些常用的增刪改查,以及分頁相關的方法。
在接口中聲明需要的業務方法。Spring Data 將根據給定的策略(具體策略稍後講解)來爲其生成實現代碼。
在 Spring 配置文件中增加一行聲明,讓 Spring 爲聲明的接口創建代理對象。配置了 後,Spring 初始化容器時將會掃描 base-package 指定的包目錄及其子目錄,爲繼承 Repository 或其子接口的接口創建代理對象,並將代理對象註冊爲 Spring Bean,業務層便可以通過 Spring 自動封裝的特性來直接使用該對象。
此外, 還提供了一些屬性和子標籤,便於做更細粒度的控制。可以在 內部使用 、 來過濾掉一些不希望被掃描到的接口。具體的使用方法見 Spring參考文檔。

應該繼承哪個接口?

應該繼承哪個接口?
前面提到,持久層接口繼承 Repository 並不是唯一選擇。Repository 接口是 Spring Data 的一個核心接口,它不提供任何方法,開發者需要在自己定義的接口中聲明需要的方法。與繼承 Repository 等價的一種方式,就是在持久層接口上使用 @RepositoryDefinition 註解,併爲其指定 domainClass 和 idClass 屬性。如下兩種方式是完全等價的:

如果持久層接口較多,且每一個接口都需要聲明相似的增刪改查方法,直接繼承 Repository 就顯得有些囉嗦,這時可以繼承 CrudRepository,它會自動爲域對象創建增刪改查方法,供業務層直接使用。開發者只是多寫了 “Crud” 四個字母,即刻便爲域對象提供了開箱即用的十個增刪改查方法。
但是,使用 CrudRepository 也有副作用,它可能暴露了你不希望暴露給業務層的方法。比如某些接口你只希望提供增加的操作而不希望提供刪除的方法。針對這種情況,開發者只能退回到 Repository 接口,然後到 CrudRepository 中把希望保留的方法聲明覆制到自定義的接口中即可。
分頁查詢和排序是持久層常用的功能,Spring Data 爲此提供了 PagingAndSortingRepository 接口,它繼承自 CrudRepository 接口,在 CrudRepository 基礎上新增了兩個與分頁有關的方法。但是,我們很少會將自定義的持久層接口直接繼承自 PagingAndSortingRepository,而是在繼承 Repository 或 CrudRepository 的基礎上,在自己聲明的方法參數列表最後增加一個 Pageable 或 Sort 類型的參數,用於指定分頁或排序信息即可,這比直接使用 PagingAndSortingRepository 提供了更大的靈活性。
JpaRepository 是繼承自 PagingAndSortingRepository 的針對 JPA 技術提供的接口,它在父接口的基礎上,提供了其他一些方法,比如 flush(),saveAndFlush(),deleteInBatch() 等。如果有這樣的需求,則可以繼承該接口。
上述四個接口,開發者到底該如何選擇?其實依據很簡單,根據具體的業務需求,選擇其中之一。筆者建議在通常情況下優先選擇 Repository 接口。因爲 Repository 接口已經能滿足日常需求,其他接口能做到的在 Repository 中也能做到,彼此之間並不存在功能強弱的問題。只是 Repository 需要顯示聲明需要的方法,而其他則可能已經提供了相關的方法,不需要再顯式聲明,但如果對 Spring Data JPA 不熟悉,別人在檢視代碼或者接手相關代碼時會有疑惑,他們不明白爲什麼明明在持久層接口中聲明瞭三個方法,而在業務層使用該接口時,卻發現有七八個方法可用,從這個角度而言,應該優先考慮使用 Repository 接口。
前面提到,Spring Data JPA 在後臺爲持久層接口創建代理對象時,會解析方法名字,並實現相應的功能。除了通過方法名字以外,它還可以通過如下兩種方式指定查詢語句:
Spring Data JPA 可以訪問 JPA 命名查詢語句。開發者只需要在定義命名查詢語句時,爲其指定一個符合給定格式的名字,Spring Data JPA 便會在創建代理對象時,使用該命名查詢語句來實現其功能。
開發者還可以直接在聲明的方法上面使用 @Query 註解,並提供一個查詢語句作爲參數,Spring Data JPA 在創建代理對象時,便以提供的查詢語句來實現其功能。
下面我們分別講述三種創建查詢的方式。
通過解析方法名創建查詢
通過前面的例子,讀者基本上對解析方法名創建查詢的方式有了一個大致的瞭解,這也是 Spring Data JPA 吸引開發者的一個很重要的因素。該功能其實並非 Spring Data JPA 首創,而是源自一個開源的 JPA 框架 Hades,該框架的作者 Oliver Gierke 本身又是 Spring Data JPA 項目的 Leader,所以把 Hades 的優勢引入到 Spring Data JPA 也就是順理成章的了。
框架在進行方法名解析時,會先把方法名多餘的前綴截取掉,比如 find、findBy、read、readBy、get、getBy,然後對剩下部分進行解析。並且如果方法的最後一個參數是 Sort 或者 Pageable 類型,也會提取相關的信息,以便按規則進行排序或者分頁查詢。
在創建查詢時,我們通過在方法名中使用屬性名稱來表達,比如 findByUserAddressZip ()。框架在解析該方法時,首先剔除 findBy,然後對剩下的屬性進行解析,詳細規則如下(此處假設該方法針對的域對象爲 AccountInfo 類型):
先判斷 userAddressZip (根據 POJO 規範,首字母變爲小寫,下同)是否爲 AccountInfo 的一個屬性,如果是,則表示根據該屬性進行查詢;如果沒有該屬性,繼續第二步;
從右往左截取第一個大寫字母開頭的字符串(此處爲 Zip),然後檢查剩下的字符串是否爲 AccountInfo 的一個屬性,如果是,則表示根據該屬性進行查詢;如果沒有該屬性,則重複第二步,繼續從右往左截取;最後假設 user 爲 AccountInfo 的一個屬性;
接着處理剩下部分( AddressZip ),先判斷 user 所對應的類型是否有 addressZip 屬性,如果有,則表示該方法最終是根據 “AccountInfo.user.addressZip” 的取值進行查詢;否則繼續按照步驟 2 的規則從右往左截取,最終表示根據 “AccountInfo.user.address.zip” 的值進行查詢。
可能會存在一種特殊情況,比如 AccountInfo 包含一個 user 的屬性,也有一個 userAddress 屬性,此時會存在混淆。讀者可以明確在屬性之間加上 “_” 以顯式表達意圖,比如 “findByUser_AddressZip()” 或者 “findByUserAddress_Zip()”。
在查詢時,通常需要同時根據多個屬性進行查詢,且查詢的條件也格式各樣(大於某個值、在某個範圍等等),Spring Data JPA 爲此提供了一些表達條件查詢的關鍵字,大致如下:
這裏寫圖片描述
這裏寫圖片描述

創建查詢的順序

Spring Data JPA 在爲接口創建代理對象時,如果發現同時存在多種上述情況可用,它該優先採用哪種策略呢?爲此, 提供了 query-lookup-strategy 屬性,用以指定查找的順序。它有如下三個取值:
create — 通過解析方法名字來創建查詢。即使有符合的命名查詢,或者方法通過 @Query 指定的查詢語句,都將會被忽略。
create-if-not-found — 如果方法通過 @Query 指定了查詢語句,則使用該語句實現查詢;如果沒有,則查找是否定義了符合條件的命名查詢,如果找到,則使用該命名查詢;如果兩者都沒有找到,則通過解析方法名字來創建查詢。這是 query-lookup-strategy 屬性的默認值。
use-declared-query — 如果方法通過 @Query 指定了查詢語句,則使用該語句實現查詢;如果沒有,則查找是否定義了符合條件的命名查詢,如果找到,則使用該命名查詢;如果兩者都沒有找到,則拋出異常。
Spring Data JPA 對事務的支持
默認情況下,Spring Data JPA 實現的方法都是使用事務的。針對查詢類型的方法,其等價於 @Transactional(readOnly=true);增刪改類型的方法,等價於 @Transactional。可以看出,除了將查詢的方法設爲只讀事務外,其他事務屬性均採用默認值。
如果用戶覺得有必要,可以在接口方法上使用 @Transactional 顯式指定事務屬性,該值覆蓋 Spring Data JPA 提供的默認值。同時,開發者也可以在業務層方法上使用 @Transactional 指定事務屬性,這主要針對一個業務層方法多次調用持久層方法的情況。持久層的事務會根據設置的事務傳播行爲來決定是掛起業務層事務還是加入業務層的事務。具體 @Transactional 的使用,請參考 Spring的參考文檔。
爲接口中的部分方法提供自定義實現
有些時候,開發者可能需要在某些方法中做一些特殊的處理,此時自動生成的代理對象不能完全滿足要求。爲了享受 Spring Data JPA 帶給我們的便利,同時又能夠爲部分方法提供自定義實現,我們可以採用如下的方法:
將需要開發者手動實現的方法從持久層接口(假設爲 AccountDao )中抽取出來,獨立成一個新的接口(假設爲 AccountDaoPlus ),並讓 AccountDao 繼承 AccountDaoPlus;
爲 AccountDaoPlus 提供自定義實現(假設爲 AccountDaoPlusImpl );
將 AccountDaoPlusImpl 配置爲 Spring Bean;
在 中按清單 19 的方式進行配置。
這裏寫圖片描述

結束語

本文主要介紹了 Spring Data JPA 的使用,以及它與 Spring 框架的無縫集成。Spring Data JPA 其實並不依賴於 Spring 框架,有興趣的讀者可以參考本文最後的”參考資源”進一步學習。

發佈了32 篇原創文章 · 獲贊 10 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章