Java(AWS SDK v2)中的 DynamoDB UpdateItem

更新本身只是一次建構器呼叫。真正消耗 Java 開發者時間的是它周圍的一切:一個永遠不會回傳 null 的回應物件、一個「有趣的那種失敗」正好是你多半已經攔截的例外之子類別的例外階層,以及一個根本無法表達這個操作的高階用戶端。

程式碼

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());
        }
    }
}

說明

  • AttributeValue.builder().n("1994") 接受的是 String,較簡短的 AttributeValue.fromN("1994") 也一樣。沒有 n(int) 的多載,因為 DynamoDB 的數字能保有 38 位有效數字,而沒有任何 Java 原始型別做得到。讀回來時,attributes().get("Awards").n() 同樣是 String;型別不對的存取子回傳的是 null 而不是拋錯,所以對一個數字呼叫 .s() 會得到一個無聲的 null,而 .type() 會告訴你哪一個才是有值的。

  • response.attributes() 永遠不會回傳 null。搭配 ReturnValue.NONE 時它回傳的是一個空但非 null 的 DefaultSdkAutoConstructMap,所以 null 檢查永遠不會觸發,而 isEmpty() 檢查也無法區分「服務什麼都沒送」與「這個項目沒有屬性」。產生出來的 hasAttributes() 才是知道差別的那個存取子。這個 SDK 裡每個集合型成員都有一個。

  • 建構器什麼都做型別檢查,就是不檢查最要緊的那部分updateExpression(String) 接受任何字串;編譯器分不出 SET 與一個錯字,所以運算式的錯誤都是執行期的 400。ADD #upd2 :updValue2 是原子式遞增,一個 attribute_exists(Artist)conditionExpression 會讓這次呼叫變成僅更新,而文法在更新運算式裡。

  • 請優先用運算式,而不是舊有的 attributeUpdates 對應。較舊的範例仍然在展示它;它無法在同一個請求裡表達多種子句型別、別名或條件。

  • getMessage() 不是服務的訊息。SDK 會附加它自己的傳輸細節:

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

    如果你要拿 request ID 去開支援案件,就記錄那一整串。否則請改用 awsErrorDetails().errorCode() 來比對,並在你只想要那個原始字串時使用 awsErrorDetails().errorMessage()

這裡的攔截順序比平常更要緊

ConditionalCheckFailedException extends DynamoDbException,所以擺在前面的 catch (DynamoDbException e) 會吞掉那個你幾乎肯定想要分支處理的失敗。請先攔截具體型別,順手把項目取走:

} 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
}

這一個的 e.retryable()false,而那是正確的:重試一個失敗的條件只會再失敗一次。

要記住的不對稱之處是這個 SDK 裡沒有 ValidationException 這個類別。在 dynamodb-2.35.9.jar 裡搜尋,沒有任何東西可以攔截。一個保留字、一個格式錯誤的運算式、一個不完整的鍵:全部都以一個普通的 DynamoDbException 抵達,只是它的 awsErrorDetails().errorCode() 剛好讀作 ValidationException。在一個靜態型別語言裡這是個刺眼的缺口,也代表運算式錯誤變成了執行期的字串比對。

這就是為什麼 DynamoDB 的保留字值得在出貨前而不是出貨後過一遍:那份清單有 573 筆,包含 YearNameStatus,在一個 Java bean 裡沒有一個看起來危險。若想瀏覽原始表格而不是它上面的 bean 映射,請下載 DynoTable

Enhanced client 表達不了這個操作

如果你其餘的資料存取都走 DynamoDbEnhancedClient 加上加註解的 bean,這個操作就是那個會把你丟回 DynamoDbClient 的例外。對 UpdateItemEnhancedRequest.Builder 做反射,會翻出 itemconditionExpressionignoreNullsignoreNullsModereturnValuesreturnValuesOnConditionCheckFailurereturnConsumedCapacityreturnItemCollectionMetrics。沒有任何一個方法接受更新運算式。

實務上的後果就是原子式計數器。ADD #upd2 :updValue2 會在伺服器端遞增 Awards,而且事前不需要讀取;enhanced client 給你的是一個映射好的 bean,以及用來決定不存在的欄位要不要被移除的 ignoreNulls,卻沒有任何東西會編譯成 ADD。透過 bean 做讀取-修改-寫回,在並行下就是一場更新遺失的競賽,而那正是本頁這段程式碼所避開的。

相關範例

參考資料

最後驗證於 2026-07-28,對照上方連結的 AWS 官方文件。

不必透過主控台就能操作 DynamoDB

一款快速的 DynamoDB 桌面用戶端,可執行 DynamoDB 無法執行的真正 SQL — JOINs、GROUP BY、聚合 — 並支援視覺化編輯與使用你自己的 Bedrock 金鑰的 AI 代理。

30 天免費試用,無需信用卡 — 之後為無時間限制的免費方案。