9

We have a DynamoDB table which has an attribute counter, which will be decremented asynchronously by multiple lambda based on an event. I am trying to update the counter using UpdateItemEnhancedRequest (using the Dynamodb Enhanced Client. - JAVA SDK 2). I am able to build the condition for updating the counter but it updates the entire item and not just the counter. Can somebody please guide on how to update a single attribute using DynamoDb Enhanced Client?

Code Sample

    public void update(String counter, T item) {

    AttributeValue value = AttributeValue.builder().n(counter).build();

    Map<String, AttributeValue> expressionValues = new HashMap<>();
    expressionValues.put(":value", value);

    Expression myExpression = Expression.builder()
            .expression("nqctr = :value")
            .expressionValues(expressionValues)
            .build();

    UpdateItemEnhancedRequest<T> updateItemEnhancedRequest =
            UpdateItemEnhancedRequest.builder(collectionClassName)
                    .item(item)
                    .conditionExpression(myExpression)
                    .build();

    getTable().updateItem(updateItemEnhancedRequest);

    }

2 Answers 2

5

When you update a specific column, you need to specify which column to update. Assume we have this table:

enter image description here

Now assume we want to update the archive column. You need to specify the column in your code. Here we change the archive column of the item that corresponds to the key to Closed (a single column update). Notice we specify the column name by using the HashMap object named updatedValues.

// Archives an item based on the key
public String archiveItem(String id){
        DynamoDbClient ddb = getClient();

        HashMap<String,AttributeValue> itemKey = new HashMap<String,AttributeValue>();
        itemKey.put("id", AttributeValue.builder()
                .s(id)
                .build());

        HashMap<String, AttributeValueUpdate> updatedValues =
                new HashMap<String,AttributeValueUpdate>();

        // Update the column specified by name with updatedVal
        updatedValues.put("archive", AttributeValueUpdate.builder()
                .value(AttributeValue.builder()
                        .s("Closed").build())
                .action(AttributeAction.PUT)
                .build());

        UpdateItemRequest request = UpdateItemRequest.builder()
                .tableName("Work")
                .key(itemKey)
                .attributeUpdates(updatedValues)
                .build();

        try {
            ddb.updateItem(request);
            return"The item was successfully archived";

NOTE: This is not the Enhanced Client.

This code is from the AWS Tutorial that show how to build a Java web app by using Spring Boot. Full tutorial here:

Creating the DynamoDB web application item tracker

TO update a single column using the Enhanced Client, call the Table method. This returns a DynamoDbTable instance. Now you can call the updateItem method.

Here is the logic to update the the archive column using the Enhanced Client. Notice you get a Work object, call its setArchive then pass the Work object. workTable.updateItem(r->r.item(work));

Java code:

  // Update the archive column by using the Enhanced Client.
    public String archiveItemEC(String id) {

        DynamoDbClient ddb = getClient();

        try {

            DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder()
                    .dynamoDbClient(getClient())
                    .build();

            DynamoDbTable<Work> workTable = enhancedClient.table("Work", TableSchema.fromBean(Work.class));

            //Get the Key object.
            Key key = Key.builder()
                    .partitionValue(id)
                    .build();

            // Get the item by using the key.
            Work work = workTable.getItem(r->r.key(key));
            work.setArchive("Closed");

            workTable.updateItem(r->r.item(work));
            return"The item was successfully archived";
        } catch (DynamoDbException e) {
            System.err.println(e.getMessage());
            System.exit(1);
        }
        return "";
    }

This answer shows both ways to update a single column in a DynamoDB table. The above tutorial now shows this way.

Sign up to request clarification or add additional context in comments.

9 Comments

Thanks for the quick response!! I was able to update using the DynamoDbClient but since we were using the DynamoDbEnhancedClient in our project, I was finding the same behaviour using the DynamoDbEnhancedClient.
I am looking into updating this tutorial to use the DynamoDbEnhancedClient to update that column.
See the new logic - It's a more object oriented approach. You get the object, change a value (ie - call setArchive()) then pass the Work object when you call updateItem.
This does not answer the original question at all. The question is about using the enhanced client to update specific attributes. The documentation does not say anything about doing so. The API only allows you to pass in an object, but that does not allow you to select specific attributes to update.
Wouldn't the the example with DynamoDbEnhancedClient cause to requests to Dynamodb, where the first one only requires one request?
|
2

In your original solution, you're misinterpreting the meaning of the conditionExpression attribute. This is used to validate conditions that must be true on the item that matches the key in order to perform the update, not the expression to perform the update itself.

There is a way to perform this operation with the enhanced client without needing to fetch the object before making an update. The UpdateItemEnhancedRequest class has an ignoreNulls attribute that will exclude all null attributes from the update. This is false by default, which is what causes a full overwrite of the object.

Let's assume this is the structure of your item (without all the enhanced client annotations and boilerplate, you can add those):

class T {
  public String partitionKey;
  public Int counter;
  public String someOtherAttribute

  public T(String partitionKey) {
    this.partitionKey = partitionKey;
    this.counter = null;
    this.someOtherAttribute = null
  }
}

You can issue an update of just the counter, and only if the item exists, like this:

public void update(Int counter, String partitionKey) {
    T updateItem = new T(partitionKey)
    updateItem.counter = counter

    Expression itemExistsExpression = Expression.builder()
            .expression("attribute_exists(partitionKey)")
            .build();

    UpdateItemEnhancedRequest<T> updateItemEnhancedRequest =
            UpdateItemEnhancedRequest.builder(collectionClassName)
                    .item(item)
                    .conditionExpression(itemExistsExpression)
                    .ignoreNulls(true)
                    .build();

    getTable().updateItem(updateItemEnhancedRequest);
}

1 Comment

Just be cautious about ignoreNulls(true). The fact that null fields are ignored can easily be unexpected by devs while writing new code, which can lead to bugs. Even if your method isn't just update(T item), even the example above can be confusing if someone passed null like update(null, "mykey"): that would have no effect. Arguably, the parameter should be int instead of Integer so it's non-nullable.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.