适用于 Java 的 SDK 版本 1 和版本 2 之间的 DynamoDB 映射 API 更改 - AWS SDK for Java 2.x

本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。

适用于 Java 的 SDK 版本 1 和版本 2 之间的 DynamoDB 映射 API 更改

创建客户端

应用场景 V1 V2

正常实例化

AmazonDynamoDB standardClient = AmazonDynamoDBClientBuilder.standard() .withCredentials(credentialsProvider) .withRegion(Regions.US_EAST_1) .build(); DynamoDBMapper mapper = new DynamoDBMapper(standardClient);
DynamoDbClient standardClient = DynamoDbClient.builder() .credentialsProvider(ProfileCredentialsProvider.create()) .region(Region.US_EAST_1) .build(); DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder() .dynamoDbClient(standardClient) .build();

最小实例化

AmazonDynamoDB standardClient = AmazonDynamoDBClientBuilder.standard(); DynamoDBMapper mapper = new DynamoDBMapper(standardClient);
DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.create();

带属性转换器*

DynamoDBMapper mapper = new DynamoDBMapper(standardClient, attributeTransformerInstance);
DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder() .dynamoDbClient(standardClient) .extensions(extensionAInstance, extensionBInstance) .build();

*V2 中的扩展与 V1 中的属性转换器大致对应。使用扩展自定义 DynamoDB 增强型客户端操作 部分包含有关 V2 中扩展的更多信息。

建立到 DynamoDB 表/索引的映射

在 V1 中,通过 bean 注释指定 DynamoDB 表名。在 V2 中,使用一种工厂方法 table() 来生成代表远程 DynamoDB 表的 DynamoDbTable 实例。table() 方法的第一个参数是 DynamoDB 表名。

应用场景 V1 V2

将 Java POJO 类映射到 DynamoDB 表

@DynamoDBTable(tableName ="Customer") public class Customer { ... }
DynamoDbTable<Customer> customerTable = enhancedClient.table("Customer", TableSchema.fromBean(Customer.class));

映射到 DynamoDB 二级索引

  1. 定义一个表示索引的 POJO 类。

    • 给类加上 @DynamoDBTable 这个注解,在注解中提供包含索引的表的名称。

    • 使用 @DynamoDBIndexHashKey 为属性添加注释,并可选择使用 @DynamoDBIndexRangeKey 为属性添加注释。

  2. 创建查询表达式。

  3. 使用代表该索引的 POJO 类的引用进行查询。例如

    mapper.query(IdEmailIndex.class, queryExpression)

    其中 IdEmailIndex 是索引的映射类。

《DynamoDB 开发人员指南》中讨论 V1 query 方法的部分展示了一个完整的示例。

  1. 使用 @DynamoDbSecondaryPartitionKey(对于 GSI)和 @DynamoDbSecondarySortKey(对于 GSI 或 LSI)对 POJO 类的属性添加注释。例如,

    @DynamoDbSecondarySortKey(indexNames = "IdEmailIndex") public String getEmail() { return this.email; }
  2. 检索对索引的引用。例如,

    DynamoDbIndex<Customer> customerIndex = customerTable.index("IdEmailIndex");
  3. 查询索引。

本指南中的 使用二级索引 部分提供了更多信息。

表操作

本节介绍大多数标准使用案例的 V1 和 V2 之间存在差异的操作 API。

在 V2 中,所有涉及单个表的操作都是在 DynamoDbTable 实例上调用,而不是在增强型客户端上调用。增强型客户端包含可以针对多个表的方法。

在下面名为表操作的表中,POJO 实例被称为 item 或称为某个具体类型(例如 customer1)。对于 V2 示例,名为 table 的实例是先前调用 enhancedClient.table() 的结果,该调用返回对 DynamoDbTable 实例的引用。

请注意,即使未显示,也可以使用 fluent 使用者模式调用大多数 V2 操作。例如,

Customer customer = table.getItem(r → r.key(key)); or Customer customer = table.getItem(r → r.key(k -> k.partitionValue("id").sortValue("email")))

