AWS Developer Blog

The DynamoDBMapper, Local Secondary Indexes, and You!

Earlier this year, Amazon DynamoDB released support for local secondary indexes. At that time, the AWS SDK for Java added support for LSIs, for both the low-level(AmazonDynamoDBClient) and high-level(DynamoDBMapper) APIs in the com.amazonaws.services.dynamodbv2 package. Since then, I have seen a few questions on how to use the DynamoDBMapper with local secondary indexes. In this post, I will build on the Music Collection sample that is included in the Amazon DynamoDB documentation.

The example table uses a String hash key (Artist), a String range key (SongTitle), and a local secondary index on the AlbumTitle attribute (also a String). I created the table used in this example with the DynamoDB support that is part of the AWS Toolkit for Eclipse, but you could use the code included in the documentation or the AWS Management Console. I also used the Eclipse Toolkit to populate the table with some sample data. Next, I created a POJO to represent an item in the MusicCollection table. The code for MusicCollectionItem is shown below.

@DynamoDBTable(tableName="MusicCollection")
public class MusicCollectionItem {

    private String artist;
    private String songTitle;
    private String albumTitle;
    private String genre;
    private String year;

    @DynamoDBHashKey(attributeName="Artist")
    public String getArtist() { return artist; }
    public void setArtist(String artist) { this.artist = artist; }

    @DynamoDBRangeKey(attributeName = "SongTitle")
    public String getSongTitle() { return songTitle; }
    public void setSongTitle(String songTitle) { this.songTitle = songTitle; }

    @DynamoDBIndexRangeKey(attributeName="AlbumTitle", 
                           localSecondaryIndexName="AlbumTitleIndex")
    public String getAlbumTitle() { return albumTitle; }
    public void setAlbumTitle(String albumTitle) { this.albumTitle = albumTitle; }

    @DynamoDBAttribute(attributeName="Genre")
    public String getGenre() { return genre; }
    public void setGenre(String genre) { this.genre = genre; }

    @DynamoDBAttribute(attributeName="Year")
    public String getYear() { return year;}
    public void setYear(String year) { this.year = year; }
}

As you can see, MusicCollectionItem has the hash key and range key annotations, but also a new annotation DynamoDBIndexRangeKey. You can find the documentation for that annotation here. The DynamoDBIndexRangeKey marks the property as an alternate range key to be used in a local secondary index. Since Amazon DynamoDB can support up to five local secondary indexes, I can also have up to five attributes annotated with the DynamoDBIndexRangeKey. Also note in the code above, since the documentation sample uses PascalCase, I needed to include the attributeName='X' in each of the annotations. If you were starting from scratch, you could make this code simpler by using attribute names that match your instance variable names.

So now that you have both a table and a corresponding POJO using a local secondary index, how do you use it with the DynamoDBMapper? Using a local secondary index with the mapper is pretty straightforward. You create the mapper the same way as before:

dynamoDB = Region.getRegion(Regions.US_WEST_2)
           .createClient(AmazonDynamoDBClient.class, new ClasspathPropertiesFileCredentialsProvider(), null);
mapper = new DynamoDBMapper(dynamoDB);;

Next, you can query the range key in the same manner as you would a table without a local secondary index:

String artist = "The Okee Dokee Brothers";
MusicCollectionItem musicKey = new MusicCollectionItem();
musicKey.setArtist(artist);
DynamoDBQueryExpression<MusicCollectionItem> queryExpression = new DynamoDBQueryExpression<MusicCollectionItem>()
      .withHashKeyValues(musicKey);
List<MusicCollectionItem> myCollection = mapper.query(MusicCollectionItem.class, queryExpression);

This code looks up my kids new favorite artist and returns all the song titles that are in my Amazon DynamoDB table. I could add a Condition that would limit the song titles, but I wanted to get list of all of them.

But what if I want to know which songs are on The Okee Dokee Brothers latest album—Can you Canoe? Well luckily, I have a local secondary index on the AlbumTitle attribute. Before local secondary indexes, I could only do a Scan operation, which would have scanned the entire table, but with local secondary indexes I can easily do a Query operation. The code for using the index is:

rangeKeyCondition = new Condition();
rangeKeyCondition.withComparisonOperator(ComparisonOperator.EQ)
     .withAttributeValueList(new AttributeValue().withS("Can You Canoe?"));
queryExpression = new DynamoDBQueryExpression<MusicCollectionItem>()
     .withHashKeyValues(musicKey)
     .withRangeKeyCondition("AlbumTitle", rangeKeyCondition);
myCollection = mapper.query(MusicCollectionItem.class, queryExpression);

As you can see, doing a query on a local secondary index with the DynamoDBMapper is exactly the same as doing a range key query.

Now that I have shown how easy it is to use a local secondary index with the DynamoDBMapper, how will you use them? Let us know in the comments!