了解 DynamoDB 增强型客户端 API 的基础知识 - Amazon SDK for Java 2.x
Amazon Web Services 文档中描述的 Amazon Web Services 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅 中国的 Amazon Web Services 服务入门 (PDF)

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

了解 DynamoDB 增强型客户端 API 的基础知识

本主题讨论 DynamoDB 增强型客户端 API 的基本功能,并将其与标准 DynamoDB 客户端 API 进行了比较。

如果您不熟悉 DynamoDB 增强型客户端 API,建议您阅读介绍性教程,熟悉基础类。

Java 中的 DynamoDB 项目

DynamoDB 表用于存储项目。根据您的用例,Java 端的项目可以采用静态结构化数据形式或动态创建结构形式。

如果您的用例要求项目使用一组一致的属性,请使用带注释的类或使用生成器生成相应的静态类型 TableSchema

或者,如果您需要存储不同结构的项目,请创建一个 DocumentTableSchemaDocumentTableSchema增强型文档 API 的一部分,只需要静态类型的主键,并且可以与 EnhancedDocument 实例结合使用来保存数据元素。增强型文档 API 将在另一个主题中介绍。

属性类型

尽管与 Java 丰富的类型系统相比,DynamoDB 支持的属性类型较少,但 DynamoDB 增强型客户端 API 提供了将 Java 类的成员与 DynamoDB 属性类型相互转换的机制。

默认情况下,DynamoDB 增强型客户端 API 支持大量类型的属性转换器,例如整数BigDecimal字符串和即时。该列表显示在 AttributeConverter 接口的已知实现类中。该列表包括许多类型和集合,例如映射、列表和集。

要存储默认不支持或不符合约定的属性类型的数据,可以编写自定义AttributeConverter实现来进行转换。 JavaBean 有关示例,请参阅属性转换部分。

要存储属性类型的类符合 Java Bean 规范(或该类为不可变数据类)的数据,可以采用两种方法。

  • 如果您有权访问源文件,则可以使用@DynamoDbBean(或@DynamoDbImmutable)为该类添加注释。讨论嵌套属性的部分提供了使用带注释的类的示例

  • 如果无权访问该属性的 JavaBean 数据类的源文件(或者您不想为自己有权访问的类的源文件添加注释),则可以使用生成器方法。这可在不定义键的情况下创建表架构。然后,您可以将此表架构嵌套在另一个表架构中以执行映射。嵌套属性部分提供了使用嵌套架构的示例

Java 基元类型值

尽管增强型客户端可以使用基元类型的属性,但我们建议使用对象类型,因为您不能用基元类型表示空值。

Null 值

使用 putItem API 时,增强型客户端不会在向 DynamoDB 发出的请求中包含映射数据对象的空值属性。

对于 updateItem 请求,将从数据库的项目中移除空值属性。如果您打算更新某些属性值并保持其他属性值不变,请复制不应更改的其他属性的值,或者使用更新生成器上的 ignoreNull() 方法。

以下示例演示 the updateItem() 方法的 ignoreNulls()