对于 V1 操作,表操作(在下方)包含一些常用的表单,并未涵盖所有重载表单。例如,load() 方法有以下重载:

mapper.load(Customer.class, hashKey) mapper.load(Customer.class, hashKey, rangeKey) mapper.load(Customer.class, hashKey, config) mapper.load(Customer.class, hashKey, rangeKey, config) mapper.load(item) mapper.load(item, config)

表操作(在下方)显示了常用的表单:

mapper.load(item) mapper.load(item, config)
表操作
应用场景 V1 V2

将 Java POJO 写入 DynamoDB 表

DynamoDB 操作PutItemUpdateItem

mapper.save(item) mapper.save(item, config) mapper.save(item, saveExpression, config)

在 V1 中,DynamoDBMapperConfig.SaveBehavior 和注释决定了将调用哪个低级 DynamoDB 方法。通常,除非在使用 SaveBehavior.CLOBBERSaveBehavior.PUT,否则会调用 UpdateItem。自动生成的密钥是一种特殊的使用案例,偶尔会同时使用 PutItemUpdateItem

table.putItem(putItemRequest) table.putItem(item) table.putItemWithResponse(item) //Returns metadata. updateItem(updateItemRequest) table.updateItem(item) table.updateItemWithResponse(item) //Returns metadata.

从 DynamoDB 表中将项目读取到 Java POJO

DynamoDB 操作:GetItem

mapper.load(item) mapper.load(item, config)
table.getItem(getItemRequest) table.getItem(item) table.getItem(key) table.getItemWithResponse(key) //Returns POJO with metadata.

从 DynamoDB 表中删除项目

DynamoDB 操作:DeleteItem

mapper.delete(item, deleteExpression, config)
table.deleteItem(deleteItemRequest) table.deleteItem(item) table.deleteItem(key)

查询 DynamoDB 表或二级索引并返回分页列表

DynamoDB 操作:Query

mapper.query(Customer.class, queryExpression) mapper.query(Customer.class, queryExpression, mapperConfig)
table.query(queryRequest) table.query(queryConditional)

使用返回的 PageIterable.stream()(延迟加载)进行同步响应,使用 PagePublisher.subscribe() 进行异步响应

查询 DynamoDB 表或二级索引并返回列表

DynamoDB 操作:Query

mapper.queryPage(Customer.class, queryExpression) mapper.queryPage(Customer.class, queryExpression, mapperConfig)
table.query(queryRequest) table.query(queryConditional)

使用返回的 PageIterable.items()(延迟加载)进行同步响应,使用 PagePublisher.items.subscribe() 进行异步响应

扫描 DynamoDB 表或二级索引并返回分页列表

DynamoDB 操作:Scan

mapper.scan(Customer.class, scanExpression) mapper.scan(Customer.class, scanExpression, mapperConfig)
table.scan() table.scan(scanRequest)

使用返回的 PageIterable.stream()(延迟加载)进行同步响应,使用 PagePublisher.subscribe() 进行异步响应

扫描 DynamoDB 表或二级索引并返回列表

DynamoDB 操作:Scan

mapper.scanPage(Customer.class, scanExpression) mapper.scanPage(Customer.class, scanExpression, mapperConfig)
table.scan() table.scan(scanRequest)

使用返回的 PageIterable.items()(延迟加载)进行同步响应,使用 PagePublisher.items.subscribe() 进行异步响应

批量读取多个表中的多个项目

DynamoDB 操作:BatchGetItem

mapper.batchLoad(Arrays.asList(customer1, customer2, book1)) mapper.batchLoad(itemsToGet) // itemsToGet: Map<Class<?>, List<KeyPair>>
enhancedClient.batchGetItem(batchGetItemRequest) enhancedClient.batchGetItem(r -> r.readBatches( ReadBatch.builder(Record1.class) .mappedTableResource(mappedTable1) .addGetItem(i -> i.key(k -> k.partitionValue(0))) .build(), ReadBatch.builder(Record2.class) .mappedTableResource(mappedTable2) .addGetItem(i -> i.key(k -> k.partitionValue(0))) .build())) // Iterate over pages with lazy loading or over all items from the same table.

