Travailler avec des résultats paginés : scans et requêtes - AWS SDK for Java 2.x

Les traductions sont fournies par des outils de traduction automatique. En cas de conflit entre le contenu d'une traduction et celui de la version originale en anglais, la version anglaise prévaudra.

Travailler avec des résultats paginés : scans et requêtes

Les scan batch méthodes query et de l'API DynamoDB Enhanced Client renvoient des réponses contenant une ou plusieurs pages. Une page contient un ou plusieurs éléments. Votre code peut traiter la réponse page par page ou traiter des éléments individuels.

Une réponse paginée renvoyée par le DynamoDbEnhancedClient client synchrone renvoie un PageIterableobjet, tandis qu'une réponse renvoyée par le client asynchrone DynamoDbEnhancedAsyncClient renvoie un objet. PagePublisher

Cette section traite du traitement des résultats paginés et fournit des exemples d'utilisation du scan et de la requête APIs.

Analyser une table

La scanméthode du SDK correspond à l'opération DynamoDB du même nom. L'API DynamoDB Enhanced Client propose les mêmes options, mais elle utilise un modèle d'objet familier et gère la pagination à votre place.

Tout d'abord, nous explorons l'PageIterableinterface en examinant la scan méthode de la classe de mappage synchrone, DynamoDbTable.

Utiliser l'API synchrone

L'exemple suivant montre la scan méthode qui utilise une expression pour filtrer les éléments renvoyés. ProductCatalogIl s'agit de l'objet modèle présenté précédemment.

L'expression de filtrage affichée après la ligne de commentaire 2 limite les ProductCatalog articles renvoyés à ceux dont le prix est compris entre 8,00 et 80,00€ inclusivement.

Cet exemple exclut également les isbn valeurs en utilisant la attributesToProject méthode indiquée après la ligne de commentaire 1.

Après la ligne de commentaire 3pagedResults, l'PageIterableobjet est renvoyé par la scan méthode. La stream méthode de PageIterable renvoi d'un java.util.Streamobjet, que vous pouvez utiliser pour traiter les pages. Dans cet exemple, le nombre de pages est compté et enregistré.

À partir de la ligne de commentaire 4, l'exemple montre deux variantes d'accès aux ProductCatalog éléments. La version située après la ligne de commentaire 4a parcourt chaque page et trie et enregistre les éléments de chaque page. La version située après la ligne de commentaire 4b ignore l'itération de la page et accède directement aux éléments.

L'PageIterableinterface offre plusieurs manières de traiter les résultats grâce à ses deux interfaces parentes : java.lang.Iterableet SdkIterable. Iterableapporte les forEach spliterator méthodes, et SdkIterable apporte la stream méthode. iterator

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

Utiliser l'API asynchrone

La scan méthode asynchrone renvoie les résultats sous forme d'PagePublisherobjet. L'PagePublisherinterface comporte deux subscribe méthodes que vous pouvez utiliser pour traiter les pages de réponse. L'une des subscribe méthodes provient de l'interface org.reactivestreams.Publisher parent. Pour traiter les pages à l'aide de cette première option, transmettez une Subscriber instance à la subscribe méthode. Le premier exemple qui suit montre l'utilisation de la subscribe méthode.

La deuxième subscribe méthode provient de l'SdkPublisherinterface. Cette version de subscribe accepte un Consumerplutôt qu'unSubscriber. Cette variante de subscribe méthode est illustrée dans le deuxième exemple qui suit.

L'exemple suivant montre la version asynchrone de la scan méthode qui utilise la même expression de filtre que dans l'exemple précédent.

Après la ligne de commentaire 3, DynamoDbAsyncTable.scan renvoie un PagePublisher objet. Sur la ligne suivante, le code crée une instance de l'org.reactivestreams.SubscriberinterfaceProductCatalogSubscriber, qui s'abonne à la ligne de commentaire 4 PagePublisher après.

L'Subscriberobjet collecte les ProductCatalog éléments de chaque page de la onNext méthode après la ligne de commentaire 8 dans l'exemple ProductCatalogSubscriber de classe. Les éléments sont stockés dans la List variable privée et sont accessibles dans le code d'appel avec la ProductCatalogSubscriber.getSubscribedItems() méthode. Ceci est appelé après la ligne de commentaire 5.

