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 objectDos 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 400Un 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 unIterable<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 unSdkIterable, así que.stream()funciona.- Los números son
Stringen el builder.AttributeValue.builder().n("1994")no es una errata de.n(1994)— el settern()toma unjava.lang.String, porque DynamoDB transporta los números como texto decimal para evitar el redondeo de los float binarios. Pasarle unintde Java no compila. keyConditionExpressionadmite 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.dynamodbmapea beans anotados en vez deMap<String, AttributeValue>, y suquerydevuelve unPageIterable<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
- DynamoDB Query en Go — la misma consulta con AWS SDK for Go v2.
- DynamoDB Scan en Java — cuando no puedes entrar por clave en una partición.
- Query vs. Scan — por qué
Queryes el valor por defecto correcto. - Expresiones de condición de clave — todos los operadores legales de clave de partición y de ordenación.
- "Query condition missed key schema element" — la condición de clave nombra el atributo equivocado o se salta la clave de partición.
- "Query key condition not supported" — un operador que la condición de clave no puede usar, como contains o una segunda condición sobre la clave de ordenación.