Search code examples
javaamazon-web-servicesamazon-dynamodbaws-sdk-java-2.0

DynamoDB - DynamoDbAtomicCounter return always null


I am following this document:

https://aws.amazon.com/blogs/developer/using-atomic-counters-in-the-enhanced-dynamodb-aws-sdk-for-java-2-x-client/

My goal is to use AtomicCounter to generate unique IDs, but I always get null for both examples:

My code looks like this:

@DynamoDbBean
public class CustomerDocument {

    private String root;
    private Long updateCounter;
    private Long customCounter;

    @DynamoDbPartitionKey
    public String getRoot() {
        return this.root;
    }

    public void setRoot(String id) {
        this.root = id;
    }

    @DynamoDbAtomicCounter
    public Long getUpdateCounter() {
        return this.updateCounter;
    }

    public void setUpdateCounter(Long counter) {
        this.updateCounter = counter;
    }

    @DynamoDbAtomicCounter(delta = 5, startValue = 10)
    public Long getCustomCounter() {
        return this.customCounter;
    }

    public void setCustomCounter(Long counter) {
        this.customCounter = counter;
    }
}

repository

CustomerDocument document = new CustomerDocument();
document.setRoot(root);

customerTable.updateItem(document);
CustomerDocument retrievedCustomer = customerTable.getItem(document);

retrievedCustomer.getUpdateCounter(); // null
retrievedCustomer.getCustomCounter(); // null

customerTable.updateItem(document);

retrievedCustomer = orderCounterTable.getItem(document);
retrievedCustomer.getUpdateCounter(); // null
retrievedCustomer.getCustomCounter(); // null

any idea about the issue, please?


Edit

based on the answer above, my code now looks like this:

public CustomerDocument nextCounter(String root) {
    CustomerDocument document = new CustomerDocument();
    document.setId(root);
    CustomerDocument item = orderCounterTable.getItem(document);
    if (item == null) {
        CustomerDocument defaultDocument = new CustomerDocument();
        defaultDocument.setId(root);
        defaultDocument.setCount(1L);
        customerTable.putItem(defaultDocument);
        return defaultDocument;
    } else {
        CustomerDocument newDocument = new CustomerDocument();
        newDocument.setId(item.getId());
        return customerTable.updateItem(newDocument);
    }
}

But I still get null every orderCounterTable.updateItem(newDocument):

I get only:

{
    "id": "20042024"
}