Une fois la liste récupérée, le code trie tous les ProductCatalog articles par prix et enregistre chaque article.

La ProductCatalogSubscriber classe CountDownLatchin bloque le fil d'appel jusqu'à ce que tous les éléments aient été ajoutés à la liste avant de continuer après la ligne de commentaire 5.

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

L'exemple d'extrait de code suivant utilise la version de la PagePublisher.subscribe méthode qui accepte une ligne de commentaire Consumer 6 après. Le paramètre Java lambda consomme des pages, qui traitent ensuite chaque élément. Dans cet exemple, chaque page est traitée et les éléments de chaque page sont triés puis enregistrés.

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

La items méthode qui consiste à PagePublisher déballer les instances du modèle afin que votre code puisse traiter les éléments directement. Cette approche est illustrée dans l'extrait suivant.

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

Interroger une table

Vous pouvez utiliser le client DynamoDB amélioré pour interroger votre table et récupérer plusieurs éléments répondant à des critères spécifiques. La query()méthode recherche des éléments en fonction des valeurs de clé primaire à l'@DynamoDbPartitionKeyaide @DynamoDbSortKey des annotations facultatives définies dans votre classe de données.

La query() méthode nécessite une valeur de clé de partition et accepte éventuellement les conditions de clé de tri pour affiner davantage les résultats. Comme l'scanAPI, les requêtes renvoient a PageIterable pour les appels synchrones et a PagePublisher pour les appels asynchrones.

Queryexemples de méthodes

L'exemple de code de query() méthode qui suit utilise la MovieActor classe. La classe de données définit une clé primaire composite composée de l'movieattribut en tant que clé de partition et de l'actorattribut en tant que clé de tri.

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

Les exemples de code qui suivent portent sur les éléments suivants.

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

Le code suivant définit deux QueryConditional instances : keyEqual (après la ligne de commentaire 1) et sortGreaterThanOrEqualTo (après la ligne de commentaire 1a).

Éléments de requête par clé de partition

L'keyEqualinstance fait correspondre les éléments dont la valeur de clé de partition est de movie01.

Cet exemple définit également une expression de filtre après la ligne de commentaire 2 qui filtre tout élément dépourvu de actingschoolnamevaleur.

QueryEnhancedRequestCombine la condition clé et l'expression du filtre pour la requête.

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 using the "keyEqual" conditional and filter expression. 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. );
Exemple — Sortie utilisant la keyEqual requête conditionnelle

Voici le résultat de l'exécution de la méthode. La sortie affiche les éléments dont movieName la valeur est movie01 et n'affiche aucun élément dont la valeur est actingSchoolName égale à. 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'}

Rechercher des éléments par clé de partition et clé de tri

sortGreaterThanOrEqualToQueryConditionalAffine une correspondance de clé de partition (movie01) en ajoutant une condition de clé de tri pour les valeurs supérieures ou égales à actor2.

QueryConditionalles méthodes qui commencent par sort nécessitent qu'une valeur de clé de partition corresponde et affinent davantage la requête par une comparaison basée sur la valeur de la clé de tri. Sortdans le nom de la méthode ne signifie pas que les résultats sont triés, mais qu'une valeur de clé de tri sera utilisée pour la comparaison.

Dans l'extrait suivant, nous modifions la demande de requête affichée précédemment après la ligne de commentaire 3. Cet extrait remplace la requête conditionnelle « KeyEqual » par la requête conditionnelle « sortGreaterThan OrEqualTo » définie après la ligne de commentaire 1a. Le code suivant supprime également l'expression du filtre.

QueryEnhancedRequest tableQuery = QueryEnhancedRequest.builder() .queryConditional(sortGreaterThanOrEqualTo).build();
Exemple — Sortie utilisant la sortGreaterThanOrEqualTo requête conditionnelle

La sortie suivante affiche les résultats de la requête. La requête renvoie les éléments dont movieName la valeur est égale à movie01 et uniquement les éléments dont actorName la valeur est supérieure ou égale à actor2. Comme nous supprimons le filtre, la requête renvoie des éléments qui n'ont aucune valeur pour l'actingSchoolNameattribut.

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