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这是一个其他 SDK 根本不可能犯的错误在 Java 里的特有形态: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() 去比对字面量的代码,都会被一次重试打破,因为重试会改变尝试次数。
在 DynamoDbException 之前捕获 ConditionalCheckFailedException,并读一读它带了什么。它继承自 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 性能 bug。
更想用 bean 而不是 AttributeValue 映射?那么 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 以 AWS SDK for Java 2.49.4、OpenJDK 26.0.1,针对 DynamoDB Local(amazon/dynamodb-local,端口 9000)复现。上方的异常文本、状态码和项目内容都是捕获到的输出,原样照录。