批量将多个项目写入多个表

DynamoDB 操作:BatchWriteItem

mapper.batchSave(Arrays.asList(customer1, customer2, book1))
enhancedClient.batchWriteItem(batchWriteItemRequest) enhancedClient.batchWriteItem(r -> r.writeBatches( WriteBatch.builder(Record1.class) .mappedTableResource(mappedTable1) .addPutItem(item1) .build(), WriteBatch.builder(Record2.class) .mappedTableResource(mappedTable2) .addPutItem(item2) .build()))

批量删除多个表中的多个项目

DynamoDB 操作:BatchWriteItem

mapper.batchDelete(Arrays.asList(customer1, customer2, book1))
enhancedClient.batchWriteItem(r -> r.writeBatches( WriteBatch.builder(Record1.class) .mappedTableResource(mappedTable1) .addDeleteItem(item1key) .build(), WriteBatch.builder(Record2.class) .mappedTableResource(mappedTable2) .addDeleteItem(item2key) .build()))

批量写入/删除多个项目

DynamoDB 操作:BatchWriteItem

mapper.batchWrite(Arrays.asList(customer1, book1), Arrays.asList(customer2))
enhancedClient.batchWriteItem(r -> r.writeBatches( WriteBatch.builder(Record1.class) .mappedTableResource(mappedTable1) .addPutItem(item1) .build(), WriteBatch.builder(Record2.class) .mappedTableResource(mappedTable2) .addDeleteItem(item2key) .build()))

执行事务性写入

DynamoDB 操作:TransactWriteItems

mapper.transactionWrite(transactionWriteRequest)
enhancedClient.transactWriteItems(transasctWriteItemsRequest)

执行事务性读取

DynamoDB 操作:TransactGetItems

mapper.transactionLoad(transactionLoadRequest)
enhancedClient.transactGetItems(transactGetItemsRequest)

获取查询中匹配项的计数

DynamoDB 操作:使用 Select.COUNT 执行 Query

mapper.count(Customer.class, queryExpression)
// Get the count from query results. PageIterable<Customer> pageIterable = customerTable.query(QueryEnhancedRequest.builder() .queryConditional(queryConditional) .select(Select.COUNT) .build()); Iterator<Page<Customer>> iterator = pageIterable.iterator(); Page<Customer> page = iterator.next(); int count = page.count(); // For a more concise approach, you can chain the method calls: int count = customerTable.query(QueryEnhancedRequest.builder() .queryConditional(queryConditional) .select(Select.COUNT) .build()) .iterator().next().count();

获取扫描中匹配项的计数

DynamoDB 操作:使用 Select.COUNT 执行 Scan

mapper.count(Customer.class, scanExpression)
// Get the count from scan results. PageIterable<Customer> pageIterable = customerTable.scan(ScanEnhancedRequest.builder() .filterExpression(filterExpression) .select(Select.COUNT) .build()); Iterator<Page<Customer>> iterator = pageIterable.iterator(); Page<Customer> page = iterator.next(); int count = page.count(); // For a more concise approach, you can chain the method calls: int count = customerTable.scan(ScanEnhancedRequest.builder() .filterExpression(filterExpression) .select(Select.COUNT) .build()) .iterator().next().count();

在 DynamoDB 中创建与 POJO 类对应的表

DynamoDB 操作:CreateTable

mapper.generateCreateTableRequest(Customer.class)

前面的语句生成一个低级别的创建表请求;用户必须在 DynamoDB 客户端中调用 createTable

table.createTable(createTableRequest) table.createTable(r -> r.provisionedThroughput(defaultThroughput()) .globalSecondaryIndices( EnhancedGlobalSecondaryIndex.builder() .indexName("gsi_1") .projection(p -> p.projectionType(ProjectionType.ALL)) .provisionedThroughput(defaultThroughput()) .build()));

在 DynamoDB 中执行并行扫描

DynamoDB 操作:使用 SegmentTotalSegments 参数执行 Scan

