aws-sdk-java
aws-sdk-java copied to clipboard
TransactionWriteRequest / DynamoDBMapper::transactionWrite ignores SaveBehavior.CLOBBER
- Expected behavior of the SDK
Version field should be ignored if SaveBehavior.CLOBBER is provided in the DynamoDBMapperConfig passed to DynamoDBMapper::transactionWrite.
- Actual behavior exhibited
Version field is not ignored.
- Details of my application environment
SDK version: 1.11.722 JRE version: Oracle 11.0.6
- The exception stacktrace
Exception in thread "main" com.amazonaws.services.dynamodbv2.model.TransactionCanceledException: Transaction cancelled, please refer cancellation reasons for specific reasons [ConditionalCheckFailed] (Service: AmazonDynamoDBv2; Status Code: 400; Error Code: TransactionCanceledException; Request ID: a1e23b6c-73c8-4a55-9e8c-c90d6582a113)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.handleErrorResponse(AmazonHttpClient.java:1799)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.handleServiceErrorResponse(AmazonHttpClient.java:1383)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeOneRequest(AmazonHttpClient.java:1359)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeHelper(AmazonHttpClient.java:1139)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.doExecute(AmazonHttpClient.java:796)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeWithTimer(AmazonHttpClient.java:764)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.execute(AmazonHttpClient.java:738)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.access$500(AmazonHttpClient.java:698)
at com.amazonaws.http.AmazonHttpClient$RequestExecutionBuilderImpl.execute(AmazonHttpClient.java:680)
at com.amazonaws.http.AmazonHttpClient.execute(AmazonHttpClient.java:544)
at com.amazonaws.http.AmazonHttpClient.execute(AmazonHttpClient.java:524)
at com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient.doInvoke(AmazonDynamoDBClient.java:5110)
at com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient.invoke(AmazonDynamoDBClient.java:5077)
at com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient.executeTransactWriteItems(AmazonDynamoDBClient.java:4201)
at com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient.transactWriteItems(AmazonDynamoDBClient.java:4167)
at com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper.transactionWrite(DynamoDBMapper.java:1122)
at com.example.reconpoc.document.Driver.selfContainedTestCase(Driver.java:73)
at com.example.reconpoc.document.Driver.main(Driver.java:31)
- Minimal Working Example
Class MyItem:
@DynamoDBTable(tableName = "IssueSampleTable")
public class MyItem {
@DynamoDBHashKey(attributeName = "id")
private String id;
@DynamoDBVersionAttribute
private Long version;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Long getVersion() {
return version;
}
public void setVersion(Long version) {
this.version = version;
}
}
Driver code (you will need to provide your own AmazonDynamoDB instance):
public static void selfContainedTestCase(AmazonDynamoDB ddb) {
DynamoDBMapper mapper = new DynamoDBMapper(ddb);
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// SETUP TABLE
// try to delete the table, if it exists
try {
ddb.deleteTable(mapper.generateDeleteTableRequest(MyItem.class));
} catch (ResourceNotFoundException e) {
// no problemo, table did not exist
}
// create new table
ProvisionedThroughput provisionedThroughput = new ProvisionedThroughput(5L, 5L);
CreateTableRequest request = mapper.generateCreateTableRequest(MyItem.class);
request.setProvisionedThroughput(provisionedThroughput);
ddb.createTable(request);
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// SAVE AN INSTANCE OF MYITEM
MyItem myItemToSave = new MyItem();
myItemToSave.setId("SOME ID");
mapper.save(myItemToSave);
System.out.println("Saved! Id = " + myItemToSave.getVersion());
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// ATTEMPT TO DELETE THE SAME INSTANCE IN A TRANSACTION, WITH CLOBBER
MyItem myItemToDelete = new MyItem();
myItemToDelete.setId("SOME ID");
TransactionWriteRequest transactionWriteRequest = new TransactionWriteRequest();
transactionWriteRequest.addDelete(myItemToDelete);
// this should not throw - but it does!
mapper.transactionWrite(transactionWriteRequest, DynamoDBMapperConfig.builder()
.withSaveBehavior(DynamoDBMapperConfig.SaveBehavior.CLOBBER)
.build());
}
Issue is present in .723 also.
Hi @joelc actually, transactionWrite does not support neither SaveBehavior nor versioning annotations, per documentation - javadoc
Hi @debora-ito,
I edited my previous response about 12 times. Doing a sneaky ninja delete and repost:
I think the documentation says one thing and the code does another.
If this were true:
Furthermore, put and update work as if SaveBehavior is set as CLOBBER.
Then using the MyItem
class above in conjunction with this method should not throw because of a version mismatch:
public static void selfContainedTestCase_TransactionWriteDoesNOTClobber(AmazonDynamoDB ddb) {
DynamoDBMapper mapper = new DynamoDBMapper(ddb);
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// SETUP TABLE
// try to delete the table, if it exists
try {
ddb.deleteTable(mapper.generateDeleteTableRequest(MyItem.class));
} catch (ResourceNotFoundException e) {
// no problemo, table did not exis.
}
// create new table
ProvisionedThroughput provisionedThroughput = new ProvisionedThroughput(5L, 5L);
CreateTableRequest request = mapper.generateCreateTableRequest(MyItem.class);
request.setProvisionedThroughput(provisionedThroughput);
ddb.createTable(request);
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// SAVE AN INSTANCE OF MYITEM
{
System.out.println("Attempting to save MyItem instance (version not set)");
MyItem myItemToSave = new MyItem();
myItemToSave.setId("SOME ID");
TransactionWriteRequest twr = new TransactionWriteRequest();
twr.addPut(myItemToSave);
mapper.transactionWrite(twr);
System.out.println("Saved! Assigned Id = " + myItemToSave.getVersion());
}
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// SAVE ANOTHER INSTANCE OF MYITEM
{
System.out.println("Attempting to save MyItem instance (version not set)");
MyItem myItemToSave = new MyItem();
myItemToSave.setId("SOME ID");
TransactionWriteRequest twr = new TransactionWriteRequest();
twr.addPut(myItemToSave);
mapper.transactionWrite(twr);
System.out.println("Saved! Assigned Id = " + myItemToSave.getVersion());
}
}
However, it does throw - this code yields:
Attempting to save MyItem instance (version not set)
Saved! Assigned Id = 1
Attempting to save MyItem instance (version not set)
Exception in thread "main" com.amazonaws.services.dynamodbv2.model.TransactionCanceledException: Transaction cancelled, please refer cancellation reasons for specific reasons [ConditionalCheckFailed] (Service: AmazonDynamoDBv2; Status Code: 400; Error Code: TransactionCanceledException; Request ID: 72ce4cc4-6981-482b-a911-ab5cd97d17e2)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.handleErrorResponse(AmazonHttpClient.java:1799)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.handleServiceErrorResponse(AmazonHttpClient.java:1383)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeOneRequest(AmazonHttpClient.java:1359)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeHelper(AmazonHttpClient.java:1139)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.doExecute(AmazonHttpClient.java:796)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeWithTimer(AmazonHttpClient.java:764)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.execute(AmazonHttpClient.java:738)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.access$500(AmazonHttpClient.java:698)
at com.amazonaws.http.AmazonHttpClient$RequestExecutionBuilderImpl.execute(AmazonHttpClient.java:680)
at com.amazonaws.http.AmazonHttpClient.execute(AmazonHttpClient.java:544)
at com.amazonaws.http.AmazonHttpClient.execute(AmazonHttpClient.java:524)
at com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient.doInvoke(AmazonDynamoDBClient.java:5110)
at com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient.invoke(AmazonDynamoDBClient.java:5077)
at com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient.executeTransactWriteItems(AmazonDynamoDBClient.java:4201)
at com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient.transactWriteItems(AmazonDynamoDBClient.java:4167)
at com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper.transactionWrite(DynamoDBMapper.java:1122)
at com.amazonaws.services.dynamodbv2.datamodeling.AbstractDynamoDBMapper.transactionWrite(AbstractDynamoDBMapper.java:163)
at com.example.reconpoc.document.Driver.selfContainedTestCase_TransactionWriteDoesNOTClobber(Driver.java:71)
at com.example.reconpoc.document.Driver.main(Driver.java:24)
It appears that DynamoDBMapper::generateTransactWriteItem
explicitly computes / adds a condition using versionAttributeConditionExpressionGenerator
.
You are right, transactionWrite added support for optimistic locking at the end of 2019, the documentation is out of date.
Marking as a feature request, will discuss it with the team.
Do we have any updates on this feature?
You are right, transactionWrite added support for optimistic locking at the end of 2019, the documentation is out of date.
Marking as a feature request, will discuss it with the team.
Maybe I'm not following, but if SaveBehavior.CLOBBER is no longer the default then what is? SaveBehavior.PUT?
This is a very old issue that is probably not getting as much attention as it deserves. We encourage you to check if this is still an issue in the latest release and if you find that this is still a problem, please feel free to provide a comment or open a new issue.