

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 使用擴充功能自訂 DynamoDB 增強型用戶端操作
<a name="ddb-en-client-extensions"></a>

DynamoDB 增強型用戶端 API 支援外掛程式擴充功能，可提供映射操作以外的功能。延伸模組使用兩種勾點方法來修改讀取和寫入操作期間的資料：
+ `beforeWrite()` - 在寫入操作發生之前對其進行修改
+ `afterRead()` - 修改讀取操作發生後的結果

有些操作 （例如項目更新） 同時執行寫入和讀取，因此會呼叫這兩種勾點方法。

## 如何載入延伸模組
<a name="ddb-en-client-extensions-loading"></a>

延伸項目會依照您在增強型用戶端建置器中指定的順序載入。負載順序可能很重要，因為一個延伸模組可以對先前延伸模組轉換的值採取行動。

根據預設，增強型用戶端會載入兩個延伸模組：
+ `[VersionedRecordExtension](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtension.html)` - 提供樂觀鎖定
+ `[AtomicCounterExtension](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/extensions/AtomicCounterExtension.html)` - 自動遞增計數器屬性

您可以使用增強型用戶端建置器覆寫預設行為，並載入任何延伸。如果您不想要預設副檔名，也可以不指定任何副檔名。

**重要**  
如果您載入自己的擴充功能，增強型用戶端不會載入任何預設擴充功能。如果您想要任一預設延伸模組提供的行為，您需要將其明確新增至延伸模組清單。

下列範例顯示如何載入名為 `verifyChecksumExtension`的自訂延伸`VersionedRecordExtension`模組。此範例中`AtomicCounterExtension`不會載入 。

```
DynamoDbEnhancedClientExtension versionedRecordExtension = VersionedRecordExtension.builder().build();

DynamoDbEnhancedClient enhancedClient = 
    DynamoDbEnhancedClient.builder()
                          .dynamoDbClient(dynamoDbClient)
                          .extensions(versionedRecordExtension, verifyChecksumExtension)
                          .build();
```

## 可用的延伸模組詳細資訊和組態
<a name="ddb-en-client-extensions-details"></a>

下列各節提供 SDK 中每個可用延伸模組的詳細資訊。

### 使用 實作樂觀鎖定 `VersionedRecordExtension`
<a name="ddb-en-client-extensions-VRE"></a>

`VersionedRecordExtension` 延伸項目會在項目寫入資料庫時遞增和追蹤項目版本號碼，以提供樂觀的鎖定。如果實際持續項目的版本編號與應用程式上次讀取的值不符，則每個寫入都會新增一個條件，導致寫入失敗。

#### Configuration
<a name="ddb-en-client-extensions-VRE-conf"></a>

若要指定要用來追蹤項目版本編號的屬性，請在資料表結構描述中標記數值屬性。

下列程式碼片段指定 `version` 屬性應保留項目版本編號。

```
    @DynamoDbVersionAttribute
    public Integer getVersion() {...};
    public void setVersion(Integer version) {...};
```

等效的靜態資料表結構描述方法會顯示在下列程式碼片段中。

```
    .addAttribute(Integer.class, a -> a.name("version")
                                       .getter(Customer::getVersion)
                                       .setter(Customer::setVersion)
                                        // Apply the 'version' tag to the attribute.
                                       .tags(VersionedRecordExtension.AttributeTags.versionAttribute())
```

#### 運作方式
<a name="ddb-en-client-extensions-VRE-how-it-works"></a>

使用 的樂觀鎖定對這些 `DynamoDbEnhancedClient`和 `DynamoDbTable`方法`VersionedRecordExtension`有下列影響：

**`putItem`**  
新項目會獲指派初始版本值 0。這可以使用 設定`@DynamoDbVersionAttribute(startAt = X)`。

**`updateItem`**  
如果您擷取項目、更新其一或多個屬性，並嘗試儲存變更，則只有在用戶端和伺服器端的版本編號相符時，操作才會成功。  
如果成功，版本編號會自動遞增 1。這可以使用 設定`@DynamoDbVersionAttribute(incrementBy = X)`。

**`deleteItem`**  
`DynamoDbVersionAttribute` 註釋沒有效果。刪除項目時，您必須手動新增條件表達式。  
下列範例新增條件式表達式，以確保已刪除的項目是已讀取的項目。在下列範例中`recordVersion`， Bean 的 屬性以 標註`@DynamoDbVersionAttribute`。  