Solution

  • This functionality works as shown in the sample serverless photo asset management app that uses DynamoDbAtomicCounter.

    In this example, the front end is a React app. The backend uses AWS SDK for Java V2. Each time the application finds a new label using Amazon Rekognition, the count column in the Amazon DynamoDB table is incremented by using DynamoDbAtomicCounter. Here is the React UI. You can see the count for each Label.

    enter image description here

    Here is the applicable code that makes it work.

    This is the DynamoDB class that uses the necessary annotations - such as @DynamoDbAtomicCounter.

        package com.example.photo;
        
        import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAtomicCounter;
        import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
        import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
        import java.util.List;
        
        @DynamoDbBean
        public class Label {
            private String id;
            private Integer count;
            private List<String> images;
        
            @DynamoDbPartitionKey
            public String getId() {
                return this.id;
            };
            public void setId(String id) {
                this.id = id;
            }
        
            @DynamoDbAtomicCounter (startValue = 1)
            public Integer getCount() {
                return this.count;
            }
            public void setCount(Integer count) {
                this.count = count;
            }
        
            public List<String> getImages() {
                return this.images;
            }
            public void setImages(List<String> images) {
                this.images = images;
            }
        }
    

    Here is a method where it is used with the Enhanced Client:

    private void addSingleRecord(DynamoDbTable<Label> table, String tag, String key) {
            // Check to see if the label exists in the Amazon DynamoDB table.
            // The count item uses an @DynamoDbAtomicCounter which means it is
            // updated automatically. No need to manually set this value when the record is
            // created or updated.
            if (!checkTagExists(table, tag)) {
                Label photoRec = new Label();
                photoRec.setId(tag);
                List<String> keyList = new ArrayList<>();
                keyList.add(key);
                photoRec.setImages(keyList);
                table.putItem(photoRec);
            } else {
                // The tag exists in the table.
                Key myKey = Key.builder()
                    .partitionValue(tag)
                    .build();
    
                // Add the file name to the list.
                Label myPhoto = table.getItem(myKey);
                Label updatedPhoto = new Label();
                List<String> imageList = myPhoto.getImages();
                imageList.add(key);
                updatedPhoto.setId(tag);
                updatedPhoto.setImages(imageList);
                table.updateItem(updatedPhoto);
            }
        }
    

    UPDATE

    Note that there is a trick here - from the SDK team:

    "I figured out what’s happening. It’s because we’re reusing the same object that we read from the db with ‘getItem’. If you create a new object and set the data, it works."

    That may be the cause of your issue. Create a new object, set the data, and the increment will happen. Notice that i create a new object here:

    Label updatedPhoto = new Label();
     List<String> imageList = myPhoto.getImages();
     imageList.add(key);
     updatedPhoto.setId(tag);
     updatedPhoto.setImages(imageList);
     table.updateItem(updatedPhoto);
    

    You can find this complete example in the AWS Code Library. You can code the backend and use the AWS CDK to standup the resources and run this app. Follow the directions in the doc and follow the CDK instructions. Once you follow everything, you will see the app in action and the working DynamoDbAtomicCounter.

    See:

    Create a photo asset management application that lets users manage photos using labels

    UPDATE ON YOUR CODE

    I had to made a few changes to your code.

    CODE:

    EnhancedAtomicCounter

      package com.example.dynamodb.enhanced.test;
    
    import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider;
    import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider;
    import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
    import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable;
    import software.amazon.awssdk.enhanced.dynamodb.Key;
    import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
    import software.amazon.awssdk.regions.Region;
    import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
    
    public class EnhancedAtomicCounter {
    
        public static void main(String[] args) {
            ProfileCredentialsProvider credentialsProvider = ProfileCredentialsProvider.create();
            Region region = Region.US_EAST_1;
            DynamoDbClient ddb = DynamoDbClient.builder()
                .credentialsProvider(EnvironmentVariableCredentialsProvider.create())
                .region(region)
                .build();
    
            DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder()
                .dynamoDbClient(ddb)
                .build();
    
            String root = "1000";
            putCustObj(enhancedClient, root);
            updateRec(enhancedClient, root);
            updateRec2(enhancedClient, root);
    
        }
    
        public static void putCustObj(DynamoDbEnhancedClient enhancedClient, String root) {
            DynamoDbTable<CustomerDocument> mappedTable = enhancedClient.table("CustomerDocument", TableSchema.fromBean(CustomerDocument.class));
            CustomerDocument document = new CustomerDocument();
            document.setRoot(root);
            document.setName("Scott");
            document.setCustomCounter(200L);
    
            // Put the customer data into an Amazon DynamoDB table.
            mappedTable.putItem(document);
        }
    
        public static void updateRec(DynamoDbEnhancedClient enhancedClient, String root) {
            DynamoDbTable<CustomerDocument> mappedTable = enhancedClient.table("CustomerDocument", TableSchema.fromBean(CustomerDocument.class));
            Key key = Key.builder()
                .partitionValue(root)
                .build();
    
            // Get the item by using the key.
            CustomerDocument result = mappedTable.getItem(r->r.key(key));
    
            // Create a new object so  @DynamoDbAtomicCounter works
            CustomerDocument newCustOb = new CustomerDocument();
            newCustOb.setRoot(result.getRoot());
            newCustOb.setName("Scott2");
            mappedTable.updateItem(newCustOb);
        }
    
        public static void updateRec2(DynamoDbEnhancedClient enhancedClient, String root) {
            DynamoDbTable<CustomerDocument> mappedTable = enhancedClient.table("CustomerDocument", TableSchema.fromBean(CustomerDocument.class));
            Key key = Key.builder()
                .partitionValue(root)
                .build();
    
            // Get the item by using the key.
            CustomerDocument result = mappedTable.getItem(r->r.key(key));
    
            // Create a new object so  @DynamoDbAtomicCounter works
            CustomerDocument newCustOb = new CustomerDocument();
            newCustOb.setRoot(result.getRoot());
            newCustOb.setName("Scott3");
            mappedTable.updateItem(newCustOb);
            System.out.println("Item updated!");
        }
    }
    

    CustomerDocument

    package com.example.dynamodb.enhanced.test;
    
    import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAtomicCounter;
    import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
    import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
    
    @DynamoDbBean
    public class CustomerDocument {
    
        private String root;
        private String name;
        private Long updateCounter;
        private Long customCounter;
    
        @DynamoDbPartitionKey
        public String getRoot() {
            return this.root;
        }
    
        public String getName() {
            return this.name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public void setRoot(String id) {
            this.root = id;
        }
    
        @DynamoDbAtomicCounter (startValue = 1)
        public Long getUpdateCounter() {
            return this.updateCounter;
        }
    
        public void setUpdateCounter(Long counter) {
            this.updateCounter = counter;
        }
    
        @DynamoDbAtomicCounter(delta = 5, startValue = 10)
        public Long getCustomCounter() {
            return this.customCounter;
        }
    
        public void setCustomCounter(Long counter) {
            this.customCounter = counter;
        }
    }
    

    In my example, I made a few updates to the record by changing the name. Now
    updateCounter should be 3. It is:

    enter image description here