

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

# 含版本編號的 DynamoDB 及樂觀鎖定
<a name="DynamoDBMapper.OptimisticLocking"></a>

*樂觀鎖定*是一種策略，確保您要更新 (或刪除) 的用戶端項目與 Amazon DynamoDB 中的項目相同。如果您使用此策略，則會保護其他項目的寫入不會覆寫資料庫寫入，反之亦然。

使用樂觀鎖定，每個項目都有做為版本編號的屬性。如果您從資料表中擷取項目，則應用程式會記錄該項目的版本編號。您可以更新項目，但只有在伺服器端的版本編號尚未變更時。若版本不相符，表示已有其他人在您之前進行修改。更新嘗試失敗，因為您有此項目的過期版本。如果發生這種情況，請檢索項目然後嘗試進行更新以重試一次。樂觀鎖定預防您意外覆寫其他人所作的變更。同時也預防其他人意外覆寫您所作的變更。

雖然您可以實作自己的樂觀鎖定策略，但 適用於 Java 的 AWS SDK 提供 `@DynamoDBVersionAttribute`註釋。在資料表的映射類別中，您指定一個屬性來存放版本編號，並使用此註釋進行標示。當您儲存物件時，DynamoDB 資料表中的對應項目會有屬性可存放版本編號。當您第一次儲存物件時，`DynamoDBMapper` 會指派版本編號，並在每次更新項目時自動遞增版本編號。只有在用戶端物件版本與 DynamoDB 資料表中項目的對應版本編號相符時，您的更新或刪除請求才會成功。

 如果發生下列情況，則會擲回 `ConditionalCheckFailedException`：
+  您使用含 `@DynamoDBVersionAttribute` 的樂觀鎖定，而且伺服器上的版本值與用戶端上的值不同。
+  您可以搭配使用 `DynamoDBMapper` 與 `DynamoDBSaveExpression`，以在儲存資料時指定自己的條件式限制條件，而且這些限制條件會失敗。

**注意**  
DynamoDB 全域資料表會在並行更新間使用「最後寫入者獲勝」核對機制。如果您使用的是全域資料表，最後寫入者政策獲勝。因此，在此情況下，鎖定政策不會如預期般運作。
`DynamoDBMapper` 交易寫入操作不支援同一物件的 `@DynamoDBVersionAttribute` 標註和條件表達式。如果交易寫入內的物件以 `@DynamoDBVersionAttribute` 標註，且具有條件表達式，則會拋出 SdkClientException。

例如，下列 Java 程式碼定義有數個屬性的 `CatalogItem` 類別。`Version` 屬性會標上 `@DynamoDBVersionAttribute` 註釋。

**Example**  

```
@DynamoDBTable(tableName="ProductCatalog")
public class CatalogItem {

    private Integer id;
    private String title;
    private String ISBN;
    private Set<String> bookAuthors;
    private String someProp;
    private Long version;

    @DynamoDBHashKey(attributeName="Id")
    public Integer getId() { return id; }
    public void setId(Integer Id) { this.id = Id; }

    @DynamoDBAttribute(attributeName="Title")
    public String getTitle() { return title; }
    public void setTitle(String title) { this.title = title; }

    @DynamoDBAttribute(attributeName="ISBN")
    public String getISBN() { return ISBN; }
    public void setISBN(String ISBN) { this.ISBN = ISBN;}

    @DynamoDBAttribute(attributeName = "Authors")
    public Set<String> getBookAuthors() { return bookAuthors; }
    public void setBookAuthors(Set<String> bookAuthors) { this.bookAuthors = bookAuthors; }

    @DynamoDBIgnore
    public String getSomeProp() { return someProp;}
    public void setSomeProp(String someProp) {this.someProp = someProp;}

    @DynamoDBVersionAttribute
    public Long getVersion() { return version; }
    public void setVersion(Long version) { this.version = version;}
}
```

您可以將 `@DynamoDBVersionAttribute` 註釋套用至基本包裝函式類別所提供的可為 Null 類型，而基本包裝函式類別提供可為 Null 類型 (例如 `Long` 和 `Integer`)。

樂觀鎖定對這些 `DynamoDBMapper` 方法的影響如下：
+ `save`：針對新項目，`DynamoDBMapper` 會指派初始版本編號 1。如果您擷取項目，請更新其一或多個屬性並嘗試儲存變更，只有在用戶端的版本編號與伺服器端的相符時，儲存操作才會成功。`DynamoDBMapper` 會自動遞增版本編號。
+ `delete`：`delete` 方法會採用物件作為參數，而 `DynamoDBMapper` 會在刪除項目之前執行版本檢查。如果請求中指定 `DynamoDBMapperConfig.SaveBehavior.CLOBBER`，則可以停用版本檢查。

  `DynamoDBMapper` 內的樂觀鎖定內部實作，使用 DynamoDB 所提供的條件式更新和條件式刪除支援。
+ `transactionWrite` —
  + `Put`：針對新項目，`DynamoDBMapper` 會指派初始版本編號 1。如果您擷取項目，請更新其一或多個屬性並嘗試儲存變更，只有在用戶端的版本編號與伺服器端的相符時，Put 操作才會成功。`DynamoDBMapper` 會自動遞增版本編號。
  + `Update`：針對新項目，`DynamoDBMapper` 會指派初始版本編號 1。如果您擷取項目，請更新其一或多個屬性並嘗試儲存變更，只有在用戶端的版本編號與伺服器端的相符時，update 操作才會成功。`DynamoDBMapper` 會自動遞增版本編號。
  + `Delete`：`DynamoDBMapper` 會在刪除項目之前執行版本檢查。只有用戶端與伺服器端的版本號碼相符時，delete 操作才會成功。
  + `ConditionCheck`：不支援 `ConditionCheck` 操作的 `@DynamoDBVersionAttribute` 註釋。以 `@DynamoDBVersionAttribute` 標註 `ConditionCheck` 項目時，會拋出 SdkClientException。

## 停用樂觀鎖定
<a name="DynamoDBMapper.OptimisticLocking.Disabling"></a>

若要停用樂觀鎖定，您可以將 `DynamoDBMapperConfig.SaveBehavior` 列舉值從 `UPDATE` 變更為 `CLOBBER`。您可以建立跳過版本檢查的 `DynamoDBMapperConfig` 執行個體來執行這項操作，並將此執行個體用於所有請求。如需 `DynamoDBMapperConfig.SaveBehavior` 和其他選用 `DynamoDBMapper` 參數的資訊，請參閱「[DynamoDBMapper 的選用組態設定](DynamoDBMapper.OptionalConfig.md)」。

您也只能設定特定操作的鎖定行為。例如，下列 Java 程式碼片段使用 `DynamoDBMapper` 來儲存型錄項目。指定 `DynamoDBMapperConfig.SaveBehavior` 的方式是將選用 `DynamoDBMapperConfig` 參數新增至 `save` 方法。

**注意**  
transactionWrite 方法不支援 DynamoDBMapperConfig.SaveBehavior 組態。不支援停用 transactionWrite 的樂觀鎖定。

**Example**  

```
DynamoDBMapper mapper = new DynamoDBMapper(client);

// Load a catalog item.
CatalogItem item = mapper.load(CatalogItem.class, 101);
item.setTitle("This is a new title for the item");
...
// Save the item.
mapper.save(item,
    new DynamoDBMapperConfig(
        DynamoDBMapperConfig.SaveBehavior.CLOBBER));
```