Java(AWS SDK v2)中的 DynamoDB PutItem
PutItem 會寫入一整個項目,並取代任何具有相同主索引鍵的既有項目(以項目為單位的動作談了它與 UpdateItem 的差別)。在 AWS SDK for Java 2.x 裡,每一個屬性都以型別化的 AttributeValue 放進 PutItemRequest,而 builder 會讓你建出一個根本不可能有效的東西。
程式碼
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.ConditionalCheckFailedException;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import software.amazon.awssdk.services.dynamodb.model.PutItemRequest;
public class PutItemExample {
public static void main(String[] args) {
try (DynamoDbClient ddb = DynamoDbClient.builder()
.region(Region.US_EAST_1)
.build()) {
Map<String, AttributeValue> item = new HashMap<>();
item.put("Artist", AttributeValue.builder().s("Arturo Sandoval").build());
item.put("SongTitle", AttributeValue.builder().s("Cubano Chant").build());
item.put("AlbumTitle", AttributeValue.builder().s("Danzon").build());
item.put("Year", AttributeValue.builder().n("1994").build());
item.put("Awards", AttributeValue.builder().n("0").build());
Map<String, String> names = new HashMap<>();
names.put("#cond0", "Artist");
names.put("#cond1", "SongTitle");
PutItemRequest request = PutItemRequest.builder()
.tableName("Music")
.item(item)
.conditionExpression("attribute_not_exists(#cond0) AND attribute_not_exists(#cond1)")
.expressionAttributeNames(names)
.build();
try {
ddb.putItem(request);
System.out.println("Song written");
} catch (ConditionalCheckFailedException e) {
System.out.println("A song with that key already exists — not overwritten");
}
} catch (DynamoDbException e) {
System.err.println(e.getMessage());
}
}
}說明
AttributeValue.builder().build() 編譯得過,而它也送不出去。這個 builder 沒有任何必填欄位,所以一個你忘了寫 .s(...) 的屬性,型別檢查完美通過,卻在服務端失敗:
DynamoDbException | ValidationException | Supplied AttributeValue is empty, must contain exactly one of the supported datatypes這是某個錯誤在 Java 裡的特有樣貌,而其他 SDK 讓它根本不可能發生:Go 的 types.AttributeValueMember* 是各自獨立的型別,所以沒有什麼東西可以「忘了設」。更全面的修法請見「Supplied AttributeValue is empty」。
把 Java 的 null 交給 .s(...),不會變成 DynamoDB 的 NULL。這才是真正會咬人的版本,因為它看起來像個值:
AttributeValue.builder().s(customer.getNotes()).build() // getNotes() returned null建構時不會丟出 NullPointerException。builder 只是什麼都沒記下來,然後請求以一模一樣的 Supplied AttributeValue is empty 訊息失敗,指向一個你從沒懷疑過的屬性。如果你要的是真正的 null,那是 AttributeValue.builder().nul(true).build();不過更常見的情況是你根本該把這一項省略。請注意這跟 Go SDK 正好相反:在那邊,nil 指標會 marshal 成 NULL,悄悄建出一個屬性;兩者是在同一天、對同一個引擎跑出來的。
getMessage() 不是服務的訊息。SDK 會附上自己的上下文,所以那個字串是:
The conditional request failed (Service: DynamoDb, Status Code: 400, Request ID: 77a08ef1-a3a9-4f97-b309-4cb0741edd1a) (SDK Attempt Count: 1)請比對 e.awsErrorDetails().errorCode(),並讀 e.awsErrorDetails().errorMessage() 取得純文字。任何拿 getMessage() 去跟字面值比較的程式碼,都會被一次重試打壞,因為重試會改變嘗試次數。
先攔 ConditionalCheckFailedException,再攔 DynamoDbException,而且要讀它帶了什麼。它繼承自 DynamoDbException,所以把 catch 區塊倒過來排,會讓那個專門的處理器永遠碰不到。在被攔下的物件上:statusCode() 回傳 400,retryable() 回傳 false,對一次商業邏輯上的回絕來說,這是誠實的答案。在請求上加 .returnValuesOnConditionCheckFailure("ALL_OLD"),e.item() 回來時就會裝著擋下這次寫入的那個項目(上面那次執行有五個屬性,Year 是 AttributeValue(N=1994)),所以你不必再補一次 getItem 去查誰贏了。
數字是以字串經由 .n(...) 進去的。DynamoDB 的 N 型別在線路上是十進位文字,這正是讓 1994 不會變成 double 的原因。慣用寫法是 .n(String.valueOf(year));沒有 .n(int) 這種多載可以拿來用。
這個 client 是 Closeable,而且是長壽的。上面的 try-with-resources 對一次性的程式是對的,對服務則是錯的:DynamoDbClient 擁有一個 HTTP 連線池,而且具備執行緒安全性,所以整個應用程式建一個、讓它活著就好。每個請求建一個,是這套 API 上最常見的 Java 效能臭蟲。
比起 AttributeValue 對應表,你更想用 bean 嗎?DynamoDB Enhanced Client(software.amazon.awssdk.enhanced.dynamodb)會把一個加了註解的類別直接對應成項目,徹底移除空 builder 這個陷阱。代價是啟動時一次反射式的 TableSchema.fromBean 掃描,如果你在意這件事,StaticTableSchema 可以避開它。
改用視覺化操作
上面每一種失敗,都是從手工打造型別化的值開始的。免費的 DynamoDB JSON 轉換器收下一般的 JSON,回傳型別化的形式,讓你在寫下第一個 AttributeValue.builder() 之前,就先看清楚項目在線路上究竟該長什麼樣。
想對你自己的資料表寫入與編輯項目 — 每個屬性一個欄位、型別選擇器、把結果複製回去變成 Java 程式碼 — 請下載 DynoTable。
相關範例
- Go 中的 DynamoDB PutItem — 用 AWS SDK for Go v2 做同一種條件式寫入,那裡的 nil 指標會以相反的方式出錯。
- Java 中的 DynamoDB UpdateItem — 改動特定屬性,而不是取代整個項目。
- DynamoDB 條件運算式 —
attribute_not_exists、樂觀鎖,以及更多。 - DynamoDB ConditionalCheckFailedException — 當項目已存在時,只建立不覆寫的條件會丟出什麼。
- DynamoDB ValidationException — 項目或運算式格式錯誤時的統包錯誤。
參考資料
- PutItem — Amazon DynamoDB API Reference
- Use PutItem with an AWS SDK or CLI — Amazon DynamoDB Developer Guide
- DynamoDbClient — AWS SDK for Java 2.x API Reference
- AttributeValue — AWS SDK for Java 2.x API Reference
- PutItemRequest — AWS SDK for Java 2.x API Reference
- Condition expressions — Amazon DynamoDB Developer Guide
已於 2026-07-28 在 OpenJDK 26.0.1 上以 AWS SDK for Java 2.49.4,對照連接埠 9000 上的 DynamoDB Local(amazon/dynamodb-local)重現。上方的例外文字、狀態碼與項目內容都是擷取到的輸出,逐字照錄。