← Back to Home

Module 2: DynamoDB Annotations and Load

Module Overview

Explore DynamoDB annotations and learn how to load data from DynamoDB tables using the AWS SDK for Java Enhanced Client.

Learning Objectives

DynamoDB Annotations

Working with DynamoDB Annotations

The AWS SDK for Java provides annotations that make it easier to map Java classes to DynamoDB tables. These annotations are part of the DynamoDB Enhanced Client, which provides a high-level, object-oriented interface for DynamoDB operations.

Key Annotations

  • @DynamoDbBean - Marks a class as a DynamoDB item. Applied at the class level.
  • @DynamoDbPartitionKey - Marks a field as the partition key for the table. Applied to a getter method.
  • @DynamoDbSortKey - Marks a field as the sort key for the table. Applied to a getter method.
  • @DynamoDbAttribute - Maps a Java field to a differently named DynamoDB attribute. Applied to a getter method.
  • @DynamoDbIgnore - Excludes a field from being persisted to DynamoDB. Applied to a getter method.

Here's an example of a Java POJO mapped to a DynamoDB table:

import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute;

@DynamoDbBean
public class MusicItem {
  private String artist;
  private String songTitle;
  private String albumTitle;
  private int yearReleased;
  private Set<String> genres;

  @DynamoDbPartitionKey
  public String getArtist() {
    return artist;
  }

  public void setArtist(String artist) {
    this.artist = artist;
  }

  @DynamoDbSortKey
  public String getSongTitle() {
    return songTitle;
  }

  public void setSongTitle(String songTitle) {
    this.songTitle = songTitle;
  }

  @DynamoDbAttribute("album_title") // Maps to different attribute name in DynamoDB
  public String getAlbumTitle() {
    return albumTitle;
  }

  // Other getters and setters...
}

Notes on the annotations:

  • The class must be annotated with @DynamoDbBean
  • The class must have getters and setters for all properties
  • The partition key is marked with @DynamoDbPartitionKey
  • If the table has a sort key, it's marked with @DynamoDbSortKey
  • For attribute names that differ between Java and DynamoDB, use @DynamoDbAttribute with the DynamoDB name

These annotations simplify working with DynamoDB by automatically handling the conversion between Java objects and DynamoDB items.

DynamoDB Loading

Loading Data from DynamoDB

Once you've set up your annotated model classes, you can use the DynamoDB Enhanced Client to load data from your tables. The Enhanced Client provides several methods for retrieving data:

Setting Up the Enhanced Client

// Create the DynamoDB client
DynamoDbClient dynamoDbClient = DynamoDbClient.builder()
    .region(Region.US_WEST_2)
    .build();

// Create the enhanced client
DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder()
    .dynamoDbClient(dynamoDbClient)
    .build();

// Create a table schema using the MusicItem class
DynamoDbTable<MusicItem> musicTable = enhancedClient.table("Music", TableSchema.fromBean(MusicItem.class));

Loading an Item by Key

To load a single item using its primary key:

// Create a key with partition key and sort key
Key key = Key.builder()
    .partitionValue("Coldplay")
    .sortValue("Viva La Vida")
    .build();

// Get the item
MusicItem musicItem = musicTable.getItem(key);

if (musicItem != null) {
    System.out.println("Found song: " + musicItem.getSongTitle() + " by " + musicItem.getArtist());
    System.out.println("Album: " + musicItem.getAlbumTitle());
} else {
    System.out.println("Song not found");
}

Querying Data

To retrieve multiple items matching a partition key:

// Query for all songs by an artist
QueryEnhancedRequest queryRequest = QueryEnhancedRequest.builder()
    .queryConditional(QueryConditional.keyEqualTo(Key.builder()
        .partitionValue("Coldplay")
        .build()))
    .build();

// Execute the query
PageIterable<MusicItem> pagedResults = musicTable.query(queryRequest);

// Process the results
for (MusicItem song : pagedResults.items()) {
    System.out.println(song.getSongTitle() + " - " + song.getAlbumTitle());
}

Advanced Query Options

You can refine queries with additional conditions:

  • Sort Key Conditions - Filter by sort key using conditions like begins_with, greater_than, etc.
  • Filtering - Apply filters on non-key attributes
  • Pagination - Control the number of items returned per request
// Query with sort key condition and filter
QueryEnhancedRequest queryWithFilter = QueryEnhancedRequest.builder()
    .queryConditional(QueryConditional.sortBeginsWith(Key.builder()
        .partitionValue("Coldplay")
        .sortValue("V")
        .build()))
    .filterExpression(Expression.builder()
        .expression("yearReleased > :year")
        .expressionValues(Collections.singletonMap(":year", AttributeValue.builder().n("2007").build()))
        .build())
    .limit(10)
    .build();

Remember that queries in DynamoDB always operate on a single partition key value, but can return multiple items with different sort keys. For cross-partition operations, you need to use a scan or global secondary indexes.

DynamoDB Annotations and Load Overview

Best Practices for DynamoDB Data Access

When working with DynamoDB and the Enhanced Client, keep these best practices in mind:

Data Access Patterns

  • Prefer queries over scans - Queries are more efficient as they target specific partitions
  • Use batch operations - BatchGetItem and BatchWriteItem can improve performance for multiple operations
  • Implement pagination - For large result sets, retrieve data in smaller chunks

Error Handling

Robust error handling is crucial for DynamoDB operations:

try {
    MusicItem result = musicTable.getItem(key);
    // Process result
} catch (DynamoDbException e) {
    System.err.println("Error getting item: " + e.getMessage());
    // Handle specific exception types
    if (e instanceof ResourceNotFoundException) {
        // Table doesn't exist
    } else if (e instanceof ProvisionedThroughputExceededException) {
        // Implement retry with exponential backoff
    }
}

Model Design Considerations

  • Keep models simple - Avoid complex inheritance hierarchies
  • Consider immutability - For thread safety, consider making your model classes immutable
  • Use converters for complex types - For types not natively supported by DynamoDB

Implementing these practices will help you build efficient, reliable applications that interact with DynamoDB.

Resources