DynamoDB Query en Java (AWS SDK v2)

queryPaginator en el AWS SDK for Java 2.x parece una colección y no lo es. Es un iterable perezoso y reiterable, y la diferencia aparece en tu factura la segunda vez que haces un bucle sobre él. Para saber cuándo Query es la lectura adecuada, mira Query vs. Scan.

Código

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import software.amazon.awssdk.services.dynamodb.model.QueryRequest;
import software.amazon.awssdk.services.dynamodb.model.QueryResponse;

public class QueryExample {
    public static void main(String[] args) {
        try (DynamoDbClient ddb = DynamoDbClient.builder()
                .region(Region.US_EAST_1)
                .build()) {

            Map<String, String> names = new HashMap<>();
            names.put("#hashKey", "Artist");
            names.put("#rangeKey", "SongTitle");

            Map<String, AttributeValue> values = new HashMap<>();
            values.put(":hashKeyValue", AttributeValue.builder().s("Arturo Sandoval").build());
            values.put(":rangeKeyValue", AttributeValue.builder().s("C").build());

            QueryRequest request = QueryRequest.builder()
                    .tableName("Music")
                    .keyConditionExpression(
                            "#hashKey = :hashKeyValue AND begins_with(#rangeKey, :rangeKeyValue)")
                    .expressionAttributeNames(names)
                    .expressionAttributeValues(values)
                    .build();

            List<Map<String, AttributeValue>> items = new ArrayList<>();
            for (QueryResponse page : ddb.queryPaginator(request)) {
                items.addAll(page.items());
            }
            System.out.println("Found " + items.size() + " items");
        } catch (DynamoDbException e) {
            System.err.println(e.getMessage());
        }
    }
}

QueryIterable reejecuta la consulta cada vez que lo iteras

Contra un conjunto de prueba de 600 canciones, cada una de ~3,9 KB y todas bajo Artist = "Arturo Sandoval", ddb.queryPaginator(request) devolvió en 2,6 ms y no envió nada. Después se recorrió dos veces el mismo objeto QueryIterable:

queryPaginator(request) returned in 2.60 ms   (class QueryIterable, 0 requests)
iteration 1: pages=3  items=600  capacity=284.5
iteration 2: pages=3  items=600  capacity=284.5   <- same object

Dos bucles for sobre una variable, 569 unidades de lectura. Las páginas nunca se cachearon; cada iteración vuelve a recorrer LastEvaluatedKey desde el principio. Si necesitas los Items dos veces, vuelca el iterable en una List una sola vez, como hace el ejemplo.

La otra cara de esa misma pereza es dónde afloran los errores. Construye una petición cuya condición de clave omita la clave de partición y queryPaginator la acepta sin quejarse, porque todavía no ha habido ninguna llamada:

queryPaginator(bad) constructed without throwing
threw on iteration: DynamoDbException / ValidationException /
Query condition missed key schema element / http 400

Un try-catch alrededor del builder no captura nada. Tiene que envolver el bucle, y por eso el ejemplo mete el bloque entero dentro de un solo try.

Medido el 2026-07-28 contra DynamoDB Local (amazon/dynamodb-local) con software.amazon.awssdk:dynamodb 2.49.4 sobre OpenJDK 26.0.1.

Explicación

  • ddb.queryPaginator(request).items() aplana las páginas en un Iterable<Map<String, AttributeValue>> y pagina por detrás, así que el bucle de dos niveles del ejemplo se reduce a uno cuando solo quieres los Items. Además es un SdkIterable, así que .stream() funciona.
  • Los números son String en el builder. AttributeValue.builder().n("1994") no es una errata de .n(1994) — el setter n() toma un java.lang.String, porque DynamoDB transporta los números como texto decimal para evitar el redondeo de los float binarios. Pasarle un int de Java no compila.
  • keyConditionExpression admite una igualdad sobre la clave de partición más como mucho una condición sobre la clave de ordenación (=, <, <=, >, >=, BETWEEN, begins_with); .scanIndexForward(false) invierte el orden y .indexName("...") redirige a un índice secundario.
  • El Enhanced Client es la otra ergonomía. software.amazon.awssdk.enhanced.dynamodb mapea beans anotados en vez de Map<String, AttributeValue>, y su query devuelve un PageIterable<T> con la misma semántica de reiteración perezosa medida arriba.

Hazlo visualmente

Tanto #hashKey como #rangeKey en este ejemplo son alias que nadie te obliga a usar: ni Artist ni SongTitle están en la lista de 573 palabras reservadas de AWS. El comprobador de palabras reservadas te dice cuáles de tus nombres de atributo necesitan de verdad el tratamiento con #, para que el mapa de alias deje de ser un ritual.

Para probar una condición de clave contra una tabla real antes de construir el QueryRequest, descarga DynoTable y pagina por los resultados en una cuadrícula.

Ejemplos relacionados

Referencias

Construye esta solicitud visualmente

Compón esta operación en el Generador de consultas de DynamoDB gratuito —condición de clave, filtro, índice, Limit, orden de clasificación y un bucle de paginación— y cópiala de vuelta como un programa ejecutable para SDK v3, CLI o boto3.

Abrir el Generador de consultas de DynamoDB

Trabaja con DynamoDB sin la Consola

Un cliente de escritorio rápido para DynamoDB que ejecuta el SQL real que DynamoDB no puede — JOINs, GROUP BY, agregaciones — con edición visual y un agente de IA con tus propias claves de Bedrock.

Prueba gratuita de 30 días, sin tarjeta — después, el plan Free sin límite de tiempo.