diff --git a/spring-cloud-aws-dynamodb/src/main/java/io/awspring/cloud/dynamodb/DynamoDbOperations.java b/spring-cloud-aws-dynamodb/src/main/java/io/awspring/cloud/dynamodb/DynamoDbOperations.java index 8c9eea223..1ceacb7a8 100644 --- a/spring-cloud-aws-dynamodb/src/main/java/io/awspring/cloud/dynamodb/DynamoDbOperations.java +++ b/spring-cloud-aws-dynamodb/src/main/java/io/awspring/cloud/dynamodb/DynamoDbOperations.java @@ -17,7 +17,9 @@ import org.springframework.lang.Nullable; import software.amazon.awssdk.enhanced.dynamodb.Key; +import software.amazon.awssdk.enhanced.dynamodb.model.DeleteItemEnhancedRequest; import software.amazon.awssdk.enhanced.dynamodb.model.PageIterable; +import software.amazon.awssdk.enhanced.dynamodb.model.PutItemEnhancedRequest; import software.amazon.awssdk.enhanced.dynamodb.model.QueryEnhancedRequest; import software.amazon.awssdk.enhanced.dynamodb.model.ScanEnhancedRequest; import software.amazon.awssdk.enhanced.dynamodb.model.UpdateItemEnhancedRequest; @@ -39,6 +41,15 @@ public interface DynamoDbOperations { */ T save(T entity); + /** + * Saves an item in DynamoDB using the provided PutItemEnhancedRequest. + * + * @param request the request object containing the item to be saved + * + * @see PutItemEnhancedRequest + */ + void save(PutItemEnhancedRequest request); + /** * Updates Entity to DynamoDB table. * @@ -71,6 +82,17 @@ public interface DynamoDbOperations { */ T delete(T entity); + /** + * Deletes a record for a given DeleteItemEnhancedRequest. + * + * @param request the request object containing the item to be deleted + * @param clazz the class type of the item to be deleted so + * {@link software.amazon.awssdk.enhanced.dynamodb.TableSchema} can be generated. + * + * @see DeleteItemEnhancedRequest + */ + T delete(DeleteItemEnhancedRequest request, Class clazz); + /** * Loads entity for a given Key. * diff --git a/spring-cloud-aws-dynamodb/src/main/java/io/awspring/cloud/dynamodb/DynamoDbTemplate.java b/spring-cloud-aws-dynamodb/src/main/java/io/awspring/cloud/dynamodb/DynamoDbTemplate.java index 822ff5fdf..f1829c288 100644 --- a/spring-cloud-aws-dynamodb/src/main/java/io/awspring/cloud/dynamodb/DynamoDbTemplate.java +++ b/spring-cloud-aws-dynamodb/src/main/java/io/awspring/cloud/dynamodb/DynamoDbTemplate.java @@ -21,7 +21,9 @@ import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient; import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable; import software.amazon.awssdk.enhanced.dynamodb.Key; +import software.amazon.awssdk.enhanced.dynamodb.model.DeleteItemEnhancedRequest; import software.amazon.awssdk.enhanced.dynamodb.model.PageIterable; +import software.amazon.awssdk.enhanced.dynamodb.model.PutItemEnhancedRequest; import software.amazon.awssdk.enhanced.dynamodb.model.QueryEnhancedRequest; import software.amazon.awssdk.enhanced.dynamodb.model.ScanEnhancedRequest; import software.amazon.awssdk.enhanced.dynamodb.model.UpdateItemEnhancedRequest; @@ -64,6 +66,12 @@ public T save(T entity) { return entity; } + public void save(PutItemEnhancedRequest request) { + Assert.notNull(request, "putItemEnhancedRequest is required"); + Assert.notNull(request.item(), "request item is required"); + prepareTable(request.item()).putItem(request); + } + public T update(T entity) { Assert.notNull(entity, "entity is required"); return prepareTable(entity).updateItem(entity); @@ -72,6 +80,7 @@ public T update(T entity) { @Override public T update(UpdateItemEnhancedRequest request) { Assert.notNull(request, "updateItemEnhancedRequest is required"); + Assert.notNull(request.item(), "request item is required"); return prepareTable(request.item()).updateItem(request); } @@ -86,6 +95,12 @@ public T delete(T entity) { return prepareTable(entity).deleteItem(entity); } + public T delete(DeleteItemEnhancedRequest request, Class clazz) { + Assert.notNull(request, "deleteItemEnhancedRequest is required"); + Assert.notNull(clazz, "clazz is required"); + return prepareTable(clazz).deleteItem(request); + } + @Nullable public T load(Key key, Class clazz) { Assert.notNull(key, "key is required"); diff --git a/spring-cloud-aws-dynamodb/src/test/java/io/awspring/cloud/dynamodb/DynamoDbTemplateIntegrationTest.java b/spring-cloud-aws-dynamodb/src/test/java/io/awspring/cloud/dynamodb/DynamoDbTemplateIntegrationTest.java index 599df971e..da2ab762f 100644 --- a/spring-cloud-aws-dynamodb/src/test/java/io/awspring/cloud/dynamodb/DynamoDbTemplateIntegrationTest.java +++ b/spring-cloud-aws-dynamodb/src/test/java/io/awspring/cloud/dynamodb/DynamoDbTemplateIntegrationTest.java @@ -16,6 +16,7 @@ package io.awspring.cloud.dynamodb; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertThrows; import java.util.ArrayList; import java.util.List; @@ -29,10 +30,13 @@ import org.springframework.util.StringUtils; import software.amazon.awssdk.enhanced.dynamodb.*; import software.amazon.awssdk.enhanced.dynamodb.model.*; +import software.amazon.awssdk.enhanced.dynamodb.model.DeleteItemEnhancedRequest; import software.amazon.awssdk.enhanced.dynamodb.model.PageIterable; +import software.amazon.awssdk.enhanced.dynamodb.model.PutItemEnhancedRequest; import software.amazon.awssdk.enhanced.dynamodb.model.QueryConditional; import software.amazon.awssdk.enhanced.dynamodb.model.QueryEnhancedRequest; import software.amazon.awssdk.enhanced.dynamodb.model.ScanEnhancedRequest; +import software.amazon.awssdk.enhanced.dynamodb.model.UpdateItemEnhancedRequest; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.awssdk.services.dynamodb.model.*; @@ -322,6 +326,91 @@ void dynamoDbTemplate_saveAndScanForParticularIndex_entitySuccessful(DynamoDbTab cleanUp(dynamoDbTable, personEntity2.getUuid()); } + @ParameterizedTest + @MethodSource("argumentSource") + void dynamoDbTemplate_saveConditionallyAndRead_entitySuccessfully(DynamoDbTable dynamoDbTable, + DynamoDbTemplate dynamoDbTemplate) { + UUID uuid = UUID.randomUUID(); + PersonEntity personEntity = new PersonEntity(uuid, "foo", null); + PersonEntity secondPersonEntity = new PersonEntity(uuid, "foo", "jar"); + PutItemEnhancedRequest putItemEnhancedRequest = PutItemEnhancedRequest.builder(PersonEntity.class) + .conditionExpression(Expression.builder().expression("attribute_not_exists(lastName)").build()) + .item(secondPersonEntity).build(); + // save a person with lastName "bar" + dynamoDbTemplate.save(personEntity); + // attempt to replace person with lastName "jar" + dynamoDbTemplate.save(putItemEnhancedRequest); + PersonEntity savedPersonEntity = dynamoDbTemplate.load( + Key.builder().partitionValue(secondPersonEntity.getUuid().toString()).build(), PersonEntity.class); + assertThat(savedPersonEntity).isEqualTo(secondPersonEntity); + + cleanUp(dynamoDbTable, personEntity.getUuid()); + } + + @ParameterizedTest + @MethodSource("argumentSource") + void dynamoDbTemplate_saveConditionally_entityFails(DynamoDbTable dynamoDbTable, + DynamoDbTemplate dynamoDbTemplate) { + UUID uuid = UUID.randomUUID(); + PersonEntity personEntity = new PersonEntity(uuid, "foo", "bar"); + PersonEntity secondPersonEntity = new PersonEntity(uuid, "foo", "jar"); + PutItemEnhancedRequest putItemEnhancedRequest = PutItemEnhancedRequest.builder(PersonEntity.class) + .conditionExpression(Expression.builder().expression("attribute_not_exists(lastName)").build()) + .item(secondPersonEntity).build(); + // save person with lastName "bar" + dynamoDbTemplate.save(personEntity); + // try to save new lastName "jar" for the same person + assertThrows(DynamoDbException.class, () -> { + dynamoDbTemplate.save(putItemEnhancedRequest); + }); + + cleanUp(dynamoDbTable, personEntity.getUuid()); + + } + + @ParameterizedTest + @MethodSource("argumentSource") + void dynamoDbTemplate_deleteConditionally_entitySuccessfully(DynamoDbTable dynamoDbTable, + DynamoDbTemplate dynamoDbTemplate) { + UUID uuid = UUID.randomUUID(); + PersonEntity personEntity = new PersonEntity(uuid, "notfoo", "bar"); + DeleteItemEnhancedRequest deleteItemEnhancedRequest = DeleteItemEnhancedRequest.builder() + .conditionExpression(Expression.builder().expression("#nameNotBeDeleted <> :value") + .putExpressionName("#nameNotBeDeleted", "name") + .putExpressionValue(":value", AttributeValue.builder().s("foo").build()).build()) + .key(Key.builder().partitionValue(personEntity.getUuid().toString()).build()).build(); + dynamoDbTemplate.save(personEntity); + dynamoDbTemplate.delete(deleteItemEnhancedRequest, PersonEntity.class); + + PersonEntity deletedEntity = dynamoDbTemplate + .load(Key.builder().partitionValue(personEntity.getUuid().toString()).build(), PersonEntity.class); + + assertThat(deletedEntity).isNull(); + } + + @ParameterizedTest + @MethodSource("argumentSource") + void dynamoDbTemplate_deleteConditionally_entityFails(DynamoDbTable dynamoDbTable, + DynamoDbTemplate dynamoDbTemplate) { + UUID uuid = UUID.randomUUID(); + PersonEntity personEntity = new PersonEntity(uuid, "foo", "bar"); + DeleteItemEnhancedRequest deleteItemEnhancedRequest = DeleteItemEnhancedRequest.builder() + .conditionExpression(Expression.builder().expression("#nameNotBeDeleted <> :value") + .putExpressionName("#nameNotBeDeleted", "name") + .putExpressionValue(":value", AttributeValue.builder().s("foo").build()).build()) + .key(Key.builder().partitionValue(personEntity.getUuid().toString()).build()).build(); + dynamoDbTemplate.save(personEntity); + assertThrows(DynamoDbException.class, () -> { + dynamoDbTemplate.delete(deleteItemEnhancedRequest, PersonEntity.class); + }); + + PersonEntity deletedEntity = dynamoDbTemplate + .load(Key.builder().partitionValue(personEntity.getUuid().toString()).build(), PersonEntity.class); + + assertThat(deletedEntity).isNotNull(); + cleanUp(dynamoDbTable, personEntity.getUuid()); + } + public static void cleanUp(DynamoDbTable dynamoDbTable, UUID uuid) { dynamoDbTable.deleteItem(Key.builder().partitionValue(uuid.toString()).build()); }