DynamoDB 和乐观锁(使用版本号)
乐观锁是一种确保正在更新(或删除)的客户端项目与 Amazon DynamoDB 中的项目相同的策略。如果您使用此策略,则将防止数据库写入由他人的写入覆盖,反之亦然。
使用乐观锁时,每个项目都具有一个充当版本号的属性。如果您检索表中的项目,则应用程序会记录该项目的版本号。您可以更新该项目,但只有在服务器端的版本号没有改变时才能更新。如果存在版本不匹配,则意味着其他人在您之前修改了该项目。更新尝试会失败,这是因为您拥有的是该项目的过时版本。如果发生此情况,您可以通过检索项目然后尝试更新来重试。乐观锁可防止您意外覆盖他人所做的更改。它还可防止他人意外覆盖您所做的更改。
虽然您可以实现自己的乐观锁策略,但AWS SDK for Java 提供了 @DynamoDBVersionAttribute
注释。在适用于表的映射类中,您需要指定一个用于存储版本号的属性,并使用此注释对其进行标记。当您保存对象时,DynamoDB 表中对应的项目就会具有存储相应版本号的属性。DynamoDBMapper
会在您第一次保存对象时分配一个版本号,并且在每次更新项目时递增版本号的值。只有在客户端对象版本与 DynamoDB 表中对应的项目版本号相匹配时,您的更新或删除请求才会成功。
如果出现以下情况,则会引发 ConditionalCheckFailedException
:
-
您使用的乐观锁具有
@DynamoDBVersionAttribute
,而服务器上的版本值与客户端的值不同。 -
您在使用
DynamoDBMapper
与DynamoDBSaveExpression
保存数据时指定了自己的条件约束,而这些约束失败了。
注意
-
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
注释应用到基元封装类提供的可为空的类型 (例如 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。
-
禁用乐观锁
要禁用乐观锁,您可以将 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));