

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

# 了解 DynamoDB 增强型客户端 API 的基础知识
<a name="ddb-en-client-use"></a>

本主题讨论 DynamoDB 增强型客户端 API 的基本功能，并将其与[标准 DynamoDB 客户端 API](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/package-summary.html) 进行了比较。

如果您不熟悉 DynamoDB 增强型客户端 API，建议您阅读[介绍性教程](ddb-en-client-getting-started.md)，熟悉基础类。

## Java 中的 DynamoDB 项目
<a name="ddb-en-client-use-usecase"></a>

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

如果您的用例要求项目使用一组一致的属性，请使用[带注释的类](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-anno-bean)或使用[生成器](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-builder)生成相应的静态类型 `TableSchema`。

或者，如果您需要存储不同结构的项目，请创建一个 `DocumentTableSchema`。`DocumentTableSchema` 是[增强型文档 API](ddb-en-client-doc-api.md) 的一部分，只需要静态类型的主键，并且可以与 `EnhancedDocument` 实例结合使用来保存数据元素。增强型文档 API 将在另一个[主题](ddb-en-client-doc-api.md)中介绍。

## 数据模型类的属性类型
<a name="ddb-en-client-use-types"></a>

尽管与 Java 丰富的类型系统相比，DynamoDB 支持的[属性类型较少](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes)，但 DynamoDB 增强型客户端 API 提供了将 Java 类的成员与 DynamoDB 属性类型相互转换的机制。

Java 数据类的属性类型（属性）应该是对象类型，而不是基元。例如，始终使用 `Long` 和 `Integer` 对象数据类型，而不是 `long` 和 `int` 基元。

