AutoGenerateUUIDExtension Overriding Existing Attribute Values
- Dominant language
- Java
- Stars
- 2.6k
- Forks
- 1k
- Avg merge
- 2d 9h
- Merged PRs (30d)
- 51
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:
```java
@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:
```java
@Configuration
public class DynamoDbConfig {
@Bean
public DynamoDbTable 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` using the `DynamoDbEnhancedClient`:
```java
@Service
public class DynamoDbTableService {
@Autowired
private DynamoDbEnhancedClient dynamoDbEnhancedClient;
public DynamoDbTable getDynamoDbTable() {
return dynamoDbEnhancedClient.table("MyEntityTable", TableSchema.fromBean(MyEntity.class));
}
}
```
### 4. Create a controller endpoints for table creation and record saving/updating
```java
@RestController
public class ApiController {
@Autowired
DynamoDbTable 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
```json
{
"customerName": "XXYYZZ"
}
```
After record creation you will receive a save records copy as response
```json
{
"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
1. **Create** a new `MyEntity` object, save it using `updateItem()`.
2. **Modify** the object's attributes (except for the partition key).
3. 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
Contributor guide
Assessment
This issue has not been assessed yet.