Arbeiten Sie mit paginierten Ergebnissen: Scans und Abfragen - AWS SDK for Java 2.x

Die vorliegende Übersetzung wurde maschinell erstellt. Im Falle eines Konflikts oder eines Widerspruchs zwischen dieser übersetzten Fassung und der englischen Fassung (einschließlich infolge von Verzögerungen bei der Übersetzung) ist die englische Fassung maßgeblich.

Arbeiten Sie mit paginierten Ergebnissen: Scans und Abfragen

Die scan batch Methoden query und der DynamoDB Enhanced Client API geben Antworten mit einer oder mehreren Seiten zurück. Eine Seite enthält ein oder mehrere Elemente. Ihr Code kann die Antwort pro Seite oder einzelne Elemente verarbeiten.

Eine vom synchronen Client zurückgegebene paginierte Antwort gibt ein PageIterableObjekt zurück, wohingegen eine vom asynchronen DynamoDbEnhancedClient DynamoDbEnhancedAsyncClient Client zurückgegebene Antwort ein Objekt zurückgibt. PagePublisher

Dieser Abschnitt befasst sich mit der Verarbeitung paginierter Ergebnisse und enthält Beispiele, in denen die Scan- und Abfrage-APIs verwendet werden.

Scannen einer Tabelle

Die scanMethode des SDK entspricht der gleichnamigen DynamoDB-Operation. Die DynamoDB Enhanced Client API bietet dieselben Optionen, verwendet jedoch ein vertrautes Objektmodell und übernimmt die Paginierung für Sie.

Zunächst untersuchen wir die PageIterable Schnittstelle, indem wir uns die scan Methode der synchronen Mapping-Klasse ansehen,. DynamoDbTable

Verwenden Sie die synchrone API

Das folgende Beispiel zeigt die scan Methode, die einen Ausdruck verwendet, um die zurückgegebenen Elemente zu filtern. Das ProductCatalogist das Modellobjekt, das zuvor gezeigt wurde.

Der nach Kommentarzeile 2 angezeigte Filterausdruck beschränkt die Anzahl der zurückgegebenen ProductCatalog Artikel auf Artikel mit einem Preiswert zwischen 8,00 und 80,00 (einschließlich).

In diesem Beispiel werden auch die isbn Werte ausgeschlossen, indem die attributesToProject Methode verwendet wird, die nach Kommentarzeile 1 gezeigt wird.

Nach Kommentarzeile 3 wird das PageIterable Objekt,pagedResults, von der scan Methode zurückgegeben. Die stream Methode von PageIterable gibt ein java.util.StreamObjekt zurück, mit dem Sie die Seiten bearbeiten können. In diesem Beispiel wird die Anzahl der Seiten gezählt und protokolliert.

Beginnend mit Kommentarzeile 4 zeigt das Beispiel zwei Varianten des Zugriffs auf die ProductCatalog Elemente. Die Version nach der Kommentarzeile 4a durchläuft jede Seite und sortiert und protokolliert die Elemente auf jeder Seite. Die Version nach der Kommentarzeile 4b überspringt die Seiteniteration und greift direkt auf die Elemente zu.

Die PageIterable Schnittstelle bietet aufgrund ihrer beiden übergeordneten Schnittstellen — und — mehrere Möglichkeiten zur Verarbeitung von Ergebnissen. java.lang.IterableSdkIterable Iterablebringt forEach die spliterator Methoden iterator und und SdkIterable bringt die stream Methode.

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()) ); }

Verwenden Sie die asynchrone API

Die asynchrone scan Methode gibt Ergebnisse als PagePublisher Objekt zurück. Die PagePublisher Schnittstelle verfügt über zwei subscribe Methoden, mit denen Sie Antwortseiten verarbeiten können. Eine subscribe Methode stammt von der org.reactivestreams.Publisher übergeordneten Schnittstelle. Um Seiten mit dieser ersten Option zu verarbeiten, übergeben Sie der subscribe Methode eine Subscriber Instanz. Das erste Beispiel, das folgt, zeigt die Verwendung der subscribe Methode.

Die zweite subscribe Methode stammt von der SdkPublisherSchnittstelle. Diese Version von subscribe akzeptiert Consumereher a als Subscriber a. Diese subscribe Methodenvariante wird im zweiten Beispiel gezeigt, das folgt.

Das folgende Beispiel zeigt die asynchrone Version der scan Methode, die denselben Filterausdruck wie im vorherigen Beispiel verwendet.

Gibt nach Kommentarzeile 3 ein PagePublisher Objekt DynamoDbAsyncTable.scan zurück. In der nächsten Zeile erstellt der Code eine Instanz der org.reactivestreams.Subscriber SchnittstelleProductCatalogSubscriber, die die vierte Zeile PagePublisher nach dem Kommentar abonniert.

Das Subscriber Objekt sammelt die ProductCatalog Elemente von jeder Seite in der onNext Methode nach der Kommentarzeile 8 im ProductCatalogSubscriber Klassenbeispiel. Die Elemente werden in der privaten List Variablen gespeichert und im aufrufenden Code mit der ProductCatalogSubscriber.getSubscribedItems() Methode aufgerufen. Dies wird nach Kommentarzeile 5 aufgerufen.

Nachdem die Liste abgerufen wurde, sortiert der Code alle ProductCatalog Artikel nach Preis und protokolliert jeden Artikel.

