javascript
Spring Data JPA 从入门到精通~查询结果的处理
參數選擇(Sort/Pageable)分頁和排序
?
特定類型的參數,Pageable 并動態 Sort 地將分頁和排序應用于查詢
案例:在查詢方法中使用 Pageable、Slice 和 Sort。
Page<User> findByLastname(String lastname, Pageable pageable); Slice<User> findByLastname(String lastname, Pageable pageable); List<User> findByLastname(String lastname, Sort sort); List<User> findByLastname(String lastname, Pageable pageable);第一種方法允許將 org.springframework.data.domain.Pageable 實例傳遞給查詢方法,以動態地將分頁添加到靜態定義的查詢中,Page 知道可用的元素和頁面的總數,它通過基礎框架里面觸發計數查詢來計算總數。由于這可能是昂貴的,這取決于所使用的場景,說白了,當用到 Pageable 的時候會默認執行一條 cout 語句。而 Slice 的用作是,只知道是否有下一個 Slice 可用,不會執行count,所以當查詢較大的結果集時,只知道數據是足夠的,而相關的業務場景也不用關心一共有多少頁。
排序選項也通過 Pageable 實例處理,如果只需要排序,需在 org.springframework.data.domain.Sort 參數中添加一個參數即可,正如看到的,只需返回一個 List 也是可能的。在這種情況下,Page 將不會創建構建實際實例所需的附加元數據(這反過來意味著必須不被發布的附加計數查詢),而僅僅是限制查詢僅查找給定范圍的實體。
限制查詢結果
案例:在查詢方法上加限制查詢結果的關鍵字 First 和 top。
User findFirstByOrderByLastnameAsc(); User findTopByOrderByAgeDesc(); Page<User> queryFirst10ByLastname(String lastname, Pageable pageable); Slice<User> findTop3ByLastname(String lastname, Pageable pageable); List<User> findFirst10ByLastname(String lastname, Sort sort); List<User> findTop10ByLastname(String lastname, Pageable pageable);查詢方法的結果可以通過關鍵字來限制 first 或 top,其可以被可互換使用,可選的數值可以追加到頂部/第一個以指定要返回的最大結果的大小。如果數字被省略,則假設結果大小為 1,限制表達式也支持 Distinct 關鍵字。此外,對于將結果集限制為一個實例的查詢,支持將結果包裝到一個實例中 Optional。如果將分頁或切片應用于限制查詢分頁(以及可用頁數的計算),則在限制結果中應用。
查詢結果的不同形式(List/Stream/Page/Future)
Page 和 List 在上面的案例中都有涉及下面將介紹的幾種特殊的方式。
流式查詢結果
可以通過使用 Java 8 Stream<T> 作為返回類型來逐步處理查詢方法的結果,而不是簡單地將查詢結果包裝在 Stream 數據存儲中,特定的方法用于執行流。
示例:使用 Java 8 流式傳輸查詢的結果 Stream<T>。
@Query("select u from User u") Stream<User> findAllByCustomQueryAndStream(); Stream<User> readAllByFirstnameNotNull(); @Query("select u from User u") Stream<User> streamAllPaged(Pageable pageable);注意:流的關閉問題,try catch?是一種用關閉方法。
Stream<User> stream; try {stream = repository.findAllByCustomQueryAndStream()stream.forEach(…); } catch (Exception e) {e.printStackTrace(); } finally {if (stream!=null){stream.close();} }異步查詢結果
可以使用 Spring 的異步方法執行功能異步執行存儲庫查詢,這意味著方法將在調用時立即返回,并且實際的查詢執行將發生在已提交給 Spring TaskExecutor 的任務中,比較適合定時任務的實際場景。
@Async Future<User> findByFirstname(String firstname); (1) @Async CompletableFuture<User> findOneByFirstname(String firstname); (2) @Async ListenableFuture<User> findOneByLastname(String lastname);(3)- 使用 java.util.concurrent.Future 的返回類型。
- 使用 java.util.concurrent.CompletableFuture 作為返回類型。
- 使用 org.springframework.util.concurrent.ListenableFuture 作為返回類型。
所支持的返回結果類型遠不止這些,可以根據實際的使用場景靈活選擇,其中 Map 和 Object[] 的返回結果也支持,這種方法不太推薦使用,應為沒有用到對象思維,不知道結果里面裝的是什么。
下表列出了 Spring Data JPA Query Method 機制支持的方法的返回值類型。
某些特定的存儲可能不支持全部的返回類型。 只有支持地理空間查詢的數據存儲才支持 GeoResult、GeoResults、GeoPage 等返回類型。
而我們要看引用的那個 Spring Data 的實現子模塊,以 Spring Data JPA 為例,看看 JPA 默認幫實現了哪些返回值類型。
還是通過工具分析 JpaRepository 幫我們實現了哪些返回類型,這樣不至于直接看官方文檔的時候一頭霧水。
Projections 對查詢結果的擴展
Spring JPA 對 Projections 的擴展的支持,個人覺得這是個非常好的東西,從字面意思上理解就是映射,指的是和 DB 的查詢結果的字段映射關系。一般情況下,我們是返回的字段和 DB 的查詢結果的字段是一一對應的,但有的時候,需要返回一些指定的字段,不需要全部返回,或者返回一些復合型的字段,還得自己寫邏輯。Spring Data 正是考慮到了這一點,允許對專用返回類型進行建模,以便更有選擇地將部分視圖對象。
假設 Person 是一個正常的實體,和數據表 Person 一一對應,我們正常的寫法如下:
@Entity class Person {@IdUUID id;String firstname, lastname;Address address;@Entitystatic class Address {String zipCode, city, street;} } interface PersonRepository extends Repository<Person, UUID> {Collection<Person> findByLastname(String lastname); }(1)但是我們想僅僅返回其中的 name 相關的字段,應該怎么做呢?如果基于 projections 的思路,其實是比較容易的。只需要聲明一個接口,包含我們要返回的屬性的方法即可。如下:
interface NamesOnly {String getFirstname();String getLastname(); }Repository 里面的寫法如下,直接用這個對象接收結果即可,如下:
interface PersonRepository extends Repository<Person, UUID> {Collection<NamesOnly> findByLastname(String lastname); }Ctroller 里面直接調用這個對象可以看看結果。
原理是,底層會有動態代理機制為這個接口生產一個實現實體類,在運行時。
(2)查詢關聯的子對象,一樣的道理,如下:
interface PersonSummary {String getFirstname();String getLastname();AddressSummary getAddress();interface AddressSummary {String getCity();} }(3)@Value 和 SPEL 也支持:
interface NamesOnly {@Value("#{target.firstname + ' ' + target.lastname}")String getFullName();… }PersonRepository 里面保持不變,這樣會返回一個 firstname 和 lastname 相加的只有 fullName 的結果集合。
(4)對 Spel 表達式的支持遠不止這些:
@Component class MyBean {String getFullName(Person person) {…//自定義的運算} } interface NamesOnly {@Value("#{@myBean.getFullName(target)}")String getFullName();… }(5)還可以通過 Spel 表達式取到方法里面的參數的值。
interface NamesOnly {@Value("#{args[0] + ' ' + target.firstname + '!'}")String getSalutation(String prefix); }(6)這時候有人會在想,只能用 interface 嗎?dto 支持嗎?也是可以的,也可以定義自己的 Dto 實體類,需要哪些字段我們直接在 Dto 類當中暴漏出來 get/set 屬性即可,如下:
class NamesOnlyDto {private final String firstname, lastname; //注意構造方法NamesOnlyDto(String firstname, String lastname) {this.firstname = firstname;this.lastname = lastname;}String getFirstname() {return this.firstname;}String getLastname() {return this.lastname;} }(7)支持動態 Projections,想通過泛化,根據不同的業務情況,返回不通的字段集合。
PersonRepository做一定的變化,如下:
interface PersonRepository extends Repository<Person, UUID> {Collection<T> findByLastname(String lastname, Class<T> type); }我們的調用方,就可以通過 class 類型動態指定返回不同字段的結果集合了,如下:
void someMethod(PersonRepository people) { //我想包含全字段,就直接用原始entity(Person.class)接收即可Collection<Person> aggregates = people.findByLastname("Matthews", Person.class); //如果我想僅僅返回名稱,我只需要指定Dto即可。Collection<NamesOnlyDto> aggregates = people.findByLastname("Matthews", NamesOnlyDto.class); }最后,Projections 的應用場景還是挺多的,望大家好好體會,這樣可以實現更優雅的代碼,去實現不同的場景。不必要用數組,冗余的對象去接收查詢結果。
總結
以上是生活随笔為你收集整理的Spring Data JPA 从入门到精通~查询结果的处理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 异常与断言
- 下一篇: Spring Security用户认证和