mapper.parallelScan(Customer.class, scanExpression, numTotalSegments)

用户需要处理工作线程并为每个分段调用 scan

table.scan(r -> r.segment(0).totalSegments(5))

将 Amazon S3 与 DynamoDB 集成以存储智能 S3 链接

mapper.createS3Link(bucket, key) mapper.getS3ClientCache()

不支持,因为它将 Amazon S3 与 DynamoDB 结合在一起。

映射类和属性

在 V1 和 V2 中,都使用 bean 样式的注释将类映射到表。V2 还提供了其他方法来为特定使用案例定义架构,例如使用不可变类。

bean 注释

下表显示了 V1 和 V2 中使用的特定使用案例的等效 bean 注释。Customer 类场景用于说明参数。

V2 中的注释(以及类和枚举)遵循驼峰命名法规范,使用“DynamoDb”,而不是“DynamoDB”。

应用场景 V1 V2
将类映射到表
@DynamoDBTable (tableName ="CustomerTable")
@DynamoDbBean @DynamoDbBean(converterProviders = {...})
表名在调用 DynamoDbEnhancedClient#table() 方法时定义。
将类成员指定为表属性
@DynamoDBAttribute(attributeName = "customerName")
@DynamoDbAttribute("customerName")
将类成员指定为哈希键/分区键
@DynamoDBHashKey
@DynamoDbPartitionKey
将类成员指定为范围键/排序键
@DynamoDBRangeKey
@DynamoDbSortKey
将类成员指定为二级索引哈希键/分区键
@DynamoDBIndexHashKey
@DynamoDbSecondaryPartitionKey
将类成员指定为二级索引范围键/排序键
@DynamoDBIndexRangeKey
@DynamoDbSecondarySortKey
映射到表时忽略此类成员
@DynamoDBIgnore
@DynamoDbIgnore
将类成员指定为自动生成的 UUID 键属性
@DynamoDBAutoGeneratedKey
@DynamoDbAutoGeneratedUuid

默认情况下,不加载提供此功能的扩展;您必须将扩展添加到客户端生成器中。

将类成员指定为自动生成的时间戳属性
@DynamoDBAutoGeneratedTimestamp
@DynamoDbAutoGeneratedTimestampAttribute

默认情况下,不加载提供此功能的扩展;您必须将扩展添加到客户端生成器中。

将类成员指定为自动递增的版本属性
@DynamoDBVersionAttribute
@DynamoDbVersionAttribute

提供此功能的扩展会自动加载。

将类成员指定为需要自定义转换
@DynamoDBTypeConverted
@DynamoDbConvertedBy
将类成员指定为存储为另一个属性类型
@DynamoDBTyped(<DynamoDBAttributeType>)

使用 AttributeConverter 实现。V2 为常用 Java 类型提供许多内置转换器。您也可以实施自己的自定义 AttributeConverterAttributeConverterProvider。请参阅本指南中的控制属性转换

指定一个可以序列化为 DynamoDB 文档(JSON 风格的文档)或子文档的类
@DynamoDBDocument
使用增强型文档 API。请参阅以下资源:

V2 附加注释

应用场景 V1 V2
如果 Java 值为 null,则将类成员指定为不存储为 Null 属性 不适用
@DynamoDbIgnoreNulls
如果所有属性都为 null,则将类成员指定为空对象 不适用
@DynamoDbPreserveEmptyObject
为类成员指定特殊更新操作 不适用
@DynamoDbUpdateBehavior
指定一个不可变类 不适用
@DynamoDbImmutable
将类成员指定为自动递增的计数器属性 不适用
@DynamoDbAtomicCounter

提供此功能的扩展会自动加载。

配置

在 V1 中,通常使用 DynamoDBMapperConfig 的实例来控制特定行为。您可以在创建映射器时或在发出请求时提供配置对象。在 V2 中,配置特定于操作的请求对象。

