Spring Webflux 和 Spring Data Reactive 中的分頁
一、簡介
在本文中,我們將探討分頁對於檢索信息的重要性,將 Spring Data Reactive 分頁與 Spring Data 進行比較,並通過示例演示如何實現分頁。
2. 分頁的意義
在處理返回大量資源集合的端點時,分頁是一個基本概念。它通過將數據分解為更小的、可管理的塊(稱為“頁面”)來實現數據的高效檢索和呈現。
考慮一個顯示產品詳細信息的 UI 頁面,該頁面可以顯示 10 到 10,000 條記錄。假設 UI 設計為從後端獲取並顯示整個目錄。在這種情況下,它將消耗額外的後端資源並導致用戶等待很長的時間。
實施分頁系統可以顯著增強用戶體驗。與立即獲取整個記錄集相比,最初檢索一些記錄並提供根據請求加載下一組記錄的選項會更有效。
使用分頁,後端可以返回具有較小子集(例如 10 條記錄)的初始響應,並使用偏移量或下一頁鏈接檢索後續頁面。這種方法將獲取和顯示記錄的負載分佈在多個頁面上,從而改善了整體應用程序體驗。
3. Spring Data 中的分頁與分頁Spring 數據反應式
Spring Data 是更大的 Spring Framework 生態系統中的一個項目,旨在簡化和增強 Java 應用程序中的數據訪問。 Spring Data 提供了一組通用的抽象和功能,通過減少樣板代碼和推廣最佳實踐來簡化開發過程。
正如 Spring Data Pagination 示例中所解釋的, PageRequest
對象接受page
、 size
和sort
參數,可用於配置和請求不同的頁面。 Spring Data 提供了[PagingAndSortingRepository,](https://docs.spring.io/spring-data/data-commons/docs/current/api/org/springframework/data/repository/PagingAndSortingRepository.html)
它提供了使用分頁和排序抽象來檢索實體的方法。存儲庫方法接受[Pageable](https://docs.spring.io/spring-data/data-commons/docs/current/api/org/springframework/data/domain/Pageable.html)
和Sort對象,可用於配置返回的[Page](https://docs.spring.io/spring-data/data-commons/docs/current/api/org/springframework/data/domain/Page.html)
信息。該Page
對象包含[totalElements](https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/domain/Page.html#getTotalElements())
和[totalPages](https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/domain/Page.html#getTotalPages())
屬性,這些屬性是通過在內部執行其他查詢來填充的。該信息可用於請求後續頁面的信息。
相反,Spring Data Reactive 並不完全支持分頁。原因就在於Spring Reactive對異步非阻塞的支持。它必須等待(或阻塞),直到返回特定頁面大小的所有數據,這不是很有效。然而, Spring Data Reactive 仍然支持[Pageable](https://docs.spring.io/spring-data/data-commons/docs/current/api/org/springframework/data/domain/Pageable.html)
。我們可以使用PageRequest
對象配置它來檢索特定的數據塊,並添加顯式查詢來獲取記錄總數。
當使用 Spring Data 時,我們可以獲得響應的Flux
,而不是Page
,其中包含有關頁面上記錄的元數據。
4. 基本應用
4.1. Spring WebFlux 和 Spring Data Reactive 中分頁的實現
在本文中,我們將使用一個簡單的 Spring R2DBC 應用程序,該應用程序通過 GET /products 公開分頁產品信息。
讓我們考慮一個簡單的產品模型:
@Data
@AllArgsConstructor
@NoArgsConstructor
@Table
public class Product {
@Id
@Getter
private UUID id;
@NotNull
@Size(max = 255, message = "The property 'name' must be less than or equal to 255 characters.")
private String name;
@NotNull
private double price;
}
我們可以通過傳遞[Pageable](https://docs.spring.io/spring-data/data-commons/docs/current/api/org/springframework/data/domain/Pageable.html)
對像從產品存儲庫中獲取產品列表,該對象包含Page
和大小等配置:
@Repository
public interface ProductRepository extends ReactiveSortingRepository<Product, UUID> {
Flux<Product> findAllBy(Pageable pageable);
}
此查詢將結果集響應為Flux
,而不是Page
,因此需要單獨查詢記錄總數以填充Page
響應。
讓我們添加一個帶有PageRequest
對象的控制器,該對像還運行一個附加查詢來獲取記錄總數。這是因為我們的存儲庫不會返回Page
信息,而是返回Flux<Product>
:
@GetMapping("/products")
public Mono<Page<Product>> findAllProducts(Pageable pageable) {
return this.productRepository.findAllBy(pageable)
.collectList()
.zipWith(this.productRepository.count())
.map(p -> new PageImpl<>(p.getT1(), pageable, p.getT2()));
}
最後,我們必須將查詢結果集和最初接收到的Pageable
對象發送到PageImpl
。此類具有計算Page
信息的輔助方法,其中包括有關頁面的元數據以獲取下一組記錄。
現在,當我們嘗試訪問端點時,我們應該收到帶有頁面元數據的產品列表:
{
"content": [
{
"id": "cdc0c4e6-d4f6-406d-980c-b8c1f5d6d106",
"name": "product_A",
"price": 1
},
{
"id": "699bc017-33e8-4feb-aee0-813b044db9fa",
"name": "product_B",
"price": 2
},
{
"id": "8b8530dc-892b-475d-bcc0-ec46ba8767bc",
"name": "product_C",
"price": 3
},
{
"id": "7a74499f-dafc-43fa-81e0-f4988af28c3e",
"name": "product_D",
"price": 4
}
],
"pageable": {
"sort": {
"sorted": false,
"unsorted": true,
"empty": true
},
"pageNumber": 0,
"pageSize": 20,
"offset": 0,
"paged": true,
"unpaged": false
},
"last": true,
"totalElements": 4,
"totalPages": 1,
"first": true,
"numberOfElements": 4,
"size": 20,
"number": 0,
"sort": {
"sorted": false,
"unsorted": true,
"empty": true
},
"empty": false
}
與 Spring Data 一樣,我們使用某些查詢參數導航到不同的頁面,並通過擴展[WebMvcConfigurationSupport](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.html)
來配置默認屬性。
讓我們將默認頁面大小從 20 更改為 100,並通過重寫addArgumentResolvers
方法將默認頁面設置為 0:
@Configuration
public class CustomWebMvcConfigurationSupport extends WebMvcConfigurationSupport {
@Bean
public PageRequest defaultPageRequest() {
return PageRequest.of(0, 100);
}
@Override
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
SortHandlerMethodArgumentResolver argumentResolver = new SortHandlerMethodArgumentResolver();
argumentResolver.setSortParameter("sort");
PageableHandlerMethodArgumentResolver resolver = new PageableHandlerMethodArgumentResolver(argumentResolver);
resolver.setFallbackPageable(defaultPageRequest());
resolver.setPageParameterName("page");
resolver.setSizeParameterName("size");
argumentResolvers.add(resolver);
}
}
現在,我們可以從第 0 頁開始發出請求,最多 100 條記錄:
$ curl --location 'http://localhost:8080/products?page=0&size=50&sort=price,DESC'
如果不指定頁面和大小參數,則默認頁面索引為 0,每頁有 100 條記錄。但請求將頁面大小設置為 50:
{
"content": [
....
],
"pageable": {
"sort": {
"sorted": false,
"unsorted": true,
"empty": true
},
"pageNumber": 0,
"pageSize": 50,
"offset": 0,
"paged": true,
"unpaged": false
},
"last": true,
"totalElements": 4,
"totalPages": 1,
"first": true,
"numberOfElements": 4,
"size": 50,
"number": 0,
"sort": {
"sorted": false,
"unsorted": true,
"empty": true
},
"empty": false
}
5. 結論
在本文中,我們了解了 Spring Data Reactive 分頁的獨特本質。我們還實現了一個返回帶分頁的產品列表的端點。
與往常一樣,示例的源代碼可用 在 GitHub 上。與往常一樣,示例的源代碼可用 在 GitHub 上。