Description
Describe the bug
The AutoGenerateUUIDExtension was incorrectly overriding existing UUID values for items that already contained non-empty values.
This caused some major issues while I working on a project by using DynamoDbTable.updateItem() method. This method didn't update the existing record instead it saves a new record with auto-generated values. When I debuged the code I found the issue is nit with updateItem() method but with the extension. When I tag partition key attribute with tag @DynamoDbAutoGeneratedUuid. I will never be able to update existing record in the Db using updateItem() method.
Regression Issue
- Select this option if this issue appears to be a regression.
Expected Behavior
When provided an an object of DynamoDbBean.
Expected: when you try to update an existing record on the DB. it updates the record as per the attributes provided by the user operation.
Current Behavior
Current: Instead of updating the existing record, the operation saves a new record onto the DB
Reproduction Steps
DynamoDB Bug Replication Steps: Auto-Generated UUID Overwriting Issue
Prerequisites
- Spring Boot project with:
- Lombok library for model annotations.
- AWS SDK v2.29.1 for DynamoDB.
- Local DynamoDB instance set up using NoSQL Workbench.
Steps to Reproduce the Issue
1. Create a DynamoDB Entity (Model)
Create a DynamoDB bean using Lombok annotations:
@DynamoDbBean
@NoArgsConstructor
@AllArgsConstructor
public class Customer {
private String customerId;
private String customerName;
private Instant createdDate;
@DynamoDbAttribute(value = "CustomerId")
@DynamoDbAutoGeneratedUuid
@DynamoDbPartitionKey
@DynamoDbUpdateBehavior(UpdateBehavior.WRITE_IF_NOT_EXISTS)
public String getCustomerId() {
return customerId;
}
public void setCustomerId(String customerId) {
this.customerId = customerId;
}
@DynamoDbAttribute(value = "CustomerName")
public String getCustomerName() {
return customerName;
}
public void setCustomerName(String customerName) {
this.customerName = customerName;
}
@DynamoDbAttribute(value = "CreatedDate")
@DynamoDbAutoGeneratedTimestampAttribute
@DynamoDbConvertedBy(CstTimeFormatConverter.class)
@DynamoDbUpdateBehavior(UpdateBehavior.WRITE_IF_NOT_EXISTS)
public Instant getCreatedDate() {
return createdDate;
}
public void setCreatedDate(Instant createdDate) {
this.createdDate = createdDate;
}
}
2. Configure DynamoDB Enhanced Client Bean
Create a DynamoDbEnhancedClient
bean in your Spring Boot configuration:
@Configuration
public class DynamoDbConfig {
@Bean
public DynamoDbTable<Customer> customerDynamoDbTable(DynamoDbEnhancedClient dynamoDbEnhancedClient){
return dynamoDbEnhancedClient.table("Customer", TableSchema.fromBean(Customer.class));
}
@Bean
public DynamoDbClient dynamoDbClient() {
return DynamoDbClient.builder()
.endpointOverride(java.net.URI.create("http://localhost:8000"))
.region(Region.US_EAST_1) // Replace with your desired region
.credentialsProvider(StaticCredentialsProvider.create(
AwsBasicCredentials.create("dummy", "vyh65"))) // Use dummy credentials for local
.build();
}
@Bean
public DynamoDbEnhancedClient dynamoDbEnhancedClient(DynamoDbClient dynamoDbClient) {
return DynamoDbEnhancedClient.builder()
.dynamoDbClient(dynamoDbClient)
.extensions(AutoGeneratedTimestampRecordExtension.create(), AutoGeneratedUuidExtension.create())
.build();
}
}
3. Create a DynamoDB Table Bean
Create a DynamoDbTable<MyEntity>
using the DynamoDbEnhancedClient
:
@Service
public class DynamoDbTableService {
@Autowired
private DynamoDbEnhancedClient dynamoDbEnhancedClient;
public DynamoDbTable<MyEntity> getDynamoDbTable() {
return dynamoDbEnhancedClient.table("MyEntityTable", TableSchema.fromBean(MyEntity.class));
}
}
4. Create a controller endpoints for table creation and record saving/updating
@RestController
public class ApiController {
@Autowired
DynamoDbTable<Customer> customerDynamoDbTable;
@PostMapping("createRecord")
public Customer createRecord(@RequestBody Customer customer){
customer = customerDynamoDbTable.updateItem(customer);
// Return the updated item
return customer;
}
@PostMapping("createTable")
public void createTable(){
customerDynamoDbTable.createTable();
}
6. Attempt creating table using the above API and then save a record by providing body
{
"customerName": "XXYYZZ"
}
After record creation you will receive a save records copy as response
{
"customerId": "623bc3df-579f-4b3d-841e-bed00cfeea0f",
"customerName": "XXYYZZ", //modify to send as body again
"createdDate": "2024-11-09T08:09:45.479307200Z"
}
using the modified response as body hit the /createRecord again it should updated the existing record. Instead it creates a new record.
6. Attempt to Update an Existing Record
- Create a new
MyEntity
object, save it usingupdateItem()
. - Modify the object's attributes (except for the partition key).
- Use
updateItem()
again to update the record in DynamoDB.
Expected Behavior
- The
updateItem()
method should update the existing record with new attribute values.
Actual Behavior
- Bug: The
@DynamoDbAutoGeneratedUuid
annotation overrides the existing partition key. - The
updateItem()
method creates a new record instead of updating the existing one.
Summary
The issue is caused by the @DynamoDbAutoGeneratedUuid
tag which regenerates the UUID during updateItem()
calls, leading to unintended new record creation instead of updating existing entries.
Possible Solution
No response
Additional Information/Context
No response
AWS Java SDK version used
2.29.1
JDK version used
21
Operating System and version
windows 11 23H2