Die Klasse CountDownLatchin der ProductCatalogSubscriber Klasse blockiert den aufrufenden Thread, bis alle Elemente zur Liste hinzugefügt wurden, bevor sie nach Kommentarzeile 5 weitermacht.

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; } }

Das folgende Codefragmentbeispiel verwendet die Version der PagePublisher.subscribe Methode, die eine Eingabe Consumer nach der Kommentarzeile 6 akzeptiert. Der Java-Lambda-Parameter verbraucht Seiten, die jedes Element weiterverarbeiten. In diesem Beispiel wird jede Seite verarbeitet und die Elemente auf jeder Seite werden sortiert und anschließend protokolliert.

// 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.

Die items Methode PagePublisher entpackt die Modellinstanzen, sodass Ihr Code die Elemente direkt verarbeiten kann. Dieser Ansatz wird im folgenden Snippet gezeigt.

// 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.

Abfragen einer Tabelle

Die query()Methode der DynamoDbTable Klasse findet Elemente auf der Grundlage von Primärschlüsselwerten. Die @DynamoDbPartitionKey Anmerkung und die optionale @DynamoDbSortKey Anmerkung werden verwendet, um den Primärschlüssel für Ihre Datenklasse zu definieren.

Die query() Methode erfordert einen Partitionsschlüsselwert, der Elemente findet, die dem angegebenen Wert entsprechen. Wenn Ihre Tabelle auch einen Sortierschlüssel definiert, können Sie Ihrer Abfrage einen Wert für diesen als zusätzliche Vergleichsbedingung hinzufügen, um die Ergebnisse zu optimieren.

Mit Ausnahme der Verarbeitung der Ergebnisse query() funktionieren die synchronen und asynchronen Versionen von genauso. Wie bei der scan API gibt die query API a PageIterable für einen synchronen Aufruf und a für einen asynchronen Aufruf zurück. PagePublisher Wir haben die Verwendung von PageIterable und PagePublisher bereits im Abschnitt „Scannen“ besprochen.

QueryBeispiele für Methoden

Das folgende query() Methodencodebeispiel verwendet die MovieActor Klasse. Die Datenklasse definiert einen zusammengesetzten Primärschlüssel, der aus dem movieAttribut für den Partitionsschlüssel und dem actorAttribut für den Sortierschlüssel besteht.

Die Klasse signalisiert auch, dass sie einen globalen sekundären Index mit dem Namen verwendet acting_award_year. Der zusammengesetzte Primärschlüssel des Indexes besteht aus dem actingawardAttribut für den Partitionsschlüssel und dem actingyearfür den Sortierschlüssel. Wenn wir später in diesem Thema zeigen, wie Indizes erstellt und verwendet werden, beziehen wir uns auf den acting_award_yearIndex.

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); } } }

In den folgenden Codebeispielen werden die folgenden Elemente abgefragt.

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'}

Der folgende Code definiert zwei QueryConditionalInstanzen. QueryConditionalsarbeiten mit Schlüsselwerten — entweder dem Partitionsschlüssel allein oder in Kombination mit dem Sortierschlüssel — und entsprechen den bedingten Schlüsselausdrücken der DynamoDB-Dienst-API. Nach der ersten Kommentarzeile definiert das Beispiel die keyEqual Instanz, die Elementen mit dem Partitionswert von entspricht. movie01

In diesem Beispiel wird auch ein Filterausdruck definiert, der alle Elemente herausfiltert, für die nach Kommentarzeile 2 kein actingschoolnameOn angegeben ist.

Nach Kommentarzeile 3 zeigt das Beispiel die QueryEnhancedRequestInstanz, die der Code an die DynamoDbTable.query() Methode übergibt. Dieses Objekt kombiniert die Schlüsselbedingung und den Filter, die das SDK verwendet, um die Anforderung an den DynamoDB-Dienst zu generieren.

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. );

Das Folgende ist das Ergebnis der Ausführung der Methode. In der Ausgabe werden Elemente mit dem movieName Wert movie01 und keine Elemente mit dem Wert actingSchoolName gleich angezeigt. 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'}

In der folgenden Variante der Abfrageanforderung, die zuvor nach Kommentarzeile 3 angezeigt wurde, ersetzt der Code die Variante durch die keyEqual QueryConditional Variante sortGreaterThanOrEqualToQueryConditional, die nach der Kommentarzeile 1a definiert wurde. Der folgende Code entfernt auch den Filterausdruck.

QueryEnhancedRequest tableQuery = QueryEnhancedRequest.builder() .queryConditional(sortGreaterThanOrEqualTo)

Da diese Tabelle einen zusammengesetzten Primärschlüssel hat, benötigen alle QueryConditional Instanzen einen Partitionsschlüsselwert. QueryConditionalMethoden, die mit beginnen, sort... geben an, dass ein Sortierschlüssel erforderlich ist. Die Ergebnisse sind nicht sortiert.

In der folgenden Ausgabe werden die Ergebnisse der Abfrage angezeigt. Die Abfrage gibt Elemente zurück, deren movieName Wert movie01 entspricht, und nur Elemente, deren actorName Wert größer oder gleich actor2 ist. Da der Filter entfernt wurde, gibt die Abfrage Elemente zurück, die keinen Wert für das Attribut haben. 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'}