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 des API de numérisation et de requête.

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

La query()méthode de la DynamoDbTable classe trouve des éléments en fonction des valeurs des clés primaires. L'@DynamoDbPartitionKeyannotation et l'@DynamoDbSortKeyannotation facultative sont utilisées pour définir la clé primaire de votre classe de données.

La query() méthode nécessite une valeur de clé de partition qui trouve les éléments correspondant à la valeur fournie. Si votre table définit également une clé de tri, vous pouvez ajouter une valeur à cette clé à votre requête en tant que condition de comparaison supplémentaire pour affiner les résultats.

À l'exception du traitement des résultats, les versions synchrone et asynchrone de query() fonctionnent de la même manière. Comme pour l'scanAPI, l'queryAPI renvoie un PageIterable pour un appel synchrone et un PagePublisher pour un appel asynchrone. Nous avons discuté de l'utilisation de PageIterable et PagePublisher précédemment dans la section de numérisation.

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 de la clé de partition et de l'actorattribut de la clé de tri.

La classe indique également qu'elle utilise un index secondaire global nommé acting_award_year. La clé primaire composite de l'index est composée de l'actingawardattribut de la clé de partition et de l'attribut actingyearde la clé de tri. Plus loin dans cette rubrique, lorsque nous montrerons comment créer et utiliser des index, nous ferons référence à l'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); } } }

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 QueryConditionalinstances. QueryConditionalsfonctionnent avec des valeurs clés (la clé de partition seule ou en combinaison avec la clé de tri) et correspondent aux principales expressions conditionnelles de l'API du service DynamoDB. Après la ligne de commentaire 1, l'exemple définit l'keyEqualinstance qui correspond aux éléments dont la valeur de partition est de movie01.

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

Après la ligne de commentaire 3, l'exemple montre l'QueryEnhancedRequestinstance que le code transmet à la DynamoDbTable.query() méthode. Cet objet combine la condition clé et le filtre utilisés par le SDK pour générer la demande au service DynamoDB.

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

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

Dans la variante de demande de requête suivante présentée précédemment après la ligne de commentaire 3, le code remplace le keyEqual QueryConditional par sortGreaterThanOrEqualTo QueryConditional celui qui a été défini après la ligne de commentaire 1a. Le code suivant supprime également l'expression du filtre.

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

Comme cette table possède une clé primaire composite, toutes les QueryConditional instances nécessitent une valeur de clé de partition. QueryConditionalles méthodes qui commencent par sort... indiquent qu'une clé de tri est requise. Les résultats ne sont pas triés.

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. Le filtre ayant été supprimé, 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'}