本章詳細講解如何利用方法名定義查詢方法(Defining Query Methods)
(1)定義查詢方法的配置方法
由於Spring JPA Repository的實現原理是采用動態代理的機制,所以我們介紹兩種定義查詢方法:從方法名稱中可以指定特定用於存儲的查詢和更新,或通過使用@Query手動定義的查詢,這個取決於實際存儲操作。只需要實體Repository繼承Spring Data Common里面的Repository接口即可,就像前面我們講的一樣。如果你想有其他更多默認通用方法的實現,可以選擇JpaRepository、PagingAndSortingRepository、CrudRepository等接口,也可以直接繼承我們后面要講的JpaSpecificationExecutor、QueryByExampleExecutor和自定義Response,都可以達到同樣的效果。
如果你不想擴展Spring數據接口,還可以使用它來注解存儲庫接口@RepositoryDefinition。擴展CrudRepository公開了一套完整的方法來操縱實體。如果你希望對所暴露的方法有選擇性,只需要將暴露的方法復制CrudRepository到域庫中即可。其實也是自定義Repository的一種。
看下面的示例,選擇性地暴露CRUD方法:
在這個示例的第一步中為所有域存儲庫定義了一個公共基礎接口,並將其暴露出來。findOne(…)和save(…)方法將被路由到由Spring Data提供的、你選擇的存儲庫的基本存儲庫實現中,例如JPA中的SimpleJpaRepository。因為它們正在匹配方法簽名CrudRepository,所以UserRepository將能夠保存用戶,並通過id查找單個用戶信息,以及觸發查詢以通過其電子郵件地址查找Users。
(2)方法的查詢策略設置
通過@EnableJpaRepositories(queryLookupStrategy=QueryLookupStrategy.Key.CREATE_IF_NOT_FOUND)可以配置方法的查詢策略,其中QueryLookupStrategy.Key的值一共有三個。
CREATE:直接根據方法名進行創建。規則是根據方法名稱的構造進行嘗試,一般的方法是從方法名中刪除給定的一組已知前綴,並解析該方法的其余部分。如果方法名不符合規則,啟動的時候就會報異常。
USE_DECLARED_QUERY:聲明方式創建,即本書說的注解方式。啟動的時候會嘗試找到一個聲明的查詢,如果沒有找到就將拋出一個異常。查詢可以由某處注釋或其他方法聲明。
CREATE_IF_NOT_FOUND:這個是默認的,以上兩種方式的結合版。先用聲明方式進行查找,如果沒有找到與方法相匹配的查詢,就用create的方法名創建規則創建一個查詢。
除非有特殊需求,一般直接用默認的,不用管。配置示例如下
(3)查詢方法的創建
內部基礎架構中有個根據方法名的查詢生成器機制,對於在存儲庫的實體上構建約束查詢很有用。該機制方法的前綴有find…By、read…By、query…By、count…By和get…By,從這些方法可以分析它的其余部分(實體里面的字段)。引入子句可以包含其他表達式,例如在Distinct要創建的查詢上設置不同的標志。然而,第一個By作為分隔符來指示實際標准的開始。在一個非常基本的水平上,你可以定義實體性條件,並與它們串聯(And和Or)。
用一句話概括,待查詢功能的方法名由查詢策略(關鍵字)、查詢字段和一些限制性條件組成。在如下例子中,可以直接在controller里面進行調用以查看效果:
解析方法的實際結果取決於創建查詢的持久性存儲。但是,有一些常見的事項需要注意:
表達式通常是可以連接的運算符的屬性遍歷。你可以使用組合屬性表達式AND和OR。你還可以將運算關鍵字Between、LessThan、GreaterThan、Like作為屬性表達式。受支持的操作員可能因數據存儲而異,因此請參閱官方參考文檔的相應部分內容。
該方法解析器支持設置一個IgnoreCase標志個別特性(例如,findByLastnameIgnoreCase(…))或支持忽略大小寫(通常是一個類型的所有屬性為String的情況下,例如,findByLastnameAndFirstnameAllIgnoreCase(…))。是否支持忽略示例可能會因存儲而異,因此請參閱參考文檔中的相關章節,了解特定於場景的查詢方法。
可以通過OrderBy在引用屬性和提供排序方向(Asc或Desc)的查詢方法中附加一個子句來應用靜態排序。要創建支持動態排序的查詢方法來影響查詢結果。
(4)關鍵字列表
注意,除了find的前綴之外,我們查看PartTree的源碼,還有如
下幾種前綴:
使用的時候要配合不同的返回結果進行使用,例如:
(5)方法的查詢策略的屬性表達式
屬性表達式(Property Expressions)只能引用托管(泛化)實體的直接屬性,如前一個示例所示。在查詢創建時,你已經確保解析的屬性是托管實體的屬性。同時,還可以通過遍歷嵌套屬性定義約束。假設一個Person實體對象里面有一個Address屬性里面包含一個ZipCode屬性。在這種情況下,方法名為:
創建及其查找的過程是:解析算法首先將整個part(AddressZipCode)解釋為屬性,並使用該名稱(uncapitalized)檢查域類的屬性。如果算法成功,就使用該屬性。如果不是,就拆分右側駝峰部分的信號源到頭部和尾部,並試圖找出相應的屬性,在我們的例子中是AddressZip和Code。如果算法找到一個具有頭部的屬性,那么它需要尾部,並從那里繼續構建樹,然后按照剛剛描述的方式將尾部分割。如果第一個分割不匹配,就將分割點移動到左邊(Address、ZipCode),然后繼續。
雖然這在大多數情況下應該起作用,但是算法可能會選擇錯誤的屬性。假設Person類也有一個addressZip屬性,該算法將在第一個分割輪中匹配,並且基本上會選擇錯誤的屬性,最后失敗(因為該類型addressZip可能沒有code屬性)。
要解決這個歧義,可以在方法名稱中手動定義遍歷點,所以我們的方法名稱最終會是:
當然Spring JPA里面是將下划線視為保留字符,但是強烈建議遵循標准Java命名約定(不使用屬性名稱中的下划線,而是使用駱駝示
例)。命名屬性的時候注意一下這個特性。
(6)查詢結果的處理
1.參數選擇分頁和排序(Pageable/Sort)
1.特定類型的參數,動態地將分頁和排序應用於查詢
示例:在查詢方法中使用Pageable、Slice和Sort。
第一種方法允許將org.springframework.data.domain.Pageable實例傳遞給查詢方法,以便動態地將分頁添加到靜態定義的查詢中。Page知道可用的元素和頁面的總數。它通過基礎框架里面觸發計數查詢來計算總數。由於這可能是昂貴的,具體取決於所使用的場景,說白了,當用到Pageable的時候會默認執行一條cout語句。而Slice的作用是,只知道是否有下一個Slice可用,不會執行count,所以當查詢較大的結果集時,只知道數據是足夠的就可以了,而且相關的業務場景也不用關心一共有多少頁。
排序選項也通過Pageable實例處理。如果只需要排序,那么在org.springframework.data.domain.Sort參數中添加一個參數即可。正如你可以看到的,只返回一個List也是可能的。在這種情況下,Page將不會創建構建實際實例所需的附加元數據(這反過來意味着必須不被發布的附加計數查詢),而僅僅是限制查詢僅查找給定范圍的實體。
2.限制查詢結果
示例:在查詢方法上加限制查詢結果的關鍵字first和top。
查詢方法的結果可以通過關鍵字來限制first或top,其可以被互換地使用。可選的數值可以追加到頂部/第一個以指定要返回的最大結果大小。如果數字被省略,則假設結果大小為1。限制表達式也支持Distinct關鍵字。此外,對於將結果集限制為一個實例的查詢,支持將結果包裝到一個實例中的Optional中。如果將分頁或切片應用於限制查詢分頁(以及可用頁數的計算),則在限制結果中應用。
2.查詢結果的不同形式(List/Stream/Page/Future)
Page和List在上面的示例中都有涉及,下面介紹幾種特殊的。
1.流式查詢結果
可以通過使用Java 8 Stream<T>作為返回類型來逐步處理查詢方法的結果,而不是簡單地將查詢結果包裝在Stream數據存儲中,特定的方法用於執行流。
2.異步查詢結果
可以使用Spring的異步方法執行功能異步的存儲庫查詢。這意味着方法將在調用時立即返回,並且實際的查詢執行將發生在已提交給Spring TaskExecutor的任務中,比較適合定時任務的實際場景。
3 Projections對查詢結果的擴展
Spring JPA對Projections擴展的支持是非常好的。從字面意思上理解就是映射,指的是和DB查詢結果的字段映射關系。一般情況下,返回的字段和DB查詢結果的字段是一一對應的,但有的時候,我們需要返回一些指定的字段,不需要全部返回,或者只返回一些復合型的字段,還要自己寫邏輯。Spring Data正是考慮到了這一點,允許對專用返回類型進行建模,以便我們有更多的選擇,將部分字段顯示成視圖對象。
假設Person是一個正常的實體,和數據表Person一一對應,正常的寫法如下:
(1)我們想僅僅返回其中與name相關的字段,應該怎么做呢?基於projections的思路,其實是比較容易的。我們只需要聲明一個
接口,包含要返回的屬性的方法即可,例如:
Repository里面的寫法如下,直接用這個對象接收結果即可
在Controtroller里面直接調用對象可以查看結果。原理是運行時底層會有動態代理機制為這個接口生成一個實現實體類。
(2)查詢關聯的子對象,例如:
(3)@Value和SPEL也支持:
PersonRepository里面保持不變,這樣會返回一個firstname和lastname相加的只有fullName的結果集合。