应用场景 V1 V1 中的默认设置 V2
DynamoDBMapperConfig.builder()
批量加载/写入重试策略
.withBatchLoadRetryStrategy(loadRetryStrategy)
.withBatchWriteRetryStrategy(writeRetryStrategy)
重试失败的项目 在底层 DynamoDBClient 中配置重试策略。请参阅本指南中的在AWS SDK for Java 2.x 中配置重试行为
一致性读取
.withConsistentReads(CONSISTENT)
EVENTUAL 默认情况下,读取操作的一致性读取为 false。在请求对象上使用 .consistentRead(true) 覆盖。
包含编组器/解组器集合的转换架构
.withConversionSchema(conversionSchema)

静态实现与旧版本向后兼容。

V2_COMPATIBLE 不适用。这是一项遗留特性,指的是 DynamoDB 的最早版本(V1)存储数据类型的方式,而在增强型客户端中不再保留此行为。DynamoDB V1 中的一个行为示例是将布尔值存储为数字而不是布尔值。
表名称
.withObjectTableNameResolver() .withTableNameOverride() .withTableNameResolver()

静态实现与旧版本向后兼容

使用注释或根据类来推断

表名在调用 DynamoDbEnhancedClient#table() 方法时定义。

分页加载策略
.withPaginationLoadingStrategy(strategy)

选项包括:LAZY_LOADINGEAGER_LOADINGITERATION_ONLY

LAZY_LOADING
  • 默认为“仅迭代”。不支持其他 V1 选项。

  • 您可以通过以下方式在 V2 中实施等同于预先加载的功能:

    List<Customer> allItems = customerTable.scan().items().stream().collect(Collectors.toList());
  • 对于延迟加载,您必须为访问的项目实施自己的缓存逻辑。

请求指标收集
.withRequestMetricCollector(collector)
null 在构建标准 DynamoDB 客户端时在 ClientOverrideConfiguration 中使用 metricPublisher()
保存行为
.withSaveBehavior(SaveBehavior.CLOBBER)

选项包括 UPDATECLOBBERPUTAPPEND_SETUPDATE_SKIP_NULL_ATTRIBUTES

UPDATE

在 V2 中,您可以显式调用 putItem()updateItem()

CLOBBER or PUT:V2 中的相应动作是调用 putItem()。没有特定的 CLOBBER 配置。

UPDATE对应于 updateItem()

UPDATE_SKIP_NULL_ATTRIBUTES对应于 updateItem()。使用请求设置 ignoreNulls 和注释/标签 DynamoDbUpdateBehavior 控制更新行为。

APPEND_SET:不支持

类型转换器工厂
.withTypeConverterFactory(typeConverterFactory)
标准类型转换器

使用以下方式在 bean 上设置

@DynamoDbBean(converterProviders = {ConverterProvider.class, DefaultAttributeConverterProvider.class})

每个操作的配置

在 V1 中,某些操作(例如 query())可以通过提交给操作的“表达式”对象来进行高度灵活的配置。例如:

DynamoDBQueryExpression<Customer> emailBwQueryExpr = new DynamoDBQueryExpression<Customer>() .withRangeKeyCondition("Email", new Condition() .withComparisonOperator(ComparisonOperator.BEGINS_WITH) .withAttributeValueList( new AttributeValue().withS("my"))); mapper.query(Customer.class, emailBwQueryExpr);

在 V2 中,不使用配置对象,而是使用生成器对请求对象设置参数。例如:

QueryEnhancedRequest emailBw = QueryEnhancedRequest.builder() .queryConditional(QueryConditional .sortBeginsWith(kb -> kb .sortValue("my"))).build(); customerTable.query(emailBw);

条件

在 V2 中,条件表达式和筛选表达式使用 Expression 对象来表达,该对象封装了条件以及名称和筛选条件的映射。

