DynamoDB Mapper GSI Query Example in Java

Trying to figure out how to perform and GSI (Global Secondary Index) Query on your AWS DynamoDB Table using DynamoDB Mapper? This is the article for you.

To perform a GSI query on DynamoDB with DynamoDB Mapper, there a couple of small pre-requisites:

  1. You need IAM user credentials with dynamodb:query permissions
  2. A dependency on the on the AWS SDK which includes DynamoDBMapper (I suggest Maven for Java dependency management)

Starting State

We’re going to be using the same table as my previous articles on DynamoDB Mapper Load and Non-GSI Queries.

The table I’ve created for this tutorial is one called CustomerOrders, and here’s a snapshot of its contents.

Starting State – My DynamoDB CustomerOrders table

I also have a model file called CustomerOrders.java that looks like below. Keep in mind I’m using the popular library Lombok to generate boilerplate code.

Note here that I’ve added the @DynamoDBIndexHashKey annotation above the orderID field. This attribute is what tells DynamoDB Mapper that this model qualifies for queries based on the index. If you try to perform a query using the index other than customerID or orderID in this case, DynamoDB Mapper will throw an exception.

@ToString
@NoArgsConstructor //You need a empty default constructor
@Getter
@Setter
@DynamoDBTable(tableName = "CustomerOrders")
public class CustomerOrder {
    @DynamoDBHashKey(attributeName = "CustomerID")
    private String customerID;
    
    @DynamoDBIndexHashKey(globalSecondaryIndexName = "OrderID-index")
    @DynamoDBRangeKey(attributeName = "OrderID")
    private String orderID;

    @DynamoDBAttribute(attributeName = "OrderAddress")
    private String orderAddress;

    @DynamoDBAttribute(attributeName = "OrderTotal")
    private Long orderTotal;
}

Notice the custom fields (minus the one explained above), as explained below:

  • @DynamoDBTable – Tells DynamoDBMapper that this model file corresponds to data stored in the table with the provided name
  • @DynamoDBIndexHashKey – Indicates this attribute is to be used as the HashKey (sometimes also called PartitionKey) for our table.
  • @DynamoDBRangeKey – Indicates this attribute is the RangeKey for our table
  • @DynamoDBAttribute – A basic attribute field

Another pre-requisite is that we need to create a Global Secondary Index (GSI) on our DynamoDb table. Note that if you don’t have an index and try to perform a query against it, you’ll get an exception that looks like below:

An exception thrown by DynamoDB mapper when attempting to perform a query on an index that does not yet exist on your table.

You may enjoy this article on DynamoDB Scan VS Query – When to Use What?

In order to prevent this, make sure you have a GSI created with the name OrderID-index and that is has a partitionkey on theOorderID attribute, like below:

Our GSI as created in the DynamoDB console. Don’t forget that GSIs are CASE SENSITIVE!

Note: Make sure the partition key you enter for your GSI is an EXACT MATCH of the attribute you currently have on your table. Case sensitivity matters here. Failure to do so will result in hours of debugging ambiguous errors and immense frustration!

Basic Client Setup

We leverage AWSCredentialsProvider and AmazonDynamoDBClient to create the corresponding inputs for our mapper class. Afterwards, we’re ready to perform our query.

        //Specify credential details
        AWSCredentialsProvider credentials = new AWSStaticCredentialsProvider(
                new BasicAWSCredentials(System.getenv("ACCESS_KEY"),
                                        System.getenv("SECRET_ACCESS_KEY")));

        //Create client
        AmazonDynamoDB ddbClient = AmazonDynamoDBClientBuilder.standard()
                .withCredentials(credentials)
                .withRegion("us-east-1") //Remember to change your region!
                .build();

        DynamoDBMapper mapper = new DynamoDBMapper(ddbClient);

Before proceeding, make sure you either set your access key and secret access key as environment variables (this is IDE / OS specific) and specifying the region your table is located. Failure to do so will result in more obscure errors that take way too long to debug.

Performing a Query on a GSI with DynamoDB Mapper

Firstly, lets look at the code and then walk through whats going on here.

        CustomerOrder customerOrder = new CustomerOrder();
        customerOrder.setOrderID("5");

        DynamoDBQueryExpression<CustomerOrder> queryExpression =
                new DynamoDBQueryExpression<CustomerOrder>()
                .withHashKeyValues(customerOrder)
                .withLimit(10)
                .withIndexName("OrderID-index")
                .withConsistentRead(false);


        List<CustomerOrder> queryResult = mapper.query(CustomerOrder.class, queryExpression);

        queryResult.forEach(System.out::println);

Performing a basic query against a HashKey is a very straightforward process. We simply instantiate an instance of our model class, in this case CustomerOrder and set the orderID to the corresponding value we are looking for. For example, if I am looking for all records with orderId “5” in this case, I just set the orderId as value 5 on my input object that I pass in to my query expression.

We then formulate our expression and provide it our search object (CustomerOrder) in the HashKeyValues field. We also need to set our Limit to specify the maximum number of results we want back – in this case 10 is fine.

We also need to provide the Index Name of our GSI in the IndexName field on our query expression. Failure to do this will make DynamoDB think you are querying against the default index, for which we have not provided a search value (CustomerID).

Note that for DynamoDB GSI Queries, consistent read is not supported. In fact, DynamoDB mapper forces you to acknowledge this by throwing an exception if you attempt to perform a query on a GSI with consistent read enabled.

Finally, we use the mapper’s query function to execute our query against DynamoDB. We just need to set the result type to List and provide the class type we expect it to return. Printing out the response produces returns a single record with orderId=5, perfect!

CustomerOrder(customerID=CUSTOMER3, orderID=5, orderAddress=123 Main St, orderTotal=7.11)

And that’s it! You just learned how to perform a GSI query on your DynamoDB Table with DynamoDB Mapper!

I hope you enjoyed this article. Check out my other DynamoDB articles here.

Total
0
Shares
Leave a Reply

Your email address will not be published. Required fields are marked *

Related Posts