本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。
使用分頁結果:掃描和查詢
DynamoDB 增強型用戶端的 scan
、 query
和 batch
方法會API傳回包含一或多個頁面 的回應。頁面包含一或多個項目。您的程式碼可以每頁處理回應,也可以處理個別項目。
同步DynamoDbEnhancedClient
用戶端傳回的分頁回應會傳回PageIterableDynamoDbEnhancedAsyncClient
傳回PagePublisher
本節會探討處理分頁結果,並提供使用掃描和查詢 的範例APIs。
掃描資料表
SDKscan
首先,我們會查看同步映射類別 scan
的方法來探索PageIterable
介面DynamoDbTable
使用同步 API
下列範例顯示使用 運算式scan
的方法。ProductCatalog 是先前顯示的模型物件。
備註行 2 之後顯示的篩選表達式會限制傳回至價格值介於 8.00 到 80.00 ProductCatalog
的項目。
此範例也會使用註解行 1 之後顯示attributesToProject
的方法排除isbn
值。
在註解行 3 之後, PageIterable
物件 pagedResults
會由 scan
方法傳回。PageIterable
傳回java.util.Stream
stream
的方法,可用於處理頁面。在此範例中,會計算和記錄頁數。
從註解行 4 開始,範例顯示存取ProductCatalog
項目的兩種變化。註解行 4a 後的版本會串流瀏覽每個頁面,並排序和記錄每個頁面上的項目。註解行 4b 後的版本會略過頁面迭代,並直接存取項目。
由於PageIterable
介面的兩個父介面 java.lang.Iterable
SdkIterable
Iterable
具有 forEach
、 iterator
和 spliterator
方法,並SdkIterable
具有 stream
方法。
public static void scanSync(DynamoDbTable<ProductCatalog> productCatalog) { Map<String, AttributeValue> expressionValues = Map.of( ":min_value", numberValue(8.00), ":max_value", numberValue(80.00)); ScanEnhancedRequest request = ScanEnhancedRequest.builder() .consistentRead(true) // 1. the 'attributesToProject()' method allows you to specify which values you want returned. .attributesToProject("id", "title", "authors", "price") // 2. Filter expression limits the items returned that match the provided criteria. .filterExpression(Expression.builder() .expression("price >= :min_value AND price <= :max_value") .expressionValues(expressionValues) .build()) .build(); // 3. A PageIterable object is returned by the scan method. PageIterable<ProductCatalog> pagedResults = productCatalog.scan(request); logger.info("page count: {}", pagedResults.stream().count()); // 4. Log the returned ProductCatalog items using two variations. // 4a. This version sorts and logs the items of each page. pagedResults.stream().forEach(p -> p.items().stream() .sorted(Comparator.comparing(ProductCatalog::price)) .forEach( item -> logger.info(item.toString()) )); // 4b. This version sorts and logs all items for all pages. pagedResults.items().stream() .sorted(Comparator.comparing(ProductCatalog::price)) .forEach( item -> logger.info(item.toString()) ); }
使用非同步 API
非同步scan
方法會將結果傳回為PagePublisher
物件。此PagePublisher
介面有兩種subscribe
方法可讓您用來處理回應頁面。一種subscribe
方法來自org.reactivestreams.Publisher
父介面。若要使用此第一個選項處理頁面,請將Subscriber
執行個體傳遞至 subscribe
方法。下列第一個範例顯示 subscribe
方法的使用。
第二個subscribe
方法來自 SdkPublishersubscribe
接受 ,Consumer
Subscriber
。此subscribe
方法變化會顯示在下列第二個範例中。
下列範例顯示使用上一個範例中所示相同篩選條件表達式scan
方法的非同步版本。
註解行 3 之後, DynamoDbAsyncTable.scan
會傳回PagePublisher
物件。在下一行中,程式碼會建立org.reactivestreams.Subscriber
介面的執行個體 ProductCatalogSubscriber
,其會在註解第 4 行PagePublisher
之後訂閱 。
Subscriber
物件會在ProductCatalogSubscriber
類別範例中的註解行 8 之後,從onNext
方法中的每個頁面收集ProductCatalog
項目。項目會儲存在私有List
變數中,並使用 ProductCatalogSubscriber.getSubscribedItems()
方法在呼叫程式碼中存取。這在註解行 5 之後呼叫。
擷取清單後,程式碼會依價格排序所有ProductCatalog
項目,並記錄每個項目。
ProductCatalogSubscriber
類別CountDownLatch
public static void scanAsync(DynamoDbAsyncTable productCatalog) { ScanEnhancedRequest request = ScanEnhancedRequest.builder() .consistentRead(true) .attributesToProject("id", "title", "authors", "price") .filterExpression(Expression.builder() // 1. :min_value and :max_value are placeholders for the values provided by the map .expression("price >= :min_value AND price <= :max_value") // 2. Two values are needed for the expression and each is supplied as a map entry. .expressionValues( Map.of( ":min_value", numberValue(8.00), ":max_value", numberValue(400_000.00))) .build()) .build(); // 3. A PagePublisher object is returned by the scan method. PagePublisher<ProductCatalog> pagePublisher = productCatalog.scan(request); ProductCatalogSubscriber subscriber = new ProductCatalogSubscriber(); // 4. Subscribe the ProductCatalogSubscriber to the PagePublisher. pagePublisher.subscribe(subscriber); // 5. Retrieve all collected ProductCatalog items accumulated by the subscriber. subscriber.getSubscribedItems().stream() .sorted(Comparator.comparing(ProductCatalog::price)) .forEach(item -> logger.info(item.toString())); // 6. Use a Consumer to work through each page. pagePublisher.subscribe(page -> page .items().stream() .sorted(Comparator.comparing(ProductCatalog::price)) .forEach(item -> logger.info(item.toString()))) .join(); // If needed, blocks the subscribe() method thread until it is finished processing. // 7. Use a Consumer to work through each ProductCatalog item. pagePublisher.items() .subscribe(product -> logger.info(product.toString())) .exceptionally(failure -> { logger.error("ERROR - ", failure); return null; }) .join(); // If needed, blocks the subscribe() method thread until it is finished processing. }
private static class ProductCatalogSubscriber implements Subscriber<Page<ProductCatalog>> { private CountDownLatch latch = new CountDownLatch(1); private Subscription subscription; private List<ProductCatalog> itemsFromAllPages = new ArrayList<>(); @Override public void onSubscribe(Subscription sub) { subscription = sub; subscription.request(1L); try { latch.await(); // Called by main thread blocking it until latch is released. } catch (InterruptedException e) { throw new RuntimeException(e); } } @Override public void onNext(Page<ProductCatalog> productCatalogPage) { // 8. Collect all the ProductCatalog instances in the page, then ask the publisher for one more page. itemsFromAllPages.addAll(productCatalogPage.items()); subscription.request(1L); } @Override public void onError(Throwable throwable) { } @Override public void onComplete() { latch.countDown(); // Call by subscription thread; latch releases. } List<ProductCatalog> getSubscribedItems() { return this.itemsFromAllPages; } }
下列程式碼片段範例使用 PagePublisher.subscribe
方法的版本,該方法接受 Consumer
後評論行 6。Java lambda 參數會耗用頁面,進一步處理每個項目。在此範例中,會處理每個頁面,然後對每個頁面上的項目進行排序和記錄。
// 6. Use a Consumer to work through each page. pagePublisher.subscribe(page -> page .items().stream() .sorted(Comparator.comparing(ProductCatalog::price)) .forEach(item -> logger.info(item.toString()))) .join(); // If needed, blocks the subscribe() method thread until it is finished processing.
PagePublisher
展開模型執行個體items
的方法,讓您的程式碼可以直接處理項目。此方法會顯示在下列程式碼片段中。
// 7. Use a Consumer to work through each ProductCatalog item. pagePublisher.items() .subscribe(product -> logger.info(product.toString())) .exceptionally(failure -> { logger.error("ERROR - ", failure); return null; }) .join(); // If needed, blocks the subscribe() method thread until it is finished processing.
查詢資料表
DynamoDbTable
類別query()
@DynamoDbPartitionKey
註釋和選用@DynamoDbSortKey
註釋用於定義資料類別的主要索引鍵。
此query()
方法需要分割區金鑰值,該值會尋找符合所提供值的項目。如果您的資料表也定義排序索引鍵,您可以將其值新增至查詢,作為額外的比較條件,以微調結果。
除了處理結果之外,同步和非同步版本query()
的運作方式也相同。與 scan
一樣API, PageIterable
會針對同步呼叫query
API傳回 ,針對PagePublisher
非同步呼叫傳回 。我們PagePublisher
先前在掃描區段中討論了 PageIterable
和 的使用。
Query
方法範例
下列query()
方法程式碼範例使用 MovieActor
類別。資料類別定義複合主金鑰,由分割區金鑰的movie
屬性和排序金鑰的actor
屬性組成。
類別也會發出訊號,表示其使用名為 的全域次要索引acting_award_year
。索引的複合主索引鍵是由分割區索引鍵的屬性和排序索引鍵actingyear
的 actingaward
組成。在本主題稍後,當我們展示如何建立和使用索引時,我們會參考索引acting_award_year
。
package org.example.tests.model; import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondaryPartitionKey; import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondarySortKey; import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey; import java.util.Objects; @DynamoDbBean public class MovieActor implements Comparable<MovieActor> { private String movieName; private String actorName; private String actingAward; private Integer actingYear; private String actingSchoolName; @DynamoDbPartitionKey @DynamoDbAttribute("movie") public String getMovieName() { return movieName; } public void setMovieName(String movieName) { this.movieName = movieName; } @DynamoDbSortKey @DynamoDbAttribute("actor") public String getActorName() { return actorName; } public void setActorName(String actorName) { this.actorName = actorName; } @DynamoDbSecondaryPartitionKey(indexNames = "acting_award_year") @DynamoDbAttribute("actingaward") public String getActingAward() { return actingAward; } public void setActingAward(String actingAward) { this.actingAward = actingAward; } @DynamoDbSecondarySortKey(indexNames = {"acting_award_year", "movie_year"}) @DynamoDbAttribute("actingyear") public Integer getActingYear() { return actingYear; } public void setActingYear(Integer actingYear) { this.actingYear = actingYear; } @DynamoDbAttribute("actingschoolname") public String getActingSchoolName() { return actingSchoolName; } public void setActingSchoolName(String actingSchoolName) { this.actingSchoolName = actingSchoolName; } @Override public String toString() { final StringBuffer sb = new StringBuffer("MovieActor{"); sb.append("movieName='").append(movieName).append('\''); sb.append(", actorName='").append(actorName).append('\''); sb.append(", actingAward='").append(actingAward).append('\''); sb.append(", actingYear=").append(actingYear); sb.append(", actingSchoolName='").append(actingSchoolName).append('\''); sb.append('}'); return sb.toString(); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; MovieActor that = (MovieActor) o; return Objects.equals(movieName, that.movieName) && Objects.equals(actorName, that.actorName) && Objects.equals(actingAward, that.actingAward) && Objects.equals(actingYear, that.actingYear) && Objects.equals(actingSchoolName, that.actingSchoolName); } @Override public int hashCode() { return Objects.hash(movieName, actorName, actingAward, actingYear, actingSchoolName); } @Override public int compareTo(MovieActor o) { if (this.movieName.compareTo(o.movieName) != 0){ return this.movieName.compareTo(o.movieName); } else { return this.actorName.compareTo(o.actorName); } } }
對下列項目進行查詢之後的程式碼範例。
MovieActor{movieName='movie01', actorName='actor0', actingAward='actingaward0', actingYear=2001, actingSchoolName='null'} MovieActor{movieName='movie01', actorName='actor1', actingAward='actingaward1', actingYear=2001, actingSchoolName='actingschool1'} MovieActor{movieName='movie01', actorName='actor2', actingAward='actingaward2', actingYear=2001, actingSchoolName='actingschool2'} MovieActor{movieName='movie01', actorName='actor3', actingAward='actingaward3', actingYear=2001, actingSchoolName='null'} MovieActor{movieName='movie01', actorName='actor4', actingAward='actingaward4', actingYear=2001, actingSchoolName='actingschool4'} MovieActor{movieName='movie02', actorName='actor0', actingAward='actingaward0', actingYear=2002, actingSchoolName='null'} MovieActor{movieName='movie02', actorName='actor1', actingAward='actingaward1', actingYear=2002, actingSchoolName='actingschool1'} MovieActor{movieName='movie02', actorName='actor2', actingAward='actingaward2', actingYear=2002, actingSchoolName='actingschool2'} MovieActor{movieName='movie02', actorName='actor3', actingAward='actingaward3', actingYear=2002, actingSchoolName='null'} MovieActor{movieName='movie02', actorName='actor4', actingAward='actingaward4', actingYear=2002, actingSchoolName='actingschool4'} MovieActor{movieName='movie03', actorName='actor0', actingAward='actingaward0', actingYear=2003, actingSchoolName='null'} MovieActor{movieName='movie03', actorName='actor1', actingAward='actingaward1', actingYear=2003, actingSchoolName='actingschool1'} MovieActor{movieName='movie03', actorName='actor2', actingAward='actingaward2', actingYear=2003, actingSchoolName='actingschool2'} MovieActor{movieName='movie03', actorName='actor3', actingAward='actingaward3', actingYear=2003, actingSchoolName='null'} MovieActor{movieName='movie03', actorName='actor4', actingAward='actingaward4', actingYear=2003, actingSchoolName='actingschool4'}
下列程式碼會定義兩個QueryConditionalQueryConditionals
使用金鑰值,無論是單獨使用分割區金鑰還是搭配排序金鑰使用,並且對應至 DynamoDB 服務 的金鑰條件式表達式API。註解行 1 之後,範例會定義符合分割區值為 之項目的keyEqual
執行個體movie01
。
此範例也會定義篩選條件表達式,以篩選評論行 2 後沒有 actingschoolname
開啟的任何項目。
註解行 3 之後,範例會顯示程式碼傳遞至DynamoDbTable.query()
方法的QueryEnhancedRequest
public static void query(DynamoDbTable movieActorTable) { // 1. Define a QueryConditional instance to return items matching a partition value. QueryConditional keyEqual = QueryConditional.keyEqualTo(b -> b.partitionValue("movie01")); // 1a. Define a QueryConditional that adds a sort key criteria to the partition value criteria. QueryConditional sortGreaterThanOrEqualTo = QueryConditional.sortGreaterThanOrEqualTo(b -> b.partitionValue("movie01").sortValue("actor2")); // 2. Define a filter expression that filters out items whose attribute value is null. final Expression filterOutNoActingschoolname = Expression.builder().expression("attribute_exists(actingschoolname)").build(); // 3. Build the query request. QueryEnhancedRequest tableQuery = QueryEnhancedRequest.builder() .queryConditional(keyEqual) .filterExpression(filterOutNoActingschoolname) .build(); // 4. Perform the query. PageIterable<MovieActor> pagedResults = movieActorTable.query(tableQuery); logger.info("page count: {}", pagedResults.stream().count()); // Log number of pages. pagedResults.items().stream() .sorted() .forEach( item -> logger.info(item.toString()) // Log the sorted list of items. );
以下是執行 方法的輸出。輸出會顯示movieName
值為 movie01 的項目,且不會顯示actingSchoolName
等於 的項目null
。
2023-03-05 13:11:05 [main] INFO org.example.tests.QueryDemo:46 - page count: 1 2023-03-05 13:11:05 [main] INFO org.example.tests.QueryDemo:51 - MovieActor{movieName='movie01', actorName='actor1', actingAward='actingaward1', actingYear=2001, actingSchoolName='actingschool1'} 2023-03-05 13:11:05 [main] INFO org.example.tests.QueryDemo:51 - MovieActor{movieName='movie01', actorName='actor2', actingAward='actingaward2', actingYear=2001, actingSchoolName='actingschool2'} 2023-03-05 13:11:05 [main] INFO org.example.tests.QueryDemo:51 - MovieActor{movieName='movie01', actorName='actor4', actingAward='actingaward4', actingYear=2001, actingSchoolName='actingschool4'}
在以下查詢請求變體中,先前在註解行 3 之後顯示,程式碼會將 取代keyEqual
QueryConditional
為註解行 1a 之後sortGreaterThanOrEqualTo
QueryConditional
定義的 。下列程式碼也會移除篩選條件表達式。
QueryEnhancedRequest tableQuery = QueryEnhancedRequest.builder() .queryConditional(sortGreaterThanOrEqualTo)
由於此資料表具有複合主索引鍵,因此所有QueryConditional
執行個體都需要分割區索引鍵值。以 開頭QueryConditional
的方法sort...
表示需要排序索引鍵。結果不會排序。
下列輸出會顯示查詢的結果。查詢會傳回movieName
值等於 movie01 的項目,且只有actorName
值大於或等於 actor2 的項目。由於已移除篩選條件,查詢會傳回屬性沒有值的項目actingSchoolName
。
2023-03-05 13:15:00 [main] INFO org.example.tests.QueryDemo:46 - page count: 1 2023-03-05 13:15:00 [main] INFO org.example.tests.QueryDemo:51 - MovieActor{movieName='movie01', actorName='actor2', actingAward='actingaward2', actingYear=2001, actingSchoolName='actingschool2'} 2023-03-05 13:15:00 [main] INFO org.example.tests.QueryDemo:51 - MovieActor{movieName='movie01', actorName='actor3', actingAward='actingaward3', actingYear=2001, actingSchoolName='null'} 2023-03-05 13:15:00 [main] INFO org.example.tests.QueryDemo:51 - MovieActor{movieName='movie01', actorName='actor4', actingAward='actingaward4', actingYear=2001, actingSchoolName='actingschool4'}