应用场景 操作 V1 V2
预期的属性条件 save()、delete()、query()、scan()
new DynamoDBSaveExpression() .withExpected(Collections.singletonMap( "otherAttribute", new ExpectedAttributeValue(false))) .withConditionalOperator(ConditionalOperator.AND);
已弃用;改用 ConditionExpression
条件表达式 delete()
deleteExpression.setConditionExpression("zipcode = :zipcode") deleteExpression.setExpressionAttributeValues(...)
Expression conditionExpression = Expression.builder() .expression("#key = :value OR #key1 = :value1") .putExpressionName("#key", "attribute") .putExpressionName("#key1", "attribute3") .putExpressionValue(":value", AttributeValues.stringValue("wrong")) .putExpressionValue(":value1", AttributeValues.stringValue("three")) .build(); DeleteItemEnhancedRequest request = DeleteItemEnhancedRequest.builder() .conditionExpression(conditionExpression).build();
筛选表达式 query()、scan()
scanExpression .withFilterExpression("#statename = :state") .withExpressionAttributeValues(attributeValueMapBuilder.build()) .withExpressionAttributeNames(attributeNameMapBuilder.build())
Map<String, AttributeValue> values = singletonMap(":key", stringValue("value")); Expression filterExpression = Expression.builder() .expression("name = :key") .expressionValues(values) .build(); QueryEnhancedRequest request = QueryEnhancedRequest.builder() .filterExpression(filterExpression).build();
查询的条件表达式 query()
queryExpression.withKeyConditionExpression()
QueryConditional keyEqual = QueryConditional.keyEqualTo(b -> b .partitionValue("movie01")); QueryEnhancedRequest tableQuery = QueryEnhancedRequest.builder() .queryConditional(keyEqual) .build();

类型转换

默认转换器

在 V2 中,SDK 为所有常见类型提供了一组默认转换器。既可以在整体提供程序级别更改类型转换器,也可以为单个属性更改类型转换器。您可以在 AttributeConverter API 参考中找到可用转换器的列表。

为属性设置自定义转换器

在 V1 中,您可以使用 @DynamoDBTypeConverted 对 getter 方法添加注释,以指定在 Java 属性类型和 DynamoDB 属性类型之间进行转换的类。例如,CurrencyFormatConverter 可以在 Java Currency 类型和 DynamoDB 字符串之间应用转换,如以下代码段所示。

@DynamoDBTypeConverted(converter = CurrencyFormatConverter.class) public Currency getCurrency() { return currency; }

上一个代码段的 V2 等效代码如下所示。

@DynamoDbConvertedBy(CurrencyFormatConverter.class) public Currency getCurrency() { return currency; }
注意

在 V1 中,您可以将注释应用于属性本身、类型或用户定义的注释,V2 仅支持将注释应用于 getter。

添加类型转换器工厂或提供程序

在 V1 中,您可以提供自己的类型转换器集,也可以通过在配置中添加类型转换器工厂来覆盖您在意的类型。类型转换器工厂扩展 DynamoDBTypeConverterFactory,通过获取对默认集的引用并对其进行扩展来完成覆盖。以下代码段演示了如何执行此操作。

DynamoDBTypeConverterFactory typeConverterFactory = DynamoDBTypeConverterFactory.standard().override() .with(String.class, CustomBoolean.class, new DynamoDBTypeConverter<String, CustomBoolean>() { @Override public String convert(CustomBoolean bool) { return String.valueOf(bool.getValue()); } @Override public CustomBoolean unconvert(String string) { return new CustomBoolean(Boolean.valueOf(string)); }}).build(); DynamoDBMapperConfig config = DynamoDBMapperConfig.builder() .withTypeConverterFactory(typeConverterFactory) .build(); DynamoDBMapper mapperWithTypeConverterFactory = new DynamoDBMapper(dynamo, config);

V2 通过 @DynamoDbBean 注释提供了类似的功能。您可以提供单个 AttributeConverterProvider 或一个有序的 AttributeConverterProvider 链。请注意,如果您提供自己的属性转换器提供程序链,则将覆盖默认的转换器提供程序,且必须在链中包括它才能使用其属性转换器。

@DynamoDbBean(converterProviders = { ConverterProvider1.class, ConverterProvider2.class, DefaultAttributeConverterProvider.class}) public class Customer { ... }

本指南中关于属性转换的部分包含了 V2 的完整示例。