Query do DynamoDB em Java (AWS SDK v2)

O queryPaginator do AWS SDK for Java 2.x parece uma coleção e não é uma. Ele é um re-iterável preguiçoso, e a diferença aparece na sua conta na segunda vez que você percorre o laço. Para saber quando o Query é a leitura certa, veja 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());
        }
    }
}

O QueryIterable re-executa a consulta a cada iteração

Contra um fixture de 600 músicas, cada uma com ~3,9 KB e todas sob Artist = "Arturo Sandoval", o ddb.queryPaginator(request) retornou em 2,6 ms e não enviou nada. Depois o mesmo objeto QueryIterable foi percorrido duas vezes:

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

Dois laços for sobre uma variável, 569 unidades de leitura. As páginas nunca foram cacheadas; cada iteração percorre o LastEvaluatedKey desde o começo de novo. Se você precisa dos itens duas vezes, drene o iterável para uma List uma vez, como o exemplo faz.

O outro lado dessa mesma preguiça é onde os erros aparecem. Construa uma requisição cuja condição de chave omita a chave de partição e o queryPaginator a aceita sem reclamar, porque nenhuma chamada aconteceu ainda:

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

Um try-catch em volta do builder não captura nada. Ele precisa envolver o laço, e é por isso que o exemplo coloca o bloco inteiro dentro de um único try.

Medido em 2026-07-28 contra o DynamoDB Local (amazon/dynamodb-local) com software.amazon.awssdk:dynamodb 2.49.4 no OpenJDK 26.0.1.

Explicação

  • ddb.queryPaginator(request).items() achata as páginas em um Iterable<Map<String, AttributeValue>> e pagina por baixo dos panos, então o laço de dois níveis do exemplo vira um só quando você quer apenas os itens. Ele também é um SdkIterable, então .stream() funciona.
  • Números são String no builder. AttributeValue.builder().n("1994") não é um erro de digitação de .n(1994) — o setter n() recebe um java.lang.String, porque o DynamoDB transporta números como texto decimal para evitar arredondamento binário de ponto flutuante. Passar um int do Java não compila.
  • keyConditionExpression recebe uma igualdade na chave de partição mais no máximo uma condição de chave de classificação (=, <, <=, >, >=, BETWEEN, begins_with); .scanIndexForward(false) inverte a ordem e .indexName("...") redireciona para um índice secundário.
  • O Enhanced Client é a outra ergonomia. O software.amazon.awssdk.enhanced.dynamodb mapeia beans anotados em vez de Map<String, AttributeValue>, e o query dele retorna um PageIterable<T> com a mesma semântica de re-iteração preguiçosa medida acima.

Faça isso visualmente

Tanto #hashKey quanto #rangeKey neste exemplo são aliases que nada obriga você a usar: nem Artist nem SongTitle estão na lista de 573 palavras reservadas da AWS. O verificador de palavras reservadas te diz quais dos seus nomes de atributo de fato precisam do tratamento com #, para o mapa de aliases deixar de ser culto à carga.

Para experimentar uma condição de chave contra uma tabela real antes de construir o QueryRequest, baixe o DynoTable e navegue pelos resultados em uma grade.

Exemplos relacionados

Referências

Monte esta solicitação visualmente

Componha esta operação no Construtor de Consultas do DynamoDB gratuito — key condition, filtro, índice, Limit, ordem de classificação e um laço de paginação — e copie de volta como um programa executável para SDK v3, CLI ou boto3.

Abrir o Construtor de Consultas do DynamoDB

Trabalhe com o DynamoDB sem o Console

Um cliente desktop rápido para DynamoDB que roda o SQL de verdade que o DynamoDB não consegue — JOINs, GROUP BY, agregações — com edição visual e um agente de IA com suas próprias chaves do Bedrock.

Teste grátis de 30 dias, sem cartão de crédito — depois o plano Grátis sem limite de tempo.