```
// 1. Read the item and get its current version.
Customer item = customerTable.getItem(Key.builder().partitionValue("someId").build());
// `recordVersion` is the bean's attribute that is annotated with `@DynamoDbVersionAttribute`.
AttributeValue currentVersion = item.getRecordVersion();

// 2. Create conditional delete with the `currentVersion` value.
DeleteItemEnhancedRequest deleteItemRequest =
    DeleteItemEnhancedRequest.builder()
       .key(KEY)
       .conditionExpression(Expression.builder()
           .expression("recordVersion = :current_version_value")
           .putExpressionValue(":current_version_value", currentVersion)
           .build()).build();

customerTable.deleteItem(deleteItemRequest);
```

**`transactWriteItems`**  
+ `addPutItem`：此方法具有與 相同的行為`putItem`。
+ `addUpdateItem`：此方法具有與 相同的行為`updateItem`。
+ `addDeleteItem`：此方法具有與 相同的行為`deleteItem`。

**`batchWriteItem`**  
+ `addPutItem`：此方法具有與 相同的行為`putItem`。
+ `addDeleteItem`：此方法具有與 相同的行為`deleteItem`。

**注意**  
DynamoDB 全域資料表在並行更新之間使用[「最後寫入器獲勝」對帳](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/V2globaltables_HowItWorks.html#V2globaltables_HowItWorks.consistency-modes)，其中 DynamoDB 會盡最大努力判斷最後寫入器。如果您使用全域資料表，此「上次寫入器獲勝」政策表示鎖定策略可能無法如預期運作，因為所有複本最終會根據 DynamoDB 決定的上次寫入收斂。

#### 如何停用
<a name="ddb-en-client-extensions-VRE-how-to-disable"></a>

若要停用樂觀鎖定，請勿使用 `@DynamoDbVersionAttribute`註釋。

### 使用 實作計數器 `AtomicCounterExtension`
<a name="ddb-en-client-extensions-ACE"></a>

每次記錄寫入資料庫時，`AtomicCounterExtension`延伸項目都會遞增標記的數值屬性。您可以指定開始值和增量值。如果未指定任何值，則開始值會設為 0，而屬性的值會遞增 1。

#### Configuration
<a name="ddb-en-client-extensions-ACE-conf"></a>

若要指定哪個屬性是計數器，請在資料表結構描述`Long`中標記 類型的屬性。

下列程式碼片段顯示 `counter` 屬性的預設開始值和增量值的使用方式。

```
    @DynamoDbAtomicCounter
    public Long getCounter() {...};
    public void setCounter(Long counter) {...};
```

靜態資料表結構描述方法會顯示在下列程式碼片段中。原子計數器延伸使用 10 的開始值，每次寫入記錄時都會將值增加 5。

```
    .addAttribute(Integer.class, a -> a.name("counter")
                                       .getter(Customer::getCounter)
                                       .setter(Customer::setCounter)
                                        // Apply the 'atomicCounter' tag to the attribute with start and increment values.
                                       .tags(StaticAttributeTags.atomicCounter(10L, 5L))
```

### 使用 新增時間戳記 `AutoGeneratedTimestampRecordExtension`
<a name="ddb-en-client-extensions-AGTE"></a>

每次項目成功寫入資料庫時，`AutoGeneratedTimestampRecordExtension`延伸項目會自動`[Instant](https://docs.oracle.com/javase/8/docs/api/java/time/Instant.html)`使用目前時間戳記更新 類型的標記屬性。預設不會載入此延伸模組。

#### Configuration
<a name="ddb-en-client-extensions-AGTE-conf"></a>

若要指定要使用目前時間戳記更新哪個屬性，請在資料表結構描述中標記 `Instant` 屬性。

`lastUpdate` 屬性是下列程式碼片段中延伸項目行為的目標。請注意， 屬性必須是 `Instant`類型的要求。

```
    @DynamoDbAutoGeneratedTimestampAttribute
    public Instant getLastUpdate() {...}
    public void setLastUpdate(Instant lastUpdate) {...}
```

等效的靜態資料表結構描述方法會顯示在下列程式碼片段中。

```
     .addAttribute(Instant.class, a -> a.name("lastUpdate")
                                        .getter(Customer::getLastUpdate)
                                        .setter(Customer::setLastUpdate)
                                        // Applying the 'autoGeneratedTimestamp' tag to the attribute.
                                        .tags(AutoGeneratedTimestampRecordExtension.AttributeTags.autoGeneratedTimestampAttribute())
```

### 使用 AutoGeneratedUuidExtension 產生 UUID
<a name="ddb-en-client-extensions-AGUE"></a>

`AutoGeneratedUuidExtension` 新記錄寫入資料庫時，延伸項目會產生屬性的唯一 UUID （通用唯一識別符）。使用 Java JDK [UUID.randomUUID()](https://docs.oracle.com/javase/8/docs/api/java/util/UUID.html#randomUUID--) 方法，並套用到 類型的屬性`java.lang.String`。預設不會載入此延伸模組。

#### Configuration
<a name="ddb-en-client-extensions-AGUE-conf"></a>

`uniqueId` 屬性是下列程式碼片段中延伸項目行為的目標。

```
    @AutoGeneratedUuidExtension
    public String getUniqueId() {...}
    public void setUniqueId(String uniqueId) {...}
```

等效的靜態資料表結構描述方法會顯示在下列程式碼片段中。

```
     .addAttribute(String.class, a -> a.name("uniqueId")
                                        .getter(Customer::getUniqueId)
                                        .setter(Customer::setUniqueId)
                                        // Applying the 'autoGeneratedUuid' tag to the attribute.
                                        .tags(AutoGeneratedUuidExtension.AttributeTags.autoGeneratedUuidAttribute())
```

如果您希望延伸模組僅針對 `putItem` 方法填入 UUID，而非針對 `updateItem`方法填入 UUID，請新增[更新行為](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/UpdateBehavior.html)註釋，如下列程式碼片段所示。

```
    @AutoGeneratedUuidExtension
    @DynamoDbUpdateBehavior(UpdateBehavior.WRITE_IF_NOT_EXISTS)
    public String getUniqueId() {...}
    public void setUniqueId(String uniqueId) {...}
```

如果您使用靜態資料表結構描述方法，請使用下列同等程式碼。

```
     .addAttribute(String.class, a -> a.name("uniqueId")
                                        .getter(Customer::getUniqueId)
                                        .setter(Customer::setUniqueId)
                                        // Applying the 'autoGeneratedUuid' tag to the attribute.
                                        .tags(AutoGeneratedUuidExtension.AttributeTags.autoGeneratedUuidAttribute(),
                                              StaticAttributeTags.updateBehavior(UpdateBehavior.WRITE_IF_NOT_EXISTS))
```

# 自訂延伸模組範例
<a name="ddb-en-client-extensions-custom"></a>

您可以實作 `DynamoDbEnhancedClientExtension`界面來建立自訂擴充功能。下列自訂延伸類別顯示一種`beforeWrite()`方法，如果資料庫中的項目還沒有`registrationDate`更新表達式，則使用更新表達式來設定屬性。

```
public final class CustomExtension implements DynamoDbEnhancedClientExtension {

    // 1. In a custom extension, use an UpdateExpression to define what action to take before
    //    an item is updated.
    @Override
    public WriteModification beforeWrite(DynamoDbExtensionContext.BeforeWrite context) {
        if ( context.operationContext().tableName().equals("Customer")
                && context.operationName().equals(OperationName.UPDATE_ITEM)) {
            return WriteModification.builder()
                    .updateExpression(createUpdateExpression())
                    .build();
        }
        return WriteModification.builder().build();  // Return an "empty" WriteModification instance if the extension should not be applied.
                                                     // In this case, if the code is not updating an item on the Customer table.
    }

    private static UpdateExpression createUpdateExpression() {

        // 2. Use a SetAction, a subclass of UpdateAction, to provide the values in the update.
        SetAction setAction =
                SetAction.builder()
                        .path("registrationDate")
                        .value("if_not_exists(registrationDate, :regValue)")
                        .putExpressionValue(":regValue", AttributeValue.fromS(Instant.now().toString()))
                        .build();
        // 3. Build the UpdateExpression with one or more UpdateAction.
        return UpdateExpression.builder()
                .addAction(setAction)
                .build();
    }
}
```