[默认情况下，DynamoDB 增强型客户端 API 支持大量类型的属性转换器，[例如](https://docs.oracle.com/javase/8/docs/api/java/lang/Integer.html)整数[[BigDecimal](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/internal/converter/attribute/BigDecimalAttributeConverter.html)、](https://docs.oracle.com/javase/8/docs/api/java/lang/String.html)字符串和即时。](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/internal/converter/attribute/InstantAsStringAttributeConverter.html)该列表显示在[ AttributeConverter 接口的已知实现类中](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/AttributeConverter.html)。该列表包括许多类型和集合，例如映射、列表和集。

要存储默认不支持或不符合约定的属性类型的数据，可以编写自定义`AttributeConverter`实现来进行转换。 JavaBean 有关[示例](ddb-en-client-adv-features-conversion.md#ddb-en-client-adv-features-conversion-example)，请参阅属性转换部分。

要存储属性类型的类符合 Java Bean 规范（或该类为[不可变数据类](ddb-en-client-use-immut.md)）的数据，可以采用两种方法。
+ 如果您有权访问源文件，则可以使用`@DynamoDbBean`（或`@DynamoDbImmutable`）为该类添加注释。讨论嵌套属性的部分提供了使用带注释的类的[示例](ddb-en-client-adv-features-nested.md#ddb-en-client-adv-features-nested-map-anno)。
+ 如果无权访问该属性的 JavaBean 数据类的源文件（或者您不想为自己有权访问的类的源文件添加注释），则可以使用生成器方法。这可在不定义键的情况下创建表架构。然后，您可以将此表架构嵌套在另一个表架构中以执行映射。嵌套属性部分提供了使用嵌套架构的[示例](ddb-en-client-adv-features-nested.md#ddb-en-client-adv-features-nested-map-builder)。

### Null 值
<a name="ddb-en-client-use-types-nulls"></a>

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

SDK 对 `updateItem` 请求的默认行为是从 DynamoDB 的项目中移除您在 `updateItem` 方法中提交的对象中设置为 null 的属性。如果您打算更新某些属性值并使其他属性值保持不变，则有两种选择。
+ 在更改值之前（使用 `getItem`）检索项目。通过使用这种方法，SDK 会将所有更新的值和旧值提交给 DynamoDB。
+ 在生成更新项目的请求时，使用 `[IgnoreNullsMode](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/IgnoreNullsMode.html).SCALAR_ONLY` 或 `IgnoreNullsMode.MAPS_ONLY`。两种模式都忽略对象中表示 DynamoDB 中标量属性的 null 值属性。本指南中的 [更新包含复杂类型的项目](ddb-en-client-adv-features-nested.md#ddb-en-client-adv-features-nested-updates) 主题包含有关 `IgnoreNullsMode` 值以及如何处理复杂类型的更多信息。

以下示例演示 `updateItem()` 方法的 `ignoreNullsMode()`。

```
    public static void updateItemNullsExample() {
        Customer customer = new Customer();
        customer.setCustName("CustomerName");
        customer.setEmail("email");
        customer.setId("1");
        customer.setRegistrationDate(Instant.now());

        logger.info("Original customer: {}", customer);

        // Put item with values for all attributes.
        try {
            customerAsyncDynamoDbTable.putItem(customer).join();
        } catch (RuntimeException rte) {
            logger.error("A exception occurred during putItem: {}", rte.getCause().getMessage(), rte);
            return;
        }

        // Create a Customer instance with the same 'id' and 'email' values, but a different 'name' value.
        // Do not set the 'registrationDate' attribute.
        Customer customerForUpdate = new Customer();
        customerForUpdate.setCustName("NewName");
        customerForUpdate.setEmail("email");
        customerForUpdate.setId("1");

        // Update item without setting the 'registrationDate' property and set IgnoreNullsMode to SCALAR_ONLY.
        try {
            Customer updatedWithNullsIgnored = customerAsyncDynamoDbTable.updateItem(b -> b
                            .item(customerForUpdate)
                            .ignoreNullsMode(IgnoreNullsMode.SCALAR_ONLY))
                    .join();
            logger.info("Customer updated with nulls ignored: {}", updatedWithNullsIgnored.toString());
        } catch (RuntimeException rte) {
            logger.error("An exception occurred during updateItem: {}", rte.getCause().getMessage(), rte);
            return;
        }

        // Update item without setting the registrationDate attribute and not setting ignoreNulls to true.
        try {
            Customer updatedWithNullsUsed = customerAsyncDynamoDbTable.updateItem(customerForUpdate)
                    .join();
            logger.info("Customer updated with nulls used: {}", updatedWithNullsUsed.toString());
        } catch (RuntimeException rte) {
            logger.error("An exception occurred during updateItem: {}", rte.getCause().getMessage(), rte);
        }
    }


// Logged lines. 
Original customer: Customer [id=1, name=CustomerName, email=email, regDate=2024-10-11T14:12:30.222858Z]
Customer updated with nulls ignored: Customer [id=1, name=NewName, email=email, regDate=2024-10-11T14:12:30.222858Z]
Customer updated with nulls used: Customer [id=1, name=NewName, email=email, regDate=null]
```

## DynamoDB 增强型客户端基本方法
<a name="ddb-en-client-use-basic-ops"></a>

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

该示例使用了前面所示的 [`Customer` 类](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-anno-bean-cust)。

```
// 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 客户端
<a name="ddb-en-client-use-compare"></a>

[DynamoDB APIs 客户端（标准版[和](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/package-summary.html)增强版）都允许您使用 DynamoDB 表执行 CRUD（创建、读取、更新和删除）数据级操作。](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/package-summary.html)客户之间的区别在于 APIs 它是如何完成的。使用标准客户端，您可以直接处理低级别数据属性。增强型客户端 API 使用熟悉的 Java 类，并映射到后台的低级别 API。

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

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

### 比较：使用标准 DynamoDB 客户端创建表
<a name="ddb-en-client-use-compare-cs1"></a>

```
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 增强型客户端创建表
<a name="ddb-en-client-use-compare-cs2"></a>

```
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 增强型客户端中使用不可变类的一个例子。本主题[后面将讨论](ddb-en-client-use-immut.md)为映射的数据类使用不可变类。

### `ProductCatalog` 类
<a name="ddb-en-client-use-compare-cs3"></a>

```
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);
        }
    }
}
```

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

### 比较：使用标准 DynamoDB 客户端的批量写入操作
<a name="ddb-en-client-use-compare-cs4"></a>

```
    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());
    }
```

### 比较：使用 DynamoDB 增强型客户端的批量写入操作
<a name="ddb-en-client-use-compare-cs5"></a>

```
    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());
    }
```