Skip to content

Commit df8e793

Browse files
Add conditional save, update and delete operation support for DynamoDB entities. Fixes #gh-1147 (#1371)
* Add save dynamodb entity with PutItemEnhancedRequest * Add update dynamodb entity with UpdateEnhancedItemRequest * Add delete dynamodb entity conditionally with DeleteItemEnhancedRequest * Remove class parameter from methods that can obtain class by calling request#item() Fixes #1147 Co-authored-by: Maciej Walkowiak <[email protected]>
1 parent 31b708b commit df8e793

File tree

3 files changed

+126
-0
lines changed

3 files changed

+126
-0
lines changed

spring-cloud-aws-dynamodb/src/main/java/io/awspring/cloud/dynamodb/DynamoDbOperations.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717

1818
import org.springframework.lang.Nullable;
1919
import software.amazon.awssdk.enhanced.dynamodb.Key;
20+
import software.amazon.awssdk.enhanced.dynamodb.model.DeleteItemEnhancedRequest;
2021
import software.amazon.awssdk.enhanced.dynamodb.model.PageIterable;
22+
import software.amazon.awssdk.enhanced.dynamodb.model.PutItemEnhancedRequest;
2123
import software.amazon.awssdk.enhanced.dynamodb.model.QueryEnhancedRequest;
2224
import software.amazon.awssdk.enhanced.dynamodb.model.ScanEnhancedRequest;
2325
import software.amazon.awssdk.enhanced.dynamodb.model.UpdateItemEnhancedRequest;
@@ -39,6 +41,15 @@ public interface DynamoDbOperations {
3941
*/
4042
<T> T save(T entity);
4143

44+
/**
45+
* Saves an item in DynamoDB using the provided PutItemEnhancedRequest.
46+
*
47+
* @param request the request object containing the item to be saved
48+
*
49+
* @see PutItemEnhancedRequest
50+
*/
51+
<T> void save(PutItemEnhancedRequest<T> request);
52+
4253
/**
4354
* Updates Entity to DynamoDB table.
4455
*
@@ -71,6 +82,17 @@ public interface DynamoDbOperations {
7182
*/
7283
<T> T delete(T entity);
7384

85+
/**
86+
* Deletes a record for a given DeleteItemEnhancedRequest.
87+
*
88+
* @param request the request object containing the item to be deleted
89+
* @param clazz the class type of the item to be deleted so
90+
* {@link software.amazon.awssdk.enhanced.dynamodb.TableSchema} can be generated.
91+
*
92+
* @see DeleteItemEnhancedRequest
93+
*/
94+
<T> T delete(DeleteItemEnhancedRequest request, Class<T> clazz);
95+
7496
/**
7597
* Loads entity for a given Key.
7698
*

spring-cloud-aws-dynamodb/src/main/java/io/awspring/cloud/dynamodb/DynamoDbTemplate.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
2222
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable;
2323
import software.amazon.awssdk.enhanced.dynamodb.Key;
24+
import software.amazon.awssdk.enhanced.dynamodb.model.DeleteItemEnhancedRequest;
2425
import software.amazon.awssdk.enhanced.dynamodb.model.PageIterable;
26+
import software.amazon.awssdk.enhanced.dynamodb.model.PutItemEnhancedRequest;
2527
import software.amazon.awssdk.enhanced.dynamodb.model.QueryEnhancedRequest;
2628
import software.amazon.awssdk.enhanced.dynamodb.model.ScanEnhancedRequest;
2729
import software.amazon.awssdk.enhanced.dynamodb.model.UpdateItemEnhancedRequest;
@@ -64,6 +66,12 @@ public <T> T save(T entity) {
6466
return entity;
6567
}
6668

69+
public <T> void save(PutItemEnhancedRequest<T> request) {
70+
Assert.notNull(request, "putItemEnhancedRequest is required");
71+
Assert.notNull(request.item(), "request item is required");
72+
prepareTable(request.item()).putItem(request);
73+
}
74+
6775
public <T> T update(T entity) {
6876
Assert.notNull(entity, "entity is required");
6977
return prepareTable(entity).updateItem(entity);
@@ -72,6 +80,7 @@ public <T> T update(T entity) {
7280
@Override
7381
public <T> T update(UpdateItemEnhancedRequest<T> request) {
7482
Assert.notNull(request, "updateItemEnhancedRequest is required");
83+
Assert.notNull(request.item(), "request item is required");
7584
return prepareTable(request.item()).updateItem(request);
7685
}
7786

@@ -86,6 +95,12 @@ public <T> T delete(T entity) {
8695
return prepareTable(entity).deleteItem(entity);
8796
}
8897

98+
public <T> T delete(DeleteItemEnhancedRequest request, Class<T> clazz) {
99+
Assert.notNull(request, "deleteItemEnhancedRequest is required");
100+
Assert.notNull(clazz, "clazz is required");
101+
return prepareTable(clazz).deleteItem(request);
102+
}
103+
89104
@Nullable
90105
public <T> T load(Key key, Class<T> clazz) {
91106
Assert.notNull(key, "key is required");

spring-cloud-aws-dynamodb/src/test/java/io/awspring/cloud/dynamodb/DynamoDbTemplateIntegrationTest.java

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package io.awspring.cloud.dynamodb;
1717

1818
import static org.assertj.core.api.Assertions.assertThat;
19+
import static org.junit.Assert.assertThrows;
1920

2021
import java.util.ArrayList;
2122
import java.util.List;
@@ -29,10 +30,13 @@
2930
import org.springframework.util.StringUtils;
3031
import software.amazon.awssdk.enhanced.dynamodb.*;
3132
import software.amazon.awssdk.enhanced.dynamodb.model.*;
33+
import software.amazon.awssdk.enhanced.dynamodb.model.DeleteItemEnhancedRequest;
3234
import software.amazon.awssdk.enhanced.dynamodb.model.PageIterable;
35+
import software.amazon.awssdk.enhanced.dynamodb.model.PutItemEnhancedRequest;
3336
import software.amazon.awssdk.enhanced.dynamodb.model.QueryConditional;
3437
import software.amazon.awssdk.enhanced.dynamodb.model.QueryEnhancedRequest;
3538
import software.amazon.awssdk.enhanced.dynamodb.model.ScanEnhancedRequest;
39+
import software.amazon.awssdk.enhanced.dynamodb.model.UpdateItemEnhancedRequest;
3640
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
3741
import software.amazon.awssdk.services.dynamodb.model.*;
3842

@@ -322,6 +326,91 @@ void dynamoDbTemplate_saveAndScanForParticularIndex_entitySuccessful(DynamoDbTab
322326
cleanUp(dynamoDbTable, personEntity2.getUuid());
323327
}
324328

329+
@ParameterizedTest
330+
@MethodSource("argumentSource")
331+
void dynamoDbTemplate_saveConditionallyAndRead_entitySuccessfully(DynamoDbTable<PersonEntity> dynamoDbTable,
332+
DynamoDbTemplate dynamoDbTemplate) {
333+
UUID uuid = UUID.randomUUID();
334+
PersonEntity personEntity = new PersonEntity(uuid, "foo", null);
335+
PersonEntity secondPersonEntity = new PersonEntity(uuid, "foo", "jar");
336+
PutItemEnhancedRequest<PersonEntity> putItemEnhancedRequest = PutItemEnhancedRequest.builder(PersonEntity.class)
337+
.conditionExpression(Expression.builder().expression("attribute_not_exists(lastName)").build())
338+
.item(secondPersonEntity).build();
339+
// save a person with lastName "bar"
340+
dynamoDbTemplate.save(personEntity);
341+
// attempt to replace person with lastName "jar"
342+
dynamoDbTemplate.save(putItemEnhancedRequest);
343+
PersonEntity savedPersonEntity = dynamoDbTemplate.load(
344+
Key.builder().partitionValue(secondPersonEntity.getUuid().toString()).build(), PersonEntity.class);
345+
assertThat(savedPersonEntity).isEqualTo(secondPersonEntity);
346+
347+
cleanUp(dynamoDbTable, personEntity.getUuid());
348+
}
349+
350+
@ParameterizedTest
351+
@MethodSource("argumentSource")
352+
void dynamoDbTemplate_saveConditionally_entityFails(DynamoDbTable<PersonEntity> dynamoDbTable,
353+
DynamoDbTemplate dynamoDbTemplate) {
354+
UUID uuid = UUID.randomUUID();
355+
PersonEntity personEntity = new PersonEntity(uuid, "foo", "bar");
356+
PersonEntity secondPersonEntity = new PersonEntity(uuid, "foo", "jar");
357+
PutItemEnhancedRequest<PersonEntity> putItemEnhancedRequest = PutItemEnhancedRequest.builder(PersonEntity.class)
358+
.conditionExpression(Expression.builder().expression("attribute_not_exists(lastName)").build())
359+
.item(secondPersonEntity).build();
360+
// save person with lastName "bar"
361+
dynamoDbTemplate.save(personEntity);
362+
// try to save new lastName "jar" for the same person
363+
assertThrows(DynamoDbException.class, () -> {
364+
dynamoDbTemplate.save(putItemEnhancedRequest);
365+
});
366+
367+
cleanUp(dynamoDbTable, personEntity.getUuid());
368+
369+
}
370+
371+
@ParameterizedTest
372+
@MethodSource("argumentSource")
373+
void dynamoDbTemplate_deleteConditionally_entitySuccessfully(DynamoDbTable<PersonEntity> dynamoDbTable,
374+
DynamoDbTemplate dynamoDbTemplate) {
375+
UUID uuid = UUID.randomUUID();
376+
PersonEntity personEntity = new PersonEntity(uuid, "notfoo", "bar");
377+
DeleteItemEnhancedRequest deleteItemEnhancedRequest = DeleteItemEnhancedRequest.builder()
378+
.conditionExpression(Expression.builder().expression("#nameNotBeDeleted <> :value")
379+
.putExpressionName("#nameNotBeDeleted", "name")
380+
.putExpressionValue(":value", AttributeValue.builder().s("foo").build()).build())
381+
.key(Key.builder().partitionValue(personEntity.getUuid().toString()).build()).build();
382+
dynamoDbTemplate.save(personEntity);
383+
dynamoDbTemplate.delete(deleteItemEnhancedRequest, PersonEntity.class);
384+
385+
PersonEntity deletedEntity = dynamoDbTemplate
386+
.load(Key.builder().partitionValue(personEntity.getUuid().toString()).build(), PersonEntity.class);
387+
388+
assertThat(deletedEntity).isNull();
389+
}
390+
391+
@ParameterizedTest
392+
@MethodSource("argumentSource")
393+
void dynamoDbTemplate_deleteConditionally_entityFails(DynamoDbTable<PersonEntity> dynamoDbTable,
394+
DynamoDbTemplate dynamoDbTemplate) {
395+
UUID uuid = UUID.randomUUID();
396+
PersonEntity personEntity = new PersonEntity(uuid, "foo", "bar");
397+
DeleteItemEnhancedRequest deleteItemEnhancedRequest = DeleteItemEnhancedRequest.builder()
398+
.conditionExpression(Expression.builder().expression("#nameNotBeDeleted <> :value")
399+
.putExpressionName("#nameNotBeDeleted", "name")
400+
.putExpressionValue(":value", AttributeValue.builder().s("foo").build()).build())
401+
.key(Key.builder().partitionValue(personEntity.getUuid().toString()).build()).build();
402+
dynamoDbTemplate.save(personEntity);
403+
assertThrows(DynamoDbException.class, () -> {
404+
dynamoDbTemplate.delete(deleteItemEnhancedRequest, PersonEntity.class);
405+
});
406+
407+
PersonEntity deletedEntity = dynamoDbTemplate
408+
.load(Key.builder().partitionValue(personEntity.getUuid().toString()).build(), PersonEntity.class);
409+
410+
assertThat(deletedEntity).isNotNull();
411+
cleanUp(dynamoDbTable, personEntity.getUuid());
412+
}
413+
325414
public static void cleanUp(DynamoDbTable<PersonEntity> dynamoDbTable, UUID uuid) {
326415
dynamoDbTable.deleteItem(Key.builder().partitionValue(uuid.toString()).build());
327416
}

0 commit comments

Comments
 (0)