10
votes

I'm new to DynamoDB and trying out a sample scenario using Transaction support. I'm using the same entity provided in dynamodb-transaction library. Only difference is I've added a range key with the hash key. Here's the table definition:

  • ItemId --> hash key , string
  • ItemName --> range key, string

@DynamoDBTable(tableName = "Item")
public static class ExampleItem {
    private String itemId;
    private String value;
    private String itemName;
    private Long version;
    @DynamoDBHashKey(attributeName = "ItemId")
    public String getItemId() {
        return itemId;
    }
    public void setItemId(String itemId) {
        this.itemId = itemId;
    }
    @DynamoDBRangeKey(attributeName="ItemName")
    public String getItemName() {
        return itemName;
    }
    public void setItemName(String itemName) {
        this.itemName = itemName;
    }
    public String getValue() {
        return value;
    }
    public void setValue(String value) {
        this.value = value;
    }
    @DynamoDBVersionAttribute
    public Long getVersion() {
        return version;
    }
    public void setVersion(Long version) {
        this.version = version;
    }
}

As you can see, I'm using the version attribute as well. Now, I'm trying to execute a simple create or update scenario using transaction/dbmapper. Here's the code snippet.

Transaction t1 = txManager.newTransaction();
ExampleItem keyItem = new ExampleItem();
keyItem.setItemId("Item1");
keyItem.setItemName("USA");
ExampleItem item = t1.load(keyItem);
if (item != null) {
    item.setValue("Magenta");
    item.setItemName("UK");
    t1.save(item);
} else {
    item = new ExampleItem();
    item.setItemId(keyItem.getItemId());
    item.setItemName("USA");
    item.setValue("Violet");
    t1.save(item);
}
t1.commit();
t1.delete();

I'm able to add the record without any issue, but I'm having a problem when I'm trying to read the record and update any attribute. I'm getting the following exception:

Uncaught exception:com.amazonaws.services.dynamodbv2.model.ConditionalCheckFailedException: Status Code: 0, AWS Service: null, AWS Request ID: null, AWS Error Code: null, AWS Error Message: Item {ItemId={S: Item1,}, ItemName={S: UK,}} had unexpected attributes: Status Code: 0, AWS Service: null, AWS Request ID: null, AWS Error Code: null, AWS Error Message: expected attribute(s) {version={Value: {N: 1,},Exists: true}} but found null
com.amazonaws.services.dynamodbv2.model.ConditionalCheckFailedException: Status Code: 0, AWS Service: null, AWS Request ID: null, AWS Error Code: null, AWS Error Message: Item {ItemId={S: Item1,}, ItemName={S: UK,}} had unexpected attributes: Status Code: 0, AWS Service: null, AWS Request ID: null, AWS Error Code: null, AWS Error Message: expected attribute(s) {version={Value: {N: 1,},Exists: true}} but found null

Looks like it has something to do with version, but not sure where am I going wrong. Any pointers will be appreciated.

-Thanks

Shamik

3
found any thing helpfull?M-sAnNan

3 Answers

3
votes

Dynamodb has this concept of Optimistic Locking, is strategy to ensure that the client-side item that you are updating (or deleting) is the same as the item in DynamoDB.

  1. First save:

If it's a first update, you have to follow this format

DynamodbSaveExpression saveExpression = 
       new DynamodbSaveExpression()
       .withExpectedEntry("ItemId", new ExpectedAttributeValue().withExists(false)
       .withConditionalOperator(AND)
       .withExpectedEntry("ItemName", new ExpectedAttributeValue().withExists(false));
// saving the item to dynamodb 
dynamodbmapper.save(itemToSave, saveExpression);

Explanation: We are telling that itemToSave should be saved only when both "ItemId", "ItemValue" combination is not there in dynamodb.

  1. Update to an existing item:

For an update to an existing item in dynamodb, you have to follow this format

// Step1: Do a GET
ExampleItem itemInDynamo = dynamoDBMapper.load(ExampleItem.class, "item_id_value", "item_name_value");
// Step2: Get the version in Dynamo
Long versionInDynamo = itemInDynamo.getVersion();
DynamodbSaveExpression saveExpression = 
       new DynamodbSaveExpression()
       .withExpectedEntry("version", 
       new ExpectedAttributeValue(new AttributeValue().withN(versionInDynamodb.toString())));
// Step3: Update the item
dynamodbmapper.save(itemToSave, saveExpression);

Save will only succeed if the version with which you are saving is the same in the Dynamodb, if not save will fail with ConditionalCheckFailedException then you have retry from step1. This is Read first then Write.

2
votes

Looks like you need to set the version when creating the item class. It's expecting 1 but found null. The versionedAttribute is expecting a value, and it seems when the item is created for the first time it will expect 1.

If you update this item in the future, it should take care of incrementing the version internally.

1
votes

Usualy this occurs when your keys are same for more then one element in dynamodb. For example if you have Folder with id as hashKey and folderName as rangeKey, You wont save the Folder again where your hash(hashkey+rangeKey) would be same.