DynamoDB Query in Java (AWS SDK v2)

queryPaginator nell'AWS SDK for Java 2.x sembra una collection e non lo è. È un iterabile pigro e ri-percorribile, e la differenza si vede sulla tua bolletta la seconda volta che ci fai un loop sopra. Per capire se Query è la lettura giusta, vedi Query vs Scan.

Codice

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 riesegue la query ogni volta che la iteri

Su una fixture di 600 canzoni, ognuna da ~3,9 KB e tutte sotto Artist = "Arturo Sandoval", ddb.queryPaginator(request) è tornato in 2,6 ms senza inviare nulla. Poi lo stesso oggetto QueryIterable è stato percorso due volte:

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

Due cicli for su una sola variabile, 569 unità di lettura. Le pagine non sono mai state messe in cache; ogni iterazione ripercorre LastEvaluatedKey da capo. Se ti servono gli Item due volte, svuota l'iterabile in una List una volta sola, come fa l'esempio.

Il rovescio della stessa pigrizia è dove emergono gli errori. Costruisci una richiesta la cui key condition omette la chiave di partizione e queryPaginator la accetta senza protestare, perché nessuna chiamata è ancora avvenuta:

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

Un try-catch avvolto attorno al builder non intercetta nulla. Deve avvolgere il loop, ed è per questo che l'esempio mette l'intero blocco dentro un solo try.

Misurato il 2026-07-28 su DynamoDB Local (amazon/dynamodb-local) con software.amazon.awssdk:dynamodb 2.49.4 su OpenJDK 26.0.1.

Spiegazione

  • ddb.queryPaginator(request).items() appiattisce le pagine in un Iterable<Map<String, AttributeValue>> e pagina dietro le quinte, quindi il doppio loop dell'esempio si riduce a uno quando vuoi solo gli Item. È anche un SdkIterable, quindi .stream() funziona.
  • I numeri sono String nel builder. AttributeValue.builder().n("1994") non è un refuso per .n(1994) — il setter n() prende una java.lang.String, perché DynamoDB trasporta i numeri come testo decimale per evitare l'arrotondamento binario dei float. Passare un int Java non compila.
  • keyConditionExpression prende un'uguaglianza sulla chiave di partizione più al massimo una condizione sulla chiave di ordinamento (=, <, <=, >, >=, BETWEEN, begins_with); .scanIndexForward(false) inverte l'ordine e .indexName("...") punta la query a un indice secondario.
  • L'Enhanced Client è l'altra ergonomia. software.amazon.awssdk.enhanced.dynamodb mappa bean annotati invece di Map<String, AttributeValue>, e la sua query restituisce un PageIterable<T> con la stessa semantica di ri-iterazione pigra misurata sopra.

Fallo visivamente

Sia #hashKey sia #rangeKey in questo esempio sono alias che nulla ti impone: né ArtistSongTitle sono nella lista AWS delle 573 parole riservate. Il controllo delle parole riservate ti dice quali dei tuoi nomi di attributo hanno davvero bisogno del trattamento con #, così la mappa degli alias smette di essere un rito.

Per provare una key condition su una tabella vera prima di costruire la QueryRequest, scarica DynoTable e sfoglia i risultati in una griglia.

Esempi correlati

Riferimenti

Costruisci questa richiesta visivamente

Componi questa operazione nel Generatore di query DynamoDB gratuito — condizione di chiave, filtro, indice, Limit, ordine di ordinamento e un loop di paginazione — e copiala come programma eseguibile per SDK v3, CLI o boto3.

Apri il Generatore di query DynamoDB

Lavora con DynamoDB senza la Console

Un client desktop veloce per DynamoDB che esegue il vero SQL che DynamoDB non può — JOINs, GROUP BY, aggregazioni — con modifica visuale e un agente AI sulle tue chiavi Bedrock.

Prova gratuita di 30 giorni, senza carta di credito — poi il piano Free senza limiti di tempo.