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 筆,包含 Year、Name 與 Status,在一個 Java bean 裡沒有一個看起來危險。若想瀏覽原始表格而不是它上面的 bean 映射,請下載 DynoTable。
Enhanced client 表達不了這個操作
如果你其餘的資料存取都走 DynamoDbEnhancedClient 加上加註解的 bean,這個操作就是那個會把你丟回 DynamoDbClient 的例外。對 UpdateItemEnhancedRequest.Builder 做反射,會翻出 item、conditionExpression、ignoreNulls、ignoreNullsMode、returnValues、returnValuesOnConditionCheckFailure、returnConsumedCapacity 與 returnItemCollectionMetrics。沒有任何一個方法接受更新運算式。
實務上的後果就是原子式計數器。ADD #upd2 :updValue2 會在伺服器端遞增 Awards,而且事前不需要讀取;enhanced client 給你的是一個映射好的 bean,以及用來決定不存在的欄位要不要被移除的 ignoreNulls,卻沒有任何東西會編譯成 ADD。透過 bean 做讀取-修改-寫回,在並行下就是一場更新遺失的競賽,而那正是本頁這段程式碼所避開的。
相關範例
- Go 中的 DynamoDB UpdateItem — 以 AWS SDK for Go v2 做同一次更新。
- Java 中的 DynamoDB PutItem — 改成整個項目替換掉。
- DynamoDB 更新運算式 —
SET、ADD、REMOVE、DELETE與各種慣用寫法。 - 認識 ReturnValues — 每個
ReturnValues選項各給你什麼。 - "Attribute name is a reserved keyword" — 為什麼這裡的別名對應不是選配。
- "Invalid UpdateExpression" 語法錯誤 — 常見的 SET/ADD 語法錯誤解讀。
參考資料
- UpdateItem — Amazon DynamoDB API Reference
- Use UpdateItem with an AWS SDK or CLI — Amazon DynamoDB Developer Guide
- DynamoDbClient — AWS SDK for Java 2.x API Reference
- UpdateItemRequest — AWS SDK for Java 2.x API Reference
- Update expressions — Amazon DynamoDB Developer Guide
最後驗證於 2026-07-28,對照上方連結的 AWS 官方文件。