UpdateItem DynamoDB in Java (AWS SDK v2)

L'aggiornamento in sé è una sola chiamata al builder. Quello che costa tempo agli sviluppatori Java è tutto ciò che gli sta attorno: un oggetto di risposta che non restituisce mai null, una gerarchia di eccezioni in cui il fallimento interessante è una sottoclasse di quella che probabilmente hai catturato, e un client di alto livello che non riesce a esprimere affatto questa operazione.

Codice

import java.util.HashMap;
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.ReturnValue;
import software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest;
import software.amazon.awssdk.services.dynamodb.model.UpdateItemResponse;

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

            Map<String, AttributeValue> key = new HashMap<>();
            key.put("Artist", AttributeValue.builder().s("Arturo Sandoval").build());
            key.put("SongTitle", AttributeValue.builder().s("Cubano Chant").build());

            Map<String, String> names = new HashMap<>();
            names.put("#upd0", "Genre");
            names.put("#upd1", "Year");
            names.put("#upd2", "Awards");

            Map<String, AttributeValue> values = new HashMap<>();
            values.put(":updValue0", AttributeValue.builder().s("Latin Jazz").build());
            values.put(":updValue1", AttributeValue.builder().n("1994").build());
            values.put(":updValue2", AttributeValue.builder().n("1").build());

            UpdateItemRequest request = UpdateItemRequest.builder()
                    .tableName("Music")
                    .key(key)
                    .updateExpression("SET #upd0 = :updValue0, #upd1 = :updValue1 ADD #upd2 :updValue2")
                    .expressionAttributeNames(names)
                    .expressionAttributeValues(values)
                    .returnValues(ReturnValue.ALL_NEW)
                    .build();

            UpdateItemResponse response = ddb.updateItem(request);
            System.out.println(response.attributes()); // the item after the update
        } catch (DynamoDbException e) {
            System.err.println(e.getMessage());
        }
    }
}

Spiegazione

  • AttributeValue.builder().n("1994") prende una String, e così fa anche la più breve AttributeValue.fromN("1994"). Non esiste un overload n(int), perché i numeri DynamoDB reggono 38 cifre significative e nessun primitivo Java lo fa. In lettura, anche attributes().get("Awards").n() è una String; l'accessor del tipo sbagliato restituisce null invece di sollevare un'eccezione, quindi .s() su un numero è un null silenzioso, e .type() ti dice quale è impostato.

  • response.attributes() non restituisce mai null. Con ReturnValue.NONE restituisce una DefaultSdkAutoConstructMap vuota ma non nulla, quindi un controllo su null non scatta mai e un controllo con isEmpty() non può distinguere "il servizio non ha mandato nulla" da "l'Item non ha attributi". Il generato hasAttributes() è l'accessor che conosce la differenza. Ogni membro collection in questo SDK ne ha uno.

  • Il builder controlla i tipi di tutto tranne la parte che conta. updateExpression(String) accetta qualsiasi stringa; il compilatore non distingue SET da un refuso, quindi gli errori di espressione sono 400 a runtime. ADD #upd2 :updValue2 è l'incremento atomico, una conditionExpression pari a attribute_exists(Artist) rende la chiamata di solo aggiornamento, e la grammatica è nelle update expression.

  • Preferisci l'espressione alla vecchia mappa attributeUpdates. Gli esempi più datati la mostrano ancora; non può esprimere più tipi di clausola, alias o una condizione in una sola richiesta.

  • getMessage() non è il messaggio del servizio. L'SDK vi accoda il proprio dettaglio di trasporto:

    The conditional request failed (Service: DynamoDb, Status Code: 400, Request ID: d99b117c-edd6-4dc9-8d3a-a5fa4fe9666c) (SDK Attempt Count: 1)

    Logga quello se vuoi il request ID per un ticket di supporto. Per i confronti usa invece awsErrorDetails().errorCode(), e usa awsErrorDetails().errorMessage() quando vuoi la stringa nuda.

Qui l'ordine dei catch conta più del solito

ConditionalCheckFailedException extends DynamoDbException, quindi un catch (DynamoDbException e) messo per primo si mangia l'unico fallimento su cui quasi certamente volevi ramificare. Cattura prima il tipo specifico, e già che ci sei prenditi l'Item:

} catch (ConditionalCheckFailedException e) {
    // with .returnValuesOnConditionCheckFailure(ReturnValuesOnConditionCheckFailure.ALL_OLD)
    if (e.hasItem()) {
        Map<String, AttributeValue> loser = e.item();  // the item as it actually was
    }
} catch (DynamoDbException e) {
    // everything else
}

Su questa e.retryable() è false, ed è corretto: riprovare una condizione fallita fallisce di nuovo.

L'asimmetria da ricordare è che ValidationException non ha una classe in questo SDK. Cerca in dynamodb-2.35.9.jar e non c'è nulla da catturare. Una parola riservata, un'espressione malformata, una chiave parziale: arrivano tutte come una semplice DynamoDbException il cui awsErrorDetails().errorCode() per caso recita ValidationException. In un linguaggio tipizzato staticamente è una lacuna stridente, e significa che gli errori di espressione sono confronti di stringhe a runtime.

Ecco perché le parole riservate di DynamoDB meritano una passata prima di rilasciare invece che dopo: la lista arriva a 573 voci e include Year, Name e Status, nessuna delle quali sembra pericolosa in un bean Java. Per sfogliare la tabella grezza invece della mappatura a bean che le sta sopra, scarica DynoTable.

L'enhanced client non riesce a esprimere questo

Se il resto del tuo accesso ai dati passa da DynamoDbEnhancedClient e da bean annotati, questa operazione è quella che ti riporta a DynamoDbClient. Usando la reflection su UpdateItemEnhancedRequest.Builder saltano fuori item, conditionExpression, ignoreNulls, ignoreNullsMode, returnValues, returnValuesOnConditionCheckFailure, returnConsumedCapacity e returnItemCollectionMetrics. Non c'è alcun metodo che accetti una update expression.

La conseguenza pratica è il contatore atomico. ADD #upd2 :updValue2 incrementa Awards lato server senza una lettura preliminare; l'enhanced client ti dà un bean mappato e ignoreNulls per decidere se i campi assenti vengono rimossi, e niente che compili fino ad ADD. Il read-modify-write attraverso un bean è una race da lost update in concorrenza, che è esattamente ciò che lo snippet di questa pagina evita.

Esempi correlati

Riferimenti

Ultima verifica 2026-07-28 rispetto alla documentazione ufficiale AWS collegata sopra.

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.