版權聲明:本文為部落客原創文章,未經部落客允許不得轉載。 https://blog.csdn.net/bitree1/article/details/50014705
下面總結一下使用 Spring Data JPA 進行持久層開發大緻需要的三個步驟:
- 聲明持久層的接口,該接口繼承 Repository,Repository 是一個标記型接口,它不包含任何方法,當然如果有需要,Spring Data 也提供了若幹 Repository 子接口,其中定義了一些常用的增删改查,以及分頁相關的方法。
- 在接口中聲明需要的業務方法。Spring Data 将根據給定的政策(具體政策稍後講解)來為其生成實作代碼。
- 在 Spring 配置檔案中增加一行聲明,讓 Spring 為聲明的接口建立代理對象。配置了 <jpa:repositories> 後,Spring 初始化容器時将會掃描 base-package 指定的包目錄及其子目錄,為繼承 Repository 或其子接口的接口建立代理對象,并将代理對象注冊為 Spring Bean,業務層便可以通過 Spring 自動封裝的特性來直接使用該對象。
此外,<jpa:repository> 還提供了一些屬性和子标簽,便于做更細粒度的控制。可以在 <jpa:repository> 内部使用 <context:include-filter>、<context:exclude-filter> 來過濾掉一些不希望被掃描到的接口。具體的使用方法見
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 為此提供了一些表達條件查詢的關鍵字,大緻如下:
- And --- 等價于 SQL 中的 and 關鍵字,比如 findByUsernameAndPassword(String user, Striang pwd);
- Or --- 等價于 SQL 中的 or 關鍵字,比如 findByUsernameOrAddress(String user, String addr);
- Between --- 等價于 SQL 中的 between 關鍵字,比如 findBySalaryBetween(int max, int min);
- LessThan --- 等價于 SQL 中的 "<",比如 findBySalaryLessThan(int max);
- GreaterThan --- 等價于 SQL 中的">",比如 findBySalaryGreaterThan(int min);
- IsNull --- 等價于 SQL 中的 "is null",比如 findByUsernameIsNull();
- IsNotNull --- 等價于 SQL 中的 "is not null",比如 findByUsernameIsNotNull();
- NotNull --- 與 IsNotNull 等價;
- Like --- 等價于 SQL 中的 "like",比如 findByUsernameLike(String user);
- NotLike --- 等價于 SQL 中的 "not like",比如 findByUsernameNotLike(String user);
- OrderBy --- 等價于 SQL 中的 "order by",比如 findByUsernameOrderBySalaryAsc(String user);
- Not --- 等價于 SQL 中的 "! =",比如 findByUsernameNot(String user);
- In --- 等價于 SQL 中的 "in",比如 findByUsernameIn(Collection<String> userList) ,方法的參數可以是 Collection 類型,也可以是數組或者不定長參數;
- NotIn --- 等價于 SQL 中的 "not in",比如 findByUsernameNotIn(Collection<String> userList) ,方法的參數可以是 Collection 類型,也可以是數組或者不定長參數;