执行事务操作 - AWS SDK for Java 2.x

本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。

执行事务操作

DynamoDB 增强型客户端 API 提供了 transactGetItems()transactWriteItems() 方法。适用于 Java 的 SDK 的事务方法在 DynamoDB 表中提供原子性、一致性、隔离性和持久性 (ACID),帮助您维护应用程序中的数据正确性。

transactGetItems() 示例

transactGetItems() 方法最多可接受 100 个单独的项目请求。所有项目都在单个原子事务中读取。《Amazon DynamoDB 开发人员指南》包含有关导致 transactGetItems() 方法失败的条件以及您调用 transactGetItem() 时使用的隔离级别的信息。

在以下示例的注释行 1 之后,代码使用 builder 参数调用 transactGetItems() 方法。使用一个数据对象调用生成器的 addGetItem() 三次,该数据对象包含 SDK 将用于生成最终请求的键值。

该请求在注释行 2 之后返回 Document 对象列表。返回的文档列表包含项目数据的非空 Document 实例,其顺序与请求的顺序相同。如果返回了项目数据,则 Document.getItem(MappedTableResource<T> mappedTableResource) 方法会将非类型化 Document 对象转换为类型化的 Java 对象,否则该方法返回 null。

public static void transactGetItemsExample(DynamoDbEnhancedClient enhancedClient, DynamoDbTable<ProductCatalog> catalogTable, DynamoDbTable<MovieActor> movieActorTable) { // 1. Request three items from two tables using a builder. final List<Document> documents = enhancedClient.transactGetItems(b -> b .addGetItem(catalogTable, Key.builder().partitionValue(2).sortValue("Title 55").build()) .addGetItem(movieActorTable, Key.builder().partitionValue("Sophie's Choice").sortValue("Meryl Streep").build()) .addGetItem(movieActorTable, Key.builder().partitionValue("Blue Jasmine").sortValue("Cate Blanchett").build()) .build()); // 2. A list of Document objects is returned in the same order as requested. ProductCatalog title55 = documents.get(0).getItem(catalogTable); if (title55 != null) { logger.info(title55.toString()); } MovieActor sophiesChoice = documents.get(1).getItem(movieActorTable); if (sophiesChoice != null) { logger.info(sophiesChoice.toString()); } // 3. The getItem() method returns null if the Document object contains no item from DynamoDB. MovieActor blueJasmine = documents.get(2).getItem(movieActorTable); if (blueJasmine != null) { logger.info(blueJasmine.toString()); } }

在代码示例运行之前,DynamoDB 表包含以下项目。

ProductCatalog{id=2, title='Title 55', isbn='orig_isbn', authors=[b, g], price=10} MovieActor{movieName='Sophie's Choice', actorName='Meryl Streep', actingAward='Best Actress', actingYear=1982, actingSchoolName='Yale School of Drama'}

记录了以下输出。如果请求了某个项目但未找到,则该项目不会返回(像请求名为 Blue Jasmine 的电影那样)。

ProductCatalog{id=2, title='Title 55', isbn='orig_isbn', authors=[b, g], price=10} MovieActor{movieName='Sophie's Choice', actorName='Meryl Streep', actingAward='Best Actress', actingYear=1982, actingSchoolName='Yale School of Drama'}

transactWriteItems() 示例

transactWriteItems() 在跨多个表的单个原子事务中最多接受 100 个放置、更新或删除操作。《Amazon DynamoDB 开发人员指南》包含有关底层 DynamoDB 服务操作的限制和失败条件的详细信息。

基本示例

在以下示例中,请求对两个表执行四个操作。之前已显示了相应的模型类 ProductCatalogMovieActor

三种可能的操作(放置、更新和删除)均使用专用的请求参数来指定详细信息。

注释行 1 之后的代码显示了 addPutItem() 方法的简单变体。该方法接受要放置的 MappedTableResource 对象和数据对象实例。注释行 2 之后的语句显示了接受 TransactPutItemEnhancedRequest 实例的变体。此变体允许您在请求中添加更多选项,例如条件表达式。随后的示例显示了单个操作的条件表达式。

在注释行 3 之后请求更新操作。TransactUpdateItemEnhancedRequest 有一种 ignoreNulls() 方法可以让您配置 SDK 如何处理模型对象上的 null 值。如果 ignoreNulls() 方法返回 true,则对于数据对象属性为 null 的情况,SDK 不会移除表的属性值。如果 ignoreNulls() 方法返回 false,则 SDK 会请求 DynamoDB 服务从表中的项目中移除这些属性。ignoreNulls 的默认值为 false。

注释行 4 之后的语句显示了接受数据对象的删除请求的变体。增强型客户端在分派最终请求之前提取键值。

public static void transactWriteItems(DynamoDbEnhancedClient enhancedClient, DynamoDbTable<ProductCatalog> catalogTable, DynamoDbTable<MovieActor> movieActorTable) { enhancedClient.transactWriteItems(b -> b // 1. Simplest variation of put item request. .addPutItem(catalogTable, getProductCatId2()) // 2. Put item request variation that accommodates condition expressions. .addPutItem(movieActorTable, TransactPutItemEnhancedRequest.builder(MovieActor.class) .item(getMovieActorStreep()) .conditionExpression(Expression.builder().expression("attribute_not_exists (movie)").build()) .build()) // 3. Update request that does not remove attribute values on the table if the data object's value is null. .addUpdateItem(catalogTable, TransactUpdateItemEnhancedRequest.builder(ProductCatalog.class) .item(getProductCatId4ForUpdate()) .ignoreNulls(Boolean.TRUE) .build()) // 4. Variation of delete request that accepts a data object. The key values are extracted for the request. .addDeleteItem(movieActorTable, getMovieActorBlanchett()) ); }

以下帮助程序方法为 add*Item 参数提供了数据对象。

public static ProductCatalog getProductCatId2() { return ProductCatalog.builder() .id(2) .isbn("1-565-85698") .authors(new HashSet<>(Arrays.asList("a", "b"))) .price(BigDecimal.valueOf(30.22)) .title("Title 55") .build(); } public static ProductCatalog getProductCatId4ForUpdate() { return ProductCatalog.builder() .id(4) .price(BigDecimal.valueOf(40.00)) .title("Title 1") .build(); } public static MovieActor getMovieActorBlanchett() { MovieActor movieActor = new MovieActor(); movieActor.setActorName("Cate Blanchett"); movieActor.setMovieName("Tar"); movieActor.setActingYear(2022); movieActor.setActingAward("Best Actress"); movieActor.setActingSchoolName("National Institute of Dramatic Art"); return movieActor; } public static MovieActor getMovieActorStreep() { MovieActor movieActor = new MovieActor(); movieActor.setActorName("Meryl Streep"); movieActor.setMovieName("Sophie's Choice"); movieActor.setActingYear(1982); movieActor.setActingAward("Best Actress"); movieActor.setActingSchoolName("Yale School of Drama"); return movieActor; }

在代码示例运行之前,DynamoDB 表包含以下项目。

1 | ProductCatalog{id=4, title='Title 1', isbn='orig_isbn', authors=[b, g], price=10} 2 | MovieActor{movieName='Tar', actorName='Cate Blanchett', actingAward='Best Actress', actingYear=2022, actingSchoolName='National Institute of Dramatic Art'}

代码运行完毕后,表中将显示以下项目。

3 | ProductCatalog{id=2, title='Title 55', isbn='1-565-85698', authors=[a, b], price=30.22} 4 | ProductCatalog{id=4, title='Title 1', isbn='orig_isbn', authors=[b, g], price=40.0} 5 | MovieActor{movieName='Sophie's Choice', actorName='Meryl Streep', actingAward='Best Actress', actingYear=1982, actingSchoolName='Yale School of Drama'}

第 2 行的项目已被删除,第 3 行和第 5 行显示了已放置的项目。第 4 行显示第 1 行的更新。price 值是该项目上唯一更改的值。如果 ignoreNulls() 返回 false,第 4 行将类似于以下行。

ProductCatalog{id=4, title='Title 1', isbn='null', authors=null, price=40.0}

条件检查示例

以下示例演示条件检查的使用。条件检查用于检查项目是否存在,或者检查数据库中项目特定属性的条件。条件检查中检查的项目不能用于事务中的其他操作。

注意

您不能将同一个事务中的多个操作指向同一个项目。例如,您不能在同一个事务中对相同项目既执行条件检查又执行更新操作。

该示例演示事务写入项目请求中的每种操作类型。在注释行 2 之后,如果 conditionExpression 参数的计算结果为 false,则 addConditionCheck() 方法会提供事务失败的条件。从帮助程序方法块中显示的方法返回的条件表达式会检查电影 Sophie's Choice 的获奖年份是否不等于 1982。如果是,则表达式的计算结果为 false,事务将失败。

本指南的另一个主题深入讨论了表达式

public static void conditionCheckFailExample(DynamoDbEnhancedClient enhancedClient, DynamoDbTable<ProductCatalog> catalogTable, DynamoDbTable<MovieActor> movieActorTable) { try { enhancedClient.transactWriteItems(b -> b // 1. Perform one of each type of operation with the next three methods. .addPutItem(catalogTable, TransactPutItemEnhancedRequest.builder(ProductCatalog.class) .item(getProductCatId2()).build()) .addUpdateItem(catalogTable, TransactUpdateItemEnhancedRequest.builder(ProductCatalog.class) .item(getProductCatId4ForUpdate()) .ignoreNulls(Boolean.TRUE).build()) .addDeleteItem(movieActorTable, TransactDeleteItemEnhancedRequest.builder() .key(b1 -> b1 .partitionValue(getMovieActorBlanchett().getMovieName()) .sortValue(getMovieActorBlanchett().getActorName())).build()) // 2. Add a condition check on a table item that is not involved in another operation in this request. .addConditionCheck(movieActorTable, ConditionCheck.builder() .conditionExpression(buildConditionCheckExpression()) .key(k -> k .partitionValue("Sophie's Choice") .sortValue("Meryl Streep")) // 3. Specify the request to return existing values from the item if the condition evaluates to true. .returnValuesOnConditionCheckFailure(ReturnValuesOnConditionCheckFailure.ALL_OLD) .build()) .build()); // 4. Catch the exception if the transaction fails and log the information. } catch (TransactionCanceledException ex) { ex.cancellationReasons().stream().forEach(cancellationReason -> { logger.info(cancellationReason.toString()); }); } }

前面的代码示例中使用了以下帮助程序方法。

private static Expression buildConditionCheckExpression() { Map<String, AttributeValue> expressionValue = Map.of( ":year", numberValue(1982)); return Expression.builder() .expression("actingyear <> :year") .expressionValues(expressionValue) .build(); } public static ProductCatalog getProductCatId2() { return ProductCatalog.builder() .id(2) .isbn("1-565-85698") .authors(new HashSet<>(Arrays.asList("a", "b"))) .price(BigDecimal.valueOf(30.22)) .title("Title 55") .build(); } public static ProductCatalog getProductCatId4ForUpdate() { return ProductCatalog.builder() .id(4) .price(BigDecimal.valueOf(40.00)) .title("Title 1") .build(); } public static MovieActor getMovieActorBlanchett() { MovieActor movieActor = new MovieActor(); movieActor.setActorName("Cate Blanchett"); movieActor.setMovieName("Blue Jasmine"); movieActor.setActingYear(2013); movieActor.setActingAward("Best Actress"); movieActor.setActingSchoolName("National Institute of Dramatic Art"); return movieActor; }

在代码示例运行之前,DynamoDB 表包含以下项目。

1 | ProductCatalog{id=4, title='Title 1', isbn='orig_isbn', authors=[b, g], price=10} 2 | MovieActor{movieName='Sophie's Choice', actorName='Meryl Streep', actingAward='Best Actress', actingYear=1982, actingSchoolName='Yale School of Drama'} 3 | MovieActor{movieName='Tar', actorName='Cate Blanchett', actingAward='Best Actress', actingYear=2022, actingSchoolName='National Institute of Dramatic Art'}

代码运行完毕后,表中将显示以下项目。

ProductCatalog{id=4, title='Title 1', isbn='orig_isbn', authors=[b, g], price=10} MovieActor{movieName='Sophie's Choice', actorName='Meryl Streep', actingAward='Best Actress', actingYear=1982, actingSchoolName='Yale School of Drama'} MovieActor{movieName='Tar', actorName='Cate Blanchett', actingAward='Best Actress', actingYear=2022, actingSchoolName='National Institute of Dramatic Art'}

由于事务失败,表中的项目保持不变。影片 Sophie's ChoiceactingYear 值为 1982,如调用 transactWriteItem() 方法之前表中项目的第 2 行所示。

要捕获事务的取消信息,请将 transactWriteItems() 方法调用封装在一个 try 块中,然后 catch TransactionCanceledException。在示例的注释行 4 之后,代码会记录每个 CancellationReason 对象。由于示例注释行 3 之后的代码指定应返回导致事务失败的项目的值,因此日志会显示电影项目 Sophie's Choice 的原始数据库值。

CancellationReason(Code=None) CancellationReason(Code=None) CancellationReason(Code=None) CancellationReason(Item={actor=AttributeValue(S=Meryl Streep), movie=AttributeValue(S=Sophie's Choice), actingaward=AttributeValue(S=Best Actress), actingyear=AttributeValue(N=1982), actingschoolname=AttributeValue(S=Yale School of Drama)}, ¬ Code=ConditionalCheckFailed, Message=The conditional request failed.)

单一操作条件示例

以下示例演示在事务请求中对单个操作使用条件的情况。注释行 1 之后的删除操作包含一个条件,该条件用于根据数据库检查操作的目标项目的值。在此示例中,在注释行 2 之后使用帮助程序方法创建的条件表达式指定,如果电影的上映年份不等于 2013 年,则应从数据库中删除该项目。

本指南稍后将讨论表达式

public static void singleOperationConditionFailExample(DynamoDbEnhancedClient enhancedClient, DynamoDbTable<ProductCatalog> catalogTable, DynamoDbTable<MovieActor> movieActorTable) { try { enhancedClient.transactWriteItems(b -> b .addPutItem(catalogTable, TransactPutItemEnhancedRequest.builder(ProductCatalog.class) .item(getProductCatId2()) .build()) .addUpdateItem(catalogTable, TransactUpdateItemEnhancedRequest.builder(ProductCatalog.class) .item(getProductCatId4ForUpdate()) .ignoreNulls(Boolean.TRUE).build()) // 1. Delete operation that contains a condition expression .addDeleteItem(movieActorTable, TransactDeleteItemEnhancedRequest.builder() .key((Key.Builder k) -> { MovieActor blanchett = getMovieActorBlanchett(); k.partitionValue(blanchett.getMovieName()) .sortValue(blanchett.getActorName()); }) .conditionExpression(buildDeleteItemExpression()) .returnValuesOnConditionCheckFailure(ReturnValuesOnConditionCheckFailure.ALL_OLD) .build()) .build()); } catch (TransactionCanceledException ex) { ex.cancellationReasons().forEach(cancellationReason -> logger.info(cancellationReason.toString())); } } // 2. Provide condition expression to check if 'actingyear' is not equal to 2013. private static Expression buildDeleteItemExpression() { Map<String, AttributeValue> expressionValue = Map.of( ":year", numberValue(2013)); return Expression.builder() .expression("actingyear <> :year") .expressionValues(expressionValue) .build(); }

前面的代码示例中使用了以下帮助程序方法。

public static ProductCatalog getProductCatId2() { return ProductCatalog.builder() .id(2) .isbn("1-565-85698") .authors(new HashSet<>(Arrays.asList("a", "b"))) .price(BigDecimal.valueOf(30.22)) .title("Title 55") .build(); } public static ProductCatalog getProductCatId4ForUpdate() { return ProductCatalog.builder() .id(4) .price(BigDecimal.valueOf(40.00)) .title("Title 1") .build(); } public static MovieActor getMovieActorBlanchett() { MovieActor movieActor = new MovieActor(); movieActor.setActorName("Cate Blanchett"); movieActor.setMovieName("Blue Jasmine"); movieActor.setActingYear(2013); movieActor.setActingAward("Best Actress"); movieActor.setActingSchoolName("National Institute of Dramatic Art"); return movieActor; }

在代码示例运行之前,DynamoDB 表包含以下项目。

1 | ProductCatalog{id=4, title='Title 1', isbn='orig_isbn', authors=[b, g], price=10} 2 | MovieActor{movieName='Blue Jasmine', actorName='Cate Blanchett', actingAward='Best Actress', actingYear=2013, actingSchoolName='National Institute of Dramatic Art'}

代码运行完毕后,表中将显示以下项目。

ProductCatalog{id=4, title='Title 1', isbn='orig_isbn', authors=[b, g], price=10} 2023-03-15 11:29:07 [main] INFO org.example.tests.TransactDemoTest:168 - MovieActor{movieName='Blue Jasmine', actorName='Cate Blanchett', actingAward='Best Actress', actingYear=2013, actingSchoolName='National Institute of Dramatic Art'}

由于事务失败,表中的项目保持不变。在代码示例运行之前,影片 Blue JasmineactingYear 值为 2013,如项目列表第 2 行所示。

以下行记录到控制台。

CancellationReason(Code=None) CancellationReason(Code=None) CancellationReason(Item={actor=AttributeValue(S=Cate Blanchett), movie=AttributeValue(S=Blue Jasmine), actingaward=AttributeValue(S=Best Actress), actingyear=AttributeValue(N=2013), actingschoolname=AttributeValue(S=National Institute of Dramatic Art)}, Code=ConditionalCheckFailed, Message=The conditional request failed)