Las traducciones son generadas a través de traducción automática. En caso de conflicto entre la traducción y la version original de inglés, prevalecerá la version en inglés.
Trabajar con resultados paginados: análisis y consultas
Los métodos scan
, query
y batch
de la API de cliente mejorado de DynamoDB devuelven respuestas con una o varias páginas. Una página contiene uno o varios elementos. El código puede procesar la respuesta por página o procesar elementos individuales.
Una respuesta paginada devuelta por el DynamoDbEnhancedClient
cliente síncrono devuelve un PageIterableDynamoDbEnhancedAsyncClient
asíncrono devuelve un objeto. PagePublisher
En esta sección se analiza el procesamiento de los resultados paginados y se proporcionan ejemplos en los que se utilizan el escaneo y la consulta. APIs
Examinar una tabla
El método scan
En primer lugar, exploramos la PageIterable
interfaz analizando el scan
método de la clase de mapeo síncrono,. DynamoDbTable
Utilizar la API síncrona
El siguiente ejemplo muestra el método scan
que usa una expresión
La expresión de filtrado que se muestra después de la línea de comentario 2 limita los ProductCatalog
artículos devueltos a aquellos con un precio comprendido entre 8,00 y 80,00€, ambos inclusive.
En este ejemplo también se excluyen isbn
los valores mediante el attributesToProject
método que se muestra después de la línea de comentario 1.
Tras la línea de comentario 3pagedResults
, el scan
método devuelve el PageIterable
objeto,. El método stream
de PageIterable
devuelve un objeto java.util.Stream
Empezando por la línea de comentarios 4, el ejemplo muestra dos variantes del acceso a los elementos de ProductCatalog
. La versión posterior a la línea de comentarios 4a recorre cada página y ordena y registra los elementos de cada página. La versión siguiente a la línea de comentarios 4b omite la iteración de la página y accede a los elementos directamente.
La interfaz PageIterable
ofrece varias formas de procesar los resultados debido a sus dos interfaces principales, java.lang.Iterable
SdkIterable
Iterable
trae los métodos forEach
, iterator
y spliterator
, y SdkIterable
el método 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()) ); }
Utilizar la API asíncrona
El método scan
asíncrono devuelve los resultados como un objeto PagePublisher
. La interfaz de PagePublisher
tiene dos métodos subscribe
que puede utilizar para procesar las páginas de respuesta. Un método subscribe
proviene de la interfaz principal org.reactivestreams.Publisher
. Para procesar páginas con esta primera opción, pase una instancia Subscriber
al método subscribe
. En el ejemplo siguiente se muestra el uso del método subscribe
.
El segundo subscribe
método proviene de la interfaz. SdkPublishersubscribe
acepta un Consumer
Subscriber
. Esta variación del método subscribe
se muestra en el segundo ejemplo siguiente.
El ejemplo siguiente muestra la versión asíncrona del método scan
que utiliza la misma expresión de filtro que se muestra en el ejemplo anterior.
Tras la línea de comentario 3, DynamoDbAsyncTable.scan
devuelve un objeto PagePublisher
. En la siguiente línea, el código crea una instancia de la interfaz de org.reactivestreams.Subscriber
, ProductCatalogSubscriber
, que se suscribe a la PagePublisher
después de la línea de comentarios 4.
El objeto Subscriber
recopila los elementos ProductCatalog
de cada página del método onNext
después de la línea de comentarios 8 del ejemplo de clase ProductCatalogSubscriber
. Los elementos se almacenan en la variable List
privada y se accede a ellos en el código de llamada con el método ProductCatalogSubscriber.getSubscribedItems()
. Esto se invoca después de la línea de comentarios 5.
Una vez recuperada la lista, el código ordena todos los artículos ProductCatalog
por precio y registra cada uno de ellos.
El comando CountDownLatchProductCatalogSubscriber
class bloquea el hilo de llamada hasta que todos los elementos se hayan agregado a la lista antes de continuar después de la línea de comentarios 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; } }
En el siguiente ejemplo de fragmento, se utiliza la versión del método PagePublisher.subscribe
que acepta una Consumer
después de la línea de comentario 6. El parámetro lambda de Java consume páginas, que procesan aún más cada elemento. En este ejemplo, se procesa cada página y los elementos de cada página se ordenan y, a continuación, se registran.
// 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.
El método items
de PagePublisher
separa las instancias del modelo para que el código pueda procesar los elementos directamente. Este método se muestra en el fragmento de código siguiente.
// 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.
Consultar una tabla
Puede utilizar el cliente mejorado de DynamoDB para consultar la tabla y recuperar varios elementos que coincidan con criterios específicos. El query()
@DynamoDbPartitionKey
y las @DynamoDbSortKey
anotaciones opcionales definidas en la clase de datos.
El query()
método requiere un valor de clave de partición y, de forma opcional, acepta condiciones de clave de clasificación para refinar aún más los resultados. Al igual que la scan
API, las consultas devuelven una PageIterable
para las llamadas sincrónicas y una PagePublisher
para las asíncronas.
Ejemplos del método Query
El ejemplo de código del método query()
siguiente utiliza la clase MovieActor
. La clase de datos define una clave principal compuesta por el atributo como clave de partición y el movie
actor
atributo como clave de clasificación.
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); } } }
Los ejemplos de código que aparecen a continuación se refieren a los siguientes elementos.
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'}
El código siguiente define dos QueryConditional
instancias: keyEqual
(después de la línea de comentario 1) y sortGreaterThanOrEqualTo
(después de la línea de comentario 1a).
Consulta los elementos por clave de partición
La keyEqual
instancia hace coincidir los elementos con un valor de clave de partición de movie01
.
En este ejemplo también se define una expresión de filtro después de la línea de comentario 2 que filtra cualquier elemento que no tenga un actingschoolname
valor.
QueryEnhancedRequest
Combina la condición clave y la expresión de filtro de la consulta.
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. );
ejemplo — Salida mediante el condicional keyEqual
de consulta
Se genera la siguiente salida de la ejecución del método. El resultado muestra los elementos con un valor movieName
de movie01 y no muestra ningún elemento con actingSchoolName
igual a 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'}
Consulte los elementos por clave de partición y clave de clasificación
sortGreaterThanOrEqualTo
QueryConditional
Refina la coincidencia de una clave de partición (movie01) añadiendo una condición de clave de ordenación para valores superiores o iguales a actor2.
QueryConditional
los métodossort
requieren un valor de clave de partición para que coincida y refinan aún más la consulta mediante una comparación basada en el valor de la clave de clasificación. Sort
en el nombre del método no significa que los resultados estén ordenados, sino que se utilizará un valor de clave de clasificación para la comparación.
En el siguiente fragmento, cambiamos la solicitud de consulta que se muestra anteriormente después de la línea de comentarios 3. Este fragmento reemplaza el condicional de consulta «keyEqual» por el condicional de consulta «» que se definió después de la sortGreaterThan OrEqualTo línea de comentarios 1a. El código siguiente también elimina la expresión del filtro.
QueryEnhancedRequest tableQuery = QueryEnhancedRequest.builder() .queryConditional(sortGreaterThanOrEqualTo).build();
ejemplo — Salida mediante el condicional de consulta sortGreaterThanOrEqualTo
La siguiente salida muestra los resultados de la consulta. La consulta devuelve los elementos que tienen un valor movieName
igual a movie01 y solo los elementos que tienen un valor actorName
mayor o igual a actor2. Como eliminamos el filtro, la consulta devuelve elementos que no tienen ningún valor para el actingSchoolName
atributo.
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'}