public void updateItemNullsExample(){ Customer customer = new Customer(); customer.setCustName("CustName"); customer.setEmail("email"); customer.setId("1"); customer.setRegistrationDate(Instant.now()); // Put item with values for all attributes. customerDynamoDbTable.putItem(customer); // Create a Customer instance with the same id value, but a different name value. // Do not set the 'registrationDate' attribute. Customer custForUpdate = new Customer(); custForUpdate.setCustName("NewName"); custForUpdate.setEmail("email"); custForUpdate.setId("1"); // Update item without setting the registrationDate attribute. customerDynamoDbTable.updateItem(b -> b .item(custForUpdate) .ignoreNulls(Boolean.TRUE)); Customer updatedWithNullsIgnored = customerDynamoDbTable.getItem(customer); // registrationDate value is unchanged. logger.info(updatedWithNullsIgnored.toString()); customerDynamoDbTable.updateItem(custForUpdate); Customer updatedWithNulls = customerDynamoDbTable.getItem(customer); // registrationDate value is null because ignoreNulls() was not used. logger.info(updatedWithNulls.toString()); } } // Logged lines. Customer [id=1, custName=NewName, email=email, registrationDate=2023-04-05T16:32:32.056Z] Customer [id=1, custName=NewName, email=email, registrationDate=null]

DynamoDB 增强型客户端基本方法

增强型客户端的基本方法映射到它们以之命名的 DynamoDB 服务操作。以下示例演示每种方法的最简单变体。您可以通过传入增强型请求对象来自定义每种方法。增强型请求对象提供了标准 DynamoDB 客户端中可用的大部分功能。它们完整记录在《 Amazon SDK for Java 2.x API Reference》中。

该示例使用了前面所示的 Customer 类

// CreateTable customerTable.createTable(); // GetItem Customer customer = customerTable.getItem(Key.builder().partitionValue("a123").build()); // UpdateItem Customer updatedCustomer = customerTable.updateItem(customer); // PutItem customerTable.putItem(customer); // DeleteItem Customer deletedCustomer = customerTable.deleteItem(Key.builder().partitionValue("a123").sortValue(456).build()); // Query PageIterable<Customer> customers = customerTable.query(keyEqualTo(k -> k.partitionValue("a123"))); // Scan PageIterable<Customer> customers = customerTable.scan(); // BatchGetItem BatchGetResultPageIterable batchResults = enhancedClient.batchGetItem(r -> r.addReadBatch(ReadBatch.builder(Customer.class) .mappedTableResource(customerTable) .addGetItem(key1) .addGetItem(key2) .addGetItem(key3) .build())); // BatchWriteItem batchResults = enhancedClient.batchWriteItem(r -> r.addWriteBatch(WriteBatch.builder(Customer.class) .mappedTableResource(customerTable) .addPutItem(customer) .addDeleteItem(key1) .addDeleteItem(key1) .build())); // TransactGetItems transactResults = enhancedClient.transactGetItems(r -> r.addGetItem(customerTable, key1) .addGetItem(customerTable, key2)); // TransactWriteItems enhancedClient.transactWriteItems(r -> r.addConditionCheck(customerTable, i -> i.key(orderKey) .conditionExpression(conditionExpression)) .addUpdateItem(customerTable, customer) .addDeleteItem(customerTable, key));

比较 DynamoDB 增强型客户端与标准 DynamoDB 客户端

这两个 DynamoDB 客户端 API(标准增强型)都允许您使用 DynamoDB 表来执行数据级 CRUD(创建、读取、更新和删除)操作。客户端 API 之间的区别在于该操作是如何实现的。使用标准客户端,您可以直接处理低级别数据属性。增强型客户端 API 使用熟悉的 Java 类,并映射到后台的低级别 API。

虽然两个客户端 API 都支持数据级操作,但标准 DynamoDB 客户端也支持资源级操作。资源级操作管理数据库,例如创建备份、列出表和更新表。增强型客户端 API 支持一定数量的资源级操作,例如创建、描述和删除表。

为了说明两个客户端 API 使用的不同方法,以下代码示例演示如何使用标准客户端和增强型客户端创建同一个 ProductCatalog 表。

比较:使用标准 DynamoDB 客户端创建表

DependencyFactory.dynamoDbClient().createTable(builder -> builder .tableName(TABLE_NAME) .attributeDefinitions( b -> b.attributeName("id").attributeType(ScalarAttributeType.N), b -> b.attributeName("title").attributeType(ScalarAttributeType.S), b -> b.attributeName("isbn").attributeType(ScalarAttributeType.S) ) .keySchema( builder1 -> builder1.attributeName("id").keyType(KeyType.HASH), builder2 -> builder2.attributeName("title").keyType(KeyType.RANGE) ) .globalSecondaryIndexes(builder3 -> builder3 .indexName("products_by_isbn") .keySchema(builder2 -> builder2 .attributeName("isbn").keyType(KeyType.HASH)) .projection(builder2 -> builder2 .projectionType(ProjectionType.INCLUDE) .nonKeyAttributes("price", "authors")) .provisionedThroughput(builder4 -> builder4 .writeCapacityUnits(5L).readCapacityUnits(5L)) ) .provisionedThroughput(builder1 -> builder1 .readCapacityUnits(5L).writeCapacityUnits(5L)) );

比较:使用 DynamoDB 增强型客户端创建表

DynamoDbEnhancedClient enhancedClient = DependencyFactory.enhancedClient(); productCatalog = enhancedClient.table(TABLE_NAME, TableSchema.fromImmutableClass(ProductCatalog.class)); productCatalog.createTable(b -> b .provisionedThroughput(b1 -> b1.readCapacityUnits(5L).writeCapacityUnits(5L)) .globalSecondaryIndices(b2 -> b2.indexName("products_by_isbn") .projection(b4 -> b4 .projectionType(ProjectionType.INCLUDE) .nonKeyAttributes("price", "authors")) .provisionedThroughput(b3 -> b3.writeCapacityUnits(5L).readCapacityUnits(5L)) ) );

增强型客户端使用以下带注释的数据类。DynamoDB 增强型客户端将 Java 数据类型映射到 DynamoDB 数据类型,代码不那么冗长,更易于理解。ProductCatalog 是在 DynamoDB 增强型客户端中使用不可变类的一个例子。本主题后面将讨论为映射的数据类使用不可变类。

package org.example.tests.model; import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbIgnore; import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbImmutable; import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondaryPartitionKey; import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey; import java.math.BigDecimal; import java.util.Objects; import java.util.Set; @DynamoDbImmutable(builder = ProductCatalog.Builder.class) public class ProductCatalog implements Comparable<ProductCatalog> { private Integer id; private String title; private String isbn; private Set<String> authors; private BigDecimal price; private ProductCatalog(Builder builder){ this.authors = builder.authors; this.id = builder.id; this.isbn = builder.isbn; this.price = builder.price; this.title = builder.title; } public static Builder builder(){ return new Builder(); } @DynamoDbPartitionKey public Integer id() { return id; } @DynamoDbSortKey public String title() { return title; } @DynamoDbSecondaryPartitionKey(indexNames = "products_by_isbn") public String isbn() { return isbn; } public Set<String> authors() { return authors; } public BigDecimal price() { return price; } public static final class Builder { private Integer id; private String title; private String isbn; private Set<String> authors; private BigDecimal price; private Builder(){} public Builder id(Integer id) { this.id = id; return this; } public Builder title(String title) { this.title = title; return this; } public Builder isbn(String ISBN) { this.isbn = ISBN; return this; } public Builder authors(Set<String> authors) { this.authors = authors; return this; } public Builder price(BigDecimal price) { this.price = price; return this; } public ProductCatalog build() { return new ProductCatalog(this); } } @Override public String toString() { final StringBuffer sb = new StringBuffer("ProductCatalog{"); sb.append("id=").append(id); sb.append(", title='").append(title).append('\''); sb.append(", isbn='").append(isbn).append('\''); sb.append(", authors=").append(authors); sb.append(", price=").append(price); sb.append('}'); return sb.toString(); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ProductCatalog that = (ProductCatalog) o; return id.equals(that.id) && title.equals(that.title) && Objects.equals(isbn, that.isbn) && Objects.equals(authors, that.authors) && Objects.equals(price, that.price); } @Override public int hashCode() { return Objects.hash(id, title, isbn, authors, price); } @Override @DynamoDbIgnore public int compareTo(ProductCatalog other) { if (this.id.compareTo(other.id) != 0){ return this.id.compareTo(other.id); } else { return this.title.compareTo(other.title); } } }

以下两个批量写入代码示例说明了使用标准客户端时,相较于增强型客户端,代码的冗长和缺乏类型安全性。

public static void batchWriteStandard(DynamoDbClient dynamoDbClient, String tableName) { Map<String, AttributeValue> catalogItem = Map.of( "authors", AttributeValue.builder().ss("a", "b").build(), "id", AttributeValue.builder().n("1").build(), "isbn", AttributeValue.builder().s("1-565-85698").build(), "title", AttributeValue.builder().s("Title 1").build(), "price", AttributeValue.builder().n("52.13").build()); Map<String, AttributeValue> catalogItem2 = Map.of( "authors", AttributeValue.builder().ss("a", "b", "c").build(), "id", AttributeValue.builder().n("2").build(), "isbn", AttributeValue.builder().s("1-208-98073").build(), "title", AttributeValue.builder().s("Title 2").build(), "price", AttributeValue.builder().n("21.99").build()); Map<String, AttributeValue> catalogItem3 = Map.of( "authors", AttributeValue.builder().ss("g", "k", "c").build(), "id", AttributeValue.builder().n("3").build(), "isbn", AttributeValue.builder().s("7-236-98618").build(), "title", AttributeValue.builder().s("Title 3").build(), "price", AttributeValue.builder().n("42.00").build()); Set<WriteRequest> writeRequests = Set.of( WriteRequest.builder().putRequest(b -> b.item(catalogItem)).build(), WriteRequest.builder().putRequest(b -> b.item(catalogItem2)).build(), WriteRequest.builder().putRequest(b -> b.item(catalogItem3)).build()); Map<String, Set<WriteRequest>> productCatalogItems = Map.of( "ProductCatalog", writeRequests); BatchWriteItemResponse response = dynamoDbClient.batchWriteItem(b -> b.requestItems(productCatalogItems)); logger.info("Unprocessed items: " + response.unprocessedItems().size()); }
public static void batchWriteEnhanced(DynamoDbTable<ProductCatalog> productCatalog) { ProductCatalog prod = ProductCatalog.builder() .id(1) .isbn("1-565-85698") .authors(new HashSet<>(Arrays.asList("a", "b"))) .price(BigDecimal.valueOf(52.13)) .title("Title 1") .build(); ProductCatalog prod2 = ProductCatalog.builder() .id(2) .isbn("1-208-98073") .authors(new HashSet<>(Arrays.asList("a", "b", "c"))) .price(BigDecimal.valueOf(21.99)) .title("Title 2") .build(); ProductCatalog prod3 = ProductCatalog.builder() .id(3) .isbn("7-236-98618") .authors(new HashSet<>(Arrays.asList("g", "k", "c"))) .price(BigDecimal.valueOf(42.00)) .title("Title 3") .build(); BatchWriteResult batchWriteResult = DependencyFactory.enhancedClient() .batchWriteItem(b -> b.writeBatches( WriteBatch.builder(ProductCatalog.class) .mappedTableResource(productCatalog) .addPutItem(prod).addPutItem(prod2).addPutItem(prod3) .build() )); logger.info("Unprocessed items: " + batchWriteResult.unprocessedPutItemsForTable(productCatalog).size()); }