diff --git a/.changes/next-release/feature-AmazonDynamoDBEnhancedClient-d5e2088.json b/.changes/next-release/feature-AmazonDynamoDBEnhancedClient-d5e2088.json
new file mode 100644
index 000000000000..2ba328c0ebe9
--- /dev/null
+++ b/.changes/next-release/feature-AmazonDynamoDBEnhancedClient-d5e2088.json
@@ -0,0 +1,6 @@
+{
+ "type": "bugfix",
+ "category": "Amazon DynamoDB Enhanced Client",
+ "contributor": "",
+ "description": "Fix handling of UpdateBehavior.WRITE_IF_NOT_EXISTS on @DynamoDbAutoGeneratedUuid"
+}
diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtension.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtension.java
index d92db8c60bbd..ccf4a222dfc3 100644
--- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtension.java
+++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtension.java
@@ -29,51 +29,53 @@
import software.amazon.awssdk.enhanced.dynamodb.EnhancedType;
import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTag;
import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableMetadata;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbUpdateBehavior;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
+import software.amazon.awssdk.utils.StringUtils;
import software.amazon.awssdk.utils.Validate;
/**
- * This extension facilitates the automatic generation of a unique UUID (Universally Unique Identifier) for a specified attribute
- * every time a new record is written to the database. The generated UUID is obtained using the
- * {@link java.util.UUID#randomUUID()} method.
- *
- * This extension is not loaded by default when you instantiate a
- * {@link software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient}. Therefore, you need to specify it in a custom
- * extension when creating the enhanced client.
- *
- * Example to add AutoGeneratedUuidExtension along with default extensions is
- * {@snippet :
- * DynamoDbEnhancedClient.builder().extensions(Stream.concat(ExtensionResolver.defaultExtensions().stream(),
- * Stream.of(AutoGeneratedUuidExtension.create())).collect(Collectors.toList())).build();
- *}
- *
- *
- * Example to just add AutoGeneratedUuidExtension without default extensions is
- * {@snippet :
- * DynamoDbEnhancedClient.builder().extensions(AutoGeneratedUuidExtension.create()).build();
- *}
- *
- *
- * To utilize the auto-generated UUID feature, first, create a field in your model that will store the UUID for the attribute.
- * This class field must be of type {@link java.lang.String}, and you need to tag it as the autoGeneratedUuidAttribute. If you are
- * using the {@link software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema}, then you should use the
- * {@link software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedUuid} annotation. If you are using
- * the {@link software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableSchema}, then you should use the
- * {@link
- * software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedUuidExtension.AttributeTags#autoGeneratedUuidAttribute()}
- * static attribute tag.
- *
- *
- * Every time a new record is successfully put into the database, the specified attribute will be automatically populated with a
- * unique UUID generated using {@link java.util.UUID#randomUUID()}. If the UUID needs to be created only for `putItem` and should
- * not be generated for an `updateItem`, then
- * {@link software.amazon.awssdk.enhanced.dynamodb.mapper.UpdateBehavior#WRITE_IF_NOT_EXISTS} must be along with
- * {@link DynamoDbUpdateBehavior}
+ * This extension facilitates the automatic generation of a unique UUID (Universally Unique Identifier) for attributes tagged with
+ * {@code @DynamoDbAutoGeneratedUuid} or {@link AutoGeneratedUuidExtension.AttributeTags#autoGeneratedUuidAttribute()}. The
+ * generated UUID is obtained using {@link java.util.UUID#randomUUID()}.
*
- *
+ * Usage:
+ *
+ * - This extension is not loaded by default; register it explicitly when building
+ * a {@link software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient}.
+ * - The annotated attribute must be of type {@link String}.
+ * - If the attribute is missing or empty in the item being written, a UUID
+ * will be generated and set in the outgoing request map.
+ * - If the attribute already has a non-empty value, it is preserved and not overwritten.
+ *
+ *
+ * Update behavior:
+ *
+ * - With the default {@link software.amazon.awssdk.enhanced.dynamodb.mapper.UpdateBehavior#WRITE_ALWAYS},
+ * a missing attribute on an update will cause a new UUID to be generated and written.
+ * - With {@link software.amazon.awssdk.enhanced.dynamodb.mapper.UpdateBehavior#WRITE_IF_NOT_EXISTS},
+ * a missing attribute on an update will cause a UUID to be generated in the outgoing map,
+ * but DynamoDB will preserve the existing stored value thanks to the conditional
+ *
if_not_exists(...)
expression the mapper generates.
+ *
+ *
+ * Difference between putItem and updateItem:
+ *
+ * - {@code putItem} always replaces the entire item. If the field is absent in the payload, the extension
+ * will generate a new UUID, even when {@code WRITE_IF_NOT_EXISTS} is specified.
+ * - {@code updateItem} respects {@code WRITE_IF_NOT_EXISTS}: if the attribute already exists in DynamoDB,
+ * the previously stored value is preserved and a new UUID will not overwrite it.
+ *
+ *
+ * Examples:
+ * {@code
+ * DynamoDbEnhancedClient client = DynamoDbEnhancedClient.builder()
+ * .dynamoDbClient(lowLevelClient)
+ * .extensions(AutoGeneratedUuidExtension.create())
+ * .build();
+ * }
*/
+
@SdkPublicApi
@ThreadSafe
public final class AutoGeneratedUuidExtension implements DynamoDbEnhancedClientExtension {
@@ -110,15 +112,18 @@ public WriteModification beforeWrite(DynamoDbExtensionContext.BeforeWrite contex
}
Map itemToTransform = new HashMap<>(context.items());
- customMetadataObject.forEach(key -> insertUuidInItemToTransform(itemToTransform, key));
+ customMetadataObject.forEach(key -> insertUuidIfMissing(itemToTransform, key));
return WriteModification.builder()
.transformedItem(Collections.unmodifiableMap(itemToTransform))
.build();
}
- private void insertUuidInItemToTransform(Map itemToTransform,
- String key) {
- itemToTransform.put(key, AttributeValue.builder().s(UUID.randomUUID().toString()).build());
+ private static void insertUuidIfMissing(Map item, String key) {
+ AttributeValue existing = item.get(key);
+ boolean missing = existing == null || StringUtils.isEmpty(existing.s());
+ if (missing) {
+ item.put(key, AttributeValue.builder().s(UUID.randomUUID().toString()).build());
+ }
}
public static final class AttributeTags {
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtensionTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtensionTest.java
index cc69f503d50f..64e5f310d12f 100644
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtensionTest.java
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtensionTest.java
@@ -66,11 +66,11 @@ public class AutoGeneratedUuidExtensionTest {
.build();
@Test
- public void beforeWrite_updateItemOperation_hasUuidInItem_doesNotCreateUpdateExpressionAndFilters() {
+ public void beforeWrite_withExistingUuid_doesNotOverwriteValue() {
ItemWithUuid SimpleItem = new ItemWithUuid();
SimpleItem.setId(RECORD_ID);
- String uuidAttribute = String.valueOf(UUID.randomUUID());
- SimpleItem.setUuidAttribute(uuidAttribute);
+ String initialUuid = String.valueOf(UUID.randomUUID());
+ SimpleItem.setUuidAttribute(initialUuid);
Map items = ITEM_WITH_UUID_MAPPER.itemToMap(SimpleItem, true);
assertThat(items).hasSize(2);
@@ -85,13 +85,15 @@ public void beforeWrite_updateItemOperation_hasUuidInItem_doesNotCreateUpdateExp
Map transformedItem = result.transformedItem();
assertThat(transformedItem).isNotNull().hasSize(2);
assertThat(transformedItem).containsEntry("id", AttributeValue.fromS(RECORD_ID));
- isValidUuid(transformedItem.get("uuidAttribute").s());
assertThat(result.updateExpression()).isNull();
+ String uuidValue = result.transformedItem().get("uuidAttribute").s();
+ isValidUuid(uuidValue);
+ assertThat(uuidValue).isEqualTo(initialUuid);
}
@Test
- public void beforeWrite_updateItemOperation_hasNoUuidInItem_doesNotCreatesUpdateExpressionAndFilters() {
+ public void beforeWrite_withMissingUuid_generatesNewValue() {
ItemWithUuid SimpleItem = new ItemWithUuid();
SimpleItem.setId(RECORD_ID);
@@ -108,8 +110,31 @@ public void beforeWrite_updateItemOperation_hasNoUuidInItem_doesNotCreatesUpdate
Map transformedItem = result.transformedItem();
assertThat(transformedItem).isNotNull().hasSize(2);
assertThat(transformedItem).containsEntry("id", AttributeValue.fromS(RECORD_ID));
- isValidUuid(transformedItem.get("uuidAttribute").s());
assertThat(result.updateExpression()).isNull();
+
+ isValidUuid(result.transformedItem().get("uuidAttribute").s());
+ assertThat(items).doesNotContainKey("uuidAttribute");
+ }
+
+ @Test
+ public void beforeWrite_withEmptyUuid_generatesNewValue() {
+ ItemWithUuid item = new ItemWithUuid();
+ item.setId(RECORD_ID);
+ item.setUuidAttribute(""); // empty should be treated as missing
+
+ Map items = ITEM_WITH_UUID_MAPPER.itemToMap(item, true);
+
+ WriteModification result = atomicCounterExtension.beforeWrite(
+ DefaultDynamoDbExtensionContext.builder()
+ .items(items)
+ .tableMetadata(ITEM_WITH_UUID_MAPPER.tableMetadata())
+ .operationName(OperationName.UPDATE_ITEM)
+ .operationContext(PRIMARY_CONTEXT)
+ .build());
+
+ assertThat(result.updateExpression()).isNull();
+ String uuidValue = result.transformedItem().get("uuidAttribute").s();
+ isValidUuid(uuidValue);
}
@Test
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedUuidRecordTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedUuidRecordTest.java
index e59ea214399b..2cf5c6fe9bba 100644
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedUuidRecordTest.java
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedUuidRecordTest.java
@@ -23,7 +23,6 @@
import java.util.Arrays;
import java.util.Collection;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -55,13 +54,10 @@
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbUpdateBehavior;
import software.amazon.awssdk.enhanced.dynamodb.model.PutItemEnhancedRequest;
-import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
+import software.amazon.awssdk.enhanced.dynamodb.model.TransactWriteItemsEnhancedRequest;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.ConditionalCheckFailedException;
import software.amazon.awssdk.services.dynamodb.model.DeleteTableRequest;
-import software.amazon.awssdk.services.dynamodb.model.GetItemRequest;
-import software.amazon.awssdk.services.dynamodb.model.GetItemResponse;
-import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput;
@RunWith(Parameterized.class)
public class AutoGeneratedUuidRecordTest extends LocalDynamoDbSyncTestBase{
@@ -314,6 +310,216 @@ public void updateItemConditionTestFailure() {
}
+ /**
+ | SCENARIO 1 (PUT → PUT), Case A: No UUIDs provided
+ | After first PUT: item is created; both UUIDs generated by extension
+ | After second PUT: item is updated; both UUIDs re-generated
+ */
+ @Test
+ public void putItemTwice_withoutUuids_bothUuidsRegeneratedOnSecondPut() {
+ mappedTable.putItem(new Record().id("id1").attribute("p1"));
+ Record afterFirstPut = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id1")));
+ assertRecordHasValidUuid(afterFirstPut);
+
+ mappedTable.putItem(new Record().id("id1").attribute("p2"));
+ Record afterSecondPut = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id1")));
+ Assertions.assertThat(afterSecondPut.getCreatedUuid()).isNotEqualTo(afterFirstPut.getCreatedUuid());
+ Assertions.assertThat(afterSecondPut.getLastUpdatedUuid()).isNotEqualTo(afterFirstPut.getLastUpdatedUuid());
+ assertRecordHasValidUuid(afterSecondPut);
+ }
+
+ /**
+ | SCENARIO 1 (PUT → PUT), Case B: Manual UUIDs provided on requests
+ | After first PUT: item is created; UUID fields are taken from the request
+ | After second PUT: item is updated; UUID fields are taken from the new request
+ */
+ @Test
+ public void putItemTwice_withManualUuidsOnRequest_requestValuesAppliedEachTime() {
+ Record first = new Record().id("id1").attribute("p1");
+ String manualCreatedFirst = "UUID-" + UUID.randomUUID();
+ String manualUpdatedFirst = "UUID-" + UUID.randomUUID();
+ first.setCreatedUuid(manualCreatedFirst);
+ first.setLastUpdatedUuid(manualUpdatedFirst);
+
+ mappedTable.putItem(first);
+ Record afterFirstPut = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id1")));
+ Assertions.assertThat(afterFirstPut.getCreatedUuid()).isEqualTo(manualCreatedFirst);
+ Assertions.assertThat(afterFirstPut.getLastUpdatedUuid()).isEqualTo(manualUpdatedFirst);
+
+ Record second = new Record().id("id1").attribute("p2");
+ second.setCreatedUuid("UUID2-" + UUID.randomUUID());
+ second.setLastUpdatedUuid("UUID2-" + UUID.randomUUID());
+
+ mappedTable.putItem(second);
+ Record afterSecondPut = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id1")));
+ Assertions.assertThat(afterSecondPut.getCreatedUuid()).isNotEqualTo(manualCreatedFirst);
+ Assertions.assertThat(afterSecondPut.getLastUpdatedUuid()).isNotEqualTo(manualUpdatedFirst);
+ }
+
+ /**
+ | SCENARIO 2 (UPDATE → UPDATE), Case A: No UUIDs provided
+ | After first UPDATE: item is created; both UUIDs generated
+ | After second UPDATE: createdUuid preserved; lastUpdatedUuid regenerated
+ */
+ @Test
+ public void updateItemTwice_withoutUuidsOnRequest_thenCreatedUuidPreservedAndLastUpdatedUuidChanged() {
+ mappedTable.updateItem(new Record().id("id1").attribute("p1"));
+ Record afterFirstUpdate = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id1")));
+ assertRecordHasValidUuid(afterFirstUpdate);
+
+ mappedTable.updateItem(new Record().id("id1").attribute("secondItemToBeUpdated"));
+ Record afterSecondUpdate = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id1")));
+ Assertions.assertThat(afterSecondUpdate.getCreatedUuid()).isEqualTo(afterFirstUpdate.getCreatedUuid());
+ Assertions.assertThat(afterSecondUpdate.getLastUpdatedUuid()).isNotEqualTo(afterFirstUpdate.getLastUpdatedUuid());
+ assertRecordHasValidUuid(afterSecondUpdate);
+ }
+
+ /**
+ | SCENARIO 2 (UPDATE → UPDATE), Case B: Manual UUIDs on first request
+ | After first UPDATE: item is created; UUIDs taken from request
+ | After second UPDATE: createdUuid preserved; lastUpdatedUuid regenerated
+ */
+ @Test
+ public void updateItemTwice_withManualUuidsFirst_thenCreatedPreserved_LastUpdatedChanges() {
+ Record firstItemToBeUpdated = new Record().id("id1").attribute("u1");
+ String manualCreated = "createdUUID-" + UUID.randomUUID();
+ String manualUpdated = "updatedUUID-" + UUID.randomUUID();
+ firstItemToBeUpdated.setCreatedUuid(manualCreated);
+ firstItemToBeUpdated.setLastUpdatedUuid(manualUpdated);
+
+ mappedTable.updateItem(firstItemToBeUpdated);
+ Record afterFirstUpdate = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id1")));
+ Assertions.assertThat(afterFirstUpdate.getCreatedUuid()).isEqualTo(manualCreated);
+ Assertions.assertThat(afterFirstUpdate.getLastUpdatedUuid()).isEqualTo(manualUpdated);
+
+ Record secondItemToBeUpdated = new Record().id("id1").attribute("u2");
+ secondItemToBeUpdated.setCreatedUuid("UUID-" + UUID.randomUUID());
+ mappedTable.updateItem(secondItemToBeUpdated);
+
+ Record afterSecondUpdate = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id1")));
+ Assertions.assertThat(afterSecondUpdate.getCreatedUuid()).isEqualTo(afterFirstUpdate.getCreatedUuid());
+ Assertions.assertThat(afterSecondUpdate.getLastUpdatedUuid()).isNotEqualTo(afterFirstUpdate.getLastUpdatedUuid());
+ }
+
+ /**
+ | SCENARIO 3: transactWriteItems.addPutItem → putItem, Case A: No UUIDs provided
+ | After first PUT (txn): item created; both UUIDs generated
+ | After second PUT: item updated; both UUIDs re-generated
+ */
+ @Test
+ public void transactPutThenPut_withoutUuids_bothUuidsRegeneratedEachTime() {
+ DynamoDbEnhancedClient client = DynamoDbEnhancedClient.builder()
+ .dynamoDbClient(getDynamoDbClient())
+ .extensions(AutoGeneratedUuidExtension.create())
+ .build();
+ DynamoDbTable table = client.table(getConcreteTableName("table-name"), TABLE_SCHEMA);
+
+ client.transactWriteItems(TransactWriteItemsEnhancedRequest.builder()
+ .addPutItem(table, new Record().id("id1").attribute("p1"))
+ .build());
+ Record afterFirstUpdateRecord = table.getItem(r -> r.key(k -> k.partitionValue("id1")));
+ String createdUuidAfterFirstUpdate = afterFirstUpdateRecord.getCreatedUuid();
+ String lastUpdatedUuidAfterFirstUpdate = afterFirstUpdateRecord.getLastUpdatedUuid();
+ assertRecordHasValidUuid(afterFirstUpdateRecord);
+
+ table.putItem(new Record().id("id1").attribute("p2"));
+ Record afterSecondUpdateRecord = table.getItem(r -> r.key(k -> k.partitionValue("id1")));
+ Assertions.assertThat(afterSecondUpdateRecord.getCreatedUuid()).isNotEqualTo(createdUuidAfterFirstUpdate);
+ Assertions.assertThat(afterSecondUpdateRecord.getLastUpdatedUuid()).isNotEqualTo(lastUpdatedUuidAfterFirstUpdate);
+ assertRecordHasValidUuid(afterSecondUpdateRecord);
+ }
+
+ /**
+ | SCENARIO 3: transactWriteItems.addPutItem → putItem
+ | Case B: Manual UUIDs on first PUT (applied), no UUIDs provided on next PUT (regenerated)
+ */
+ @Test
+ public void transactPutWithManualCreatedUuid_thenPutWithoutUuid_thenBothUuidsRegeneratedOnSecondPut() {
+ DynamoDbEnhancedClient client = DynamoDbEnhancedClient.builder()
+ .dynamoDbClient(getDynamoDbClient())
+ .extensions(AutoGeneratedUuidExtension.create())
+ .build();
+ DynamoDbTable table = client.table(getConcreteTableName("table-name"), TABLE_SCHEMA);
+
+ String manualCreated = "createdUUID-" + UUID.randomUUID();
+ String manualUpdated = "updatedUUID-" + UUID.randomUUID();
+ Record firstPut = new Record().id("id1").attribute("p1");
+ firstPut.setCreatedUuid(manualCreated);
+ firstPut.setLastUpdatedUuid(manualUpdated);
+ client.transactWriteItems(TransactWriteItemsEnhancedRequest.builder()
+ .addPutItem(table, firstPut)
+ .build());
+ Record afterFirstUpdateRecord = table.getItem(r -> r.key(k -> k.partitionValue("id1")));
+ Assertions.assertThat(afterFirstUpdateRecord.getCreatedUuid()).isEqualTo(manualCreated);
+ Assertions.assertThat(afterFirstUpdateRecord.getLastUpdatedUuid()).isEqualTo(manualUpdated);
+
+ table.putItem(new Record().id("id1").attribute("p2"));
+ Record afterSecondUpdateRecord = table.getItem(r -> r.key(k -> k.partitionValue("id1")));
+ Assertions.assertThat(afterSecondUpdateRecord.getCreatedUuid()).isNotEqualTo(manualCreated);
+ Assertions.assertThat(afterSecondUpdateRecord.getLastUpdatedUuid()).isNotEqualTo(afterFirstUpdateRecord.getLastUpdatedUuid());
+ assertRecordHasValidUuid(afterSecondUpdateRecord);
+ }
+
+ /**
+ | SCENARIO 4: transactWriteItems.addUpdateItem → updateItem, Case A: No UUIDs provided
+ | After first UPDATE: item created; both UUIDs generated
+ | After second UPDATE: createdUuid preserved; lastUpdatedUuid regenerated
+ */
+ @Test
+ public void transactUpdateThenUpdate_withoutUuidsOnRequest_thenCreatedUuidPreserved_andLastUpdatedUuidRegenerated() {
+ DynamoDbEnhancedClient client = DynamoDbEnhancedClient.builder()
+ .dynamoDbClient(getDynamoDbClient())
+ .extensions(AutoGeneratedUuidExtension.create())
+ .build();
+ DynamoDbTable table = client.table(getConcreteTableName("table-name"), TABLE_SCHEMA);
+
+ client.transactWriteItems(TransactWriteItemsEnhancedRequest.builder()
+ .addUpdateItem(table, new Record().id("id1").attribute("u1"))
+ .build());
+ Record afterTransactionUpdateRecord = table.getItem(r -> r.key(k -> k.partitionValue("id1")));
+ assertRecordHasValidUuid(afterTransactionUpdateRecord);
+
+ table.updateItem(new Record().id("id1").attribute("u2"));
+ Record afterUpdateRecord = table.getItem(r -> r.key(k -> k.partitionValue("id1")));
+ Assertions.assertThat(afterUpdateRecord.getCreatedUuid()).isEqualTo(afterTransactionUpdateRecord.getCreatedUuid());
+ Assertions.assertThat(afterUpdateRecord.getLastUpdatedUuid()).isNotEqualTo(afterTransactionUpdateRecord.getLastUpdatedUuid());
+ assertRecordHasValidUuid(afterUpdateRecord);
+ }
+
+ /**
+ | SCENARIO 4: transactWriteItems.addUpdateItem → updateItem
+ | Case B: Manual UUIDs on first update (applied);
+ | on second update, createdUuid preserved; lastUpdatedUuid regenerated
+ */
+ @Test
+ public void transactUpdateThenUpdate_withManualCreatedUuid_thenCreatedPreserved_LastUpdatedChanges() {
+ DynamoDbEnhancedClient client = DynamoDbEnhancedClient.builder()
+ .dynamoDbClient(getDynamoDbClient())
+ .extensions(AutoGeneratedUuidExtension.create())
+ .build();
+ DynamoDbTable table = client.table(getConcreteTableName("table-name"), TABLE_SCHEMA);
+
+ Record firstItemToBeUpdated = new Record().id("id1").attribute("u1");
+ String manualCreated = "createdUUID-" + UUID.randomUUID();
+ String manualUpdated = "updatedUUID-" + UUID.randomUUID();
+ firstItemToBeUpdated.setCreatedUuid(manualCreated);
+ firstItemToBeUpdated.setLastUpdatedUuid(manualUpdated);
+ client.transactWriteItems(TransactWriteItemsEnhancedRequest.builder()
+ .addUpdateItem(table, firstItemToBeUpdated)
+ .build());
+ Record afterFirstUpdateRecord = table.getItem(r -> r.key(k -> k.partitionValue("id1")));
+ Assertions.assertThat(afterFirstUpdateRecord.getCreatedUuid()).isEqualTo(manualCreated);
+ Assertions.assertThat(afterFirstUpdateRecord.getLastUpdatedUuid()).isEqualTo(manualUpdated);
+
+ Record secondItemToBeUpdated = new Record().id("id1").attribute("u2");
+ secondItemToBeUpdated.setCreatedUuid("idUID-" + UUID.randomUUID());
+ table.updateItem(secondItemToBeUpdated);
+
+ Record afterSecondUpdateRecord = table.getItem(r -> r.key(k -> k.partitionValue("id1")));
+ Assertions.assertThat(afterSecondUpdateRecord.getCreatedUuid()).isEqualTo(afterFirstUpdateRecord.getCreatedUuid());
+ Assertions.assertThat(afterSecondUpdateRecord.getLastUpdatedUuid()).isNotEqualTo(afterFirstUpdateRecord.getLastUpdatedUuid());
+ }
+
public static Record createUniqueFakeItem() {
Record record = new Record();
record.setId(UUID.randomUUID().toString());