DynamoDB 和乐观锁(使用版本号) - Amazon DynamoDB

DynamoDB 和乐观锁(使用版本号)

乐观锁是一种确保正在更新(或删除)的客户端项目与 Amazon DynamoDB 中的项目相同的策略。如果您使用此策略,则将防止数据库写入由他人的写入覆盖,反之亦然。

使用乐观锁时,每个项目都具有一个充当版本号的属性。如果您检索表中的项目,则应用程序会记录该项目的版本号。您可以更新该项目,但只有在服务器端的版本号没有改变时才能更新。如果存在版本不匹配,则意味着其他人在您之前修改了该项目。更新尝试会失败,这是因为您拥有的是该项目的过时版本。如果发生此情况,您可以通过检索项目然后尝试更新来重试。乐观锁可防止您意外覆盖他人所做的更改。它还可防止他人意外覆盖您所做的更改。

虽然您可以实现自己的乐观锁策略,但AWS SDK for Java 提供了 @DynamoDBVersionAttribute 注释。在适用于表的映射类中,您需要指定一个用于存储版本号的属性,并使用此注释对其进行标记。当您保存对象时,DynamoDB 表中对应的项目就会具有存储相应版本号的属性。DynamoDBMapper 会在您第一次保存对象时分配一个版本号,并且在每次更新项目时递增版本号的值。只有在客户端对象版本与 DynamoDB 表中对应的项目版本号相匹配时,您的更新或删除请求才会成功。

如果出现以下情况,则会引发 ConditionalCheckFailedException

  • 您使用的乐观锁具有 @DynamoDBVersionAttribute,而服务器上的版本值与客户端的值不同。

  • 您在使用 DynamoDBMapperDynamoDBSaveExpression 保存数据时指定了自己的条件约束,而这些约束失败了。

注意
  • DynamoDB 全局表在并发更新之间使用“以最后写入者为准”原则。如果使用全局表,则以最后写入者策略为准。因此,在这种情况下,锁定策略无法按预期方式工作。

  • DynamoDBMapper 事务写入操作在同一对象中不支持 @DynamoDBVersionAttribute 注释和条件表达式。如果事务写入中的对象使用 @DynamoDBVersionAttribute 进行了注释,并且还包含条件表达式,则将引发 SdkClientException。

例如,以下 Java 代码定义的 CatalogItem 类具有多个属性。Version 属性由 @DynamoDBVersionAttribute 注释进行标记。

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

乐观锁对这些 DynamoDBMapper 方法具有以下影响:

  • save— 对于新项目,DynamoDBMapper 分配初始版本号 1。如果您检索项目,然后更新它的一个或多个属性,并尝试保存所做更改,那么只有在客户端和服务器端的版本号匹配时保存操作才能成功。DynamoDBMapper 会自动递增版本号。

  • deletedelete 方法接受对象作为参数,并且 DynamoDBMapper 会在删除项目之前执行版本检查。在请求中指定 DynamoDBMapperConfig.SaveBehavior.CLOBBER 可以禁用版本检查。

    DynamoDBMapper 中的内部乐观锁实现利用了 DynamoDB 提供的有条件更新和有条件删除支持。

  • transactionWrite

    • Put— 对于新项目,DynamoDBMapper 分配初始版本号 1。如果您检索项目,然后更新它的一个或多个属性,并尝试保存所做更改,那么只有在客户端和服务器端的版本号匹配时放置操作才能成功。DynamoDBMapper 会自动递增版本号。

    • Update— 对于新项目,DynamoDBMapper 分配初始版本号 1。如果您检索项目,然后更新它的一个或多个属性,并尝试保存所做更改,那么只有在客户端和服务器端的版本号匹配时更新操作才能成功。DynamoDBMapper 会自动递增版本号。

    • DeleteDynamoDBMapper 会在删除项目之前执行版本检查。仅当客户端与服务器端的版本号相匹配时,删除操作才会成功。

    • ConditionCheckConditionCheck 操作不支持 @DynamoDBVersionAttribute 注释。当 ConditionCheck 项目使用 @DynamoDBVersionAttribute 进行了注释时,将引发 SdkClientException。

禁用乐观锁

要禁用乐观锁,您可以将 DynamoDBMapperConfig.SaveBehavior 枚举值从 UPDATE 更改为 CLOBBER。您可以通过创建可跳过版本检查的 DynamoDBMapperConfig 实例,然后在所有请求中使用此实例来实现这一目的。有关 DynamoDBMapperConfig.SaveBehavior 和其他可选 DynamoDBMapper 参数的信息,请参阅DynamoDBMapper 的可选配置设置

您也可以针对特定操作设置锁定行为。例如,以下 Java 代码段使用 DynamoDBMapper 保存目录项目。它可以通过将可选 DynamoDBMapperConfig.SaveBehavior 参数添加到 DynamoDBMapperConfig 方法来指定 save

注意

transactionWrite 方法不支持 DynamoDBMapperConfig.SaveBehavior 配置。不支持对 transactionWrite 禁用乐观锁。

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