

# 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` 注释应用到基元封装类提供的可为空的类型 (例如 `Long` 和 `Integer`)。

乐观锁对这些 `DynamoDBMapper` 方法具有以下影响：
+ `save`— 对于新项目，`DynamoDBMapper` 分配初始版本号 1。如果您检索项目，然后更新它的一个或多个属性，并尝试保存所做更改，那么只有在客户端和服务器端的版本号匹配时保存操作才能成功。`DynamoDBMapper` 会自动递增版本号。
+ `delete` — `delete` 方法接受对象作为参数，并且 `DynamoDBMapper` 会在删除项目之前执行版本检查。在请求中指定 `DynamoDBMapperConfig.SaveBehavior.CLOBBER` 可以禁用版本检查。

  `DynamoDBMapper` 中的内部乐观锁实现利用了 DynamoDB 提供的有条件更新和有条件删除支持。
+ `transactionWrite` —
  + `Put`— 对于新项目，`DynamoDBMapper` 分配初始版本号 1。如果您检索项目，然后更新它的一个或多个属性，并尝试保存所做更改，那么只有在客户端和服务器端的版本号匹配时放置操作才能成功。`DynamoDBMapper` 会自动递增版本号。
  + `Update`— 对于新项目，`DynamoDBMapper` 分配初始版本号 1。如果您检索项目，然后更新它的一个或多个属性，并尝试保存所做更改，那么只有在客户端和服务器端的版本号匹配时更新操作才能成功。`DynamoDBMapper` 会自动递增版本号。
  + `Delete`—`DynamoDBMapper` 会在删除项目之前执行版本检查。仅当客户端与服务器端的版本号相匹配时，删除操作才会成功。
  + `ConditionCheck` — `ConditionCheck` 操作不支持 `@DynamoDBVersionAttribute` 注释。当 `ConditionCheck` 项目使用 `@DynamoDBVersionAttribute` 进行了注释时，将引发 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));
```