Mapping items in DynamoDB tables
The DynamoDB enhanced client is a high-level library that is part of the AWS SDK for Java 2.x. It offers a straightforward way to map client-side classes to DynamoDB tables. You define the relationships between tables and their corresponding model classes in your code. After you define those relationships, you can intuitively perform various create, read, update, or delete (CRUD) operations on tables or items in DynamoDB.
To begin working with the enhanced client in your project, add a dependency on the Maven
artifact dynamodb-enhanced
in addition to the dynamodb
artifact to
your build file, as shown in one of the following examples.
Create a
DynamoDbEnhancedClient
The DynamoDbEnhancedClient
instance is used to work with DynamoDB
tables and mapped classes. You create a DynamoDbEnhancedClient
from an
existing DynamoDbClient
object, as shown in the following example.
DynamoDbClient ddb = DynamoDbClient.builder() .region(Region.US_EAST_1) .build(); DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder() .dynamoDbClient(ddb) .build(); // Use the enhancedClient.
Generate a TableSchema
When you work with the mapping features of the enhanced client, the first step you
take is to generate a TableSchema
. You can do this in a couple of ways.
Use an annotated Java bean
The SDK for Java 2.x includes a set of annotationsTableSchema
for mapping your classes to tables.
Start by creating a Java data class that includes a default public constructor and
standardized names of getters and setters for each property in the class. Include a
class-level annotation to indicate it is a DynamoDbBean
and, at a
minimum, include a DynamoDbPartitionKey
annotation on the getter or
setter for the primary key of the table record.
The following Customer
class shows these annotations that will link
the class definition to the DynamoDB table.
/* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ package com.example.dynamodb; 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 java.time.Instant; /** * This class is used by the Enhanced Client examples. */ @DynamoDbBean public class Customer { private String id; private String name; private String email; private Instant regDate; @DynamoDbPartitionKey public String getId() { return this.id; } public void setId(String id) { this.id = id; } public String getCustName() { return this.name; } public void setCustName(String name) { this.name = name; } @DynamoDbSortKey public String getEmail() { return this.email; } public void setEmail(String email) { this.email = email; } public Instant getRegistrationDate() { return regDate; } public void setRegistrationDate(Instant registrationDate) { this.regDate = registrationDate; } @Override public String toString() { return "Customer [id=" + id + ", name=" + name + ", email=" + email + ", regDate=" + regDate + "]"; } }
Once you have created an annotated Java bean, use it to create the
TableSchema
, as shown in the following snippet.
TableSchema<Customer> customerTableSchema = TableSchema.fromBean(Customer.class);
Use a builder
If you would prefer to skip the slightly costly bean introspection for a faster solution, you can instead declare your schema directly and let the compiler do the heavy lifting. If you do it this way, your class does not need to follow bean naming standards nor does it need to be annotated. This following example uses a builder and is equivalent to the bean example.
TableSchema<Customer> customerTableSchema = TableSchema.builder(Customer.class) .newItemSupplier(Customer::new) .addAttribute(String.class, a -> a.name("id") .getter(Customer::getId) .setter(Customer::setId) .tags(primaryPartitionKey())) .addAttribute(Integer.class, a -> a.name("email") .getter(Customer::getEmail) .setter(Customer::setEmail) .tags(primarySortKey())) .addAttribute(String.class, a -> a.name("name") .getter(Customer::getCustName) .setter(Customer::setCustName)) .addAttribute(Instant.class, a -> a.name("registrationDate") .getter(Customer::getRegistrationDate) .setter(Customer::setRegistrationDate)) .build();
Create a table using the enhanced client
The following example shows you how to create a DynamoDB table from the
Customer
Java data bean class.
To create the DynamoDbTable
TableSchema
to the table()
method of the enhanced client.
This example creates a table with the name Customer
—identical to the
class name—but the table name can be something else. Whatever you name the table,
you must use this name in additional applications to work with the table. Supply this
name to the table()
method anytime you create another
DynamoDbTable
object.
The lambda parameter, builder
, passed to the createTable
method allows you to customize the tableDynamoDbWaiter
. Since the creation of a table takes a bit of
time, using a waiter avoids the need for you to write logic that polls the DynamoDB service
to see if the table exists before using the table.
Imports
import com.example.dynamodb.Customer; import software.amazon.awssdk.core.internal.waiters.ResponseOrException; import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient; import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable; import software.amazon.awssdk.enhanced.dynamodb.TableSchema; import software.amazon.awssdk.services.dynamodb.model.DescribeTableResponse; import software.amazon.awssdk.services.dynamodb.waiters.DynamoDbWaiter;
Code
public class EnhancedCreateTable { public static void createTable(DynamoDbEnhancedClient enhancedClient) { // Create a DynamoDbTable object DynamoDbTable<Customer> customerTable = enhancedClient.table("Customer", TableSchema.fromBean(Customer.class)); // Create the table customerTable.createTable(builder -> builder .provisionedThroughput(b -> b .readCapacityUnits(10L) .writeCapacityUnits(10L) .build()) ); System.out.println("Waiting for table creation..."); try (DynamoDbWaiter waiter = DynamoDbWaiter.create()) { // DynamoDbWaiter is Autocloseable ResponseOrException<DescribeTableResponse> response = waiter .waitUntilTableExists(builder -> builder.tableName("Customer").build()) .matched(); DescribeTableResponse tableDescription = response.response().orElseThrow( () -> new RuntimeException("Customer table was not created.")); // The actual error can be inspected in response.exception() System.out.println(tableDescription.table().tableName() + " was created."); } } }
See the complete example
Retrieve (get) an item from a table
To get an item from a DynamoDB table, create a DynamoDbTable object and call getItem()
with a GetItemEnhancedRequest object. As an alternative to passing in a
GetItemEnhancedRequest
object, you can take advantage of lambda
expressions and the SDK's builder pattern. In the example below, the getItem()
method takes a
Consumer<GetItemEnhancedRequest.Builder>
that ultimately creates
the GetItemEnhancedRequest
object for you.
For explanatory purposes, the example uses full lambda syntax,
(GetItemEnhancedRequest.Builder requestBuilder) ->
requestBuilder.key(key)
, but a simpler expression such as rb →
rb.key(key)
works as well.
The following code snippet demonstrates the use of the enhanced client to get information from an item in a DynamoDB table.
Imports
import com.example.dynamodb.Customer; 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.enhanced.dynamodb.model.GetItemEnhancedRequest; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
Code
public static String getItem(DynamoDbEnhancedClient enhancedClient) { Customer result = null; try { DynamoDbTable<Customer> table = enhancedClient.table("Customer", TableSchema.fromBean(Customer.class)); Key key = Key.builder() .partitionValue("id101").sortValue("tred@noserver.com") .build(); // Get the item by using the key. result = table.getItem( (GetItemEnhancedRequest.Builder requestBuilder) -> requestBuilder.key(key)); System.out.println("******* The description value is " + result.getCustName()); } catch (DynamoDbException e) { System.err.println(e.getMessage()); System.exit(1); } return result.getCustName(); }
See the complete example
Add a new item to a table
To insert a new item into a table using the enhanced client, you create an instance of
the Java bean data class that is annotated with @DynamoDbBean
. Use the
setters of the Java bean instance to add the data that you want to insert. Use the
putItem()
method of the DynamoDbTable
to perform
the insertion.
The following example shows one item added to the Customer
table.
Imports
import com.example.dynamodb.Customer; 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.TableSchema; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.awssdk.services.dynamodb.model.DynamoDbException; import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneOffset;
Code
public static void putRecord(DynamoDbEnhancedClient enhancedClient) { try { DynamoDbTable<Customer> custTable = enhancedClient.table("Customer", TableSchema.fromBean(Customer.class)); // Create an Instant value. LocalDate localDate = LocalDate.parse("2020-04-07"); LocalDateTime localDateTime = localDate.atStartOfDay(); Instant instant = localDateTime.toInstant(ZoneOffset.UTC); // Populate the Table. Customer custRecord = new Customer(); custRecord.setCustName("Tom red"); custRecord.setId("id101"); custRecord.setEmail("tred@noserver.com"); custRecord.setRegistrationDate(instant) ; // Put the customer data into an Amazon DynamoDB table. custTable.putItem(custRecord); } catch (DynamoDbException e) { System.err.println(e.getMessage()); System.exit(1); } System.out.println("Customer data added to the table with id id101"); }
See the complete example
Batch create (put) and delete items
You can batch a series of put requests (PutItemEnhancedRequest) and delete requests (DeleteItemEnhancedRequest) to one or more tables, and then send all of the changes in a single request.
The SDK offers the builder pattern to create the PutItemEnhancedRequest
for you. A lambda expression, such as builder -> builder.item(record2)
is
all you need to provide to the addPutItem()
method as shown in the example below.
A DynamoDbTable object is created and three items are queued up to be added
to the Customer table in the first call to WriteBatch.builder
. You can call addDeleteItem()
and addPutItem()
(part of WriteBatch.Builder) multiple times in each batch, as needed.
To queue up changes to a different table, make another call to
WriteBatch.builder()
and provide a corresponding DynamoDbTable object in mappedTableResource()
. This is shown
below in the second WriteBatch.builder()
call using the Music table and
deleting one item from the table.
Imports
import com.example.dynamodb.Customer; import com.example.dynamodb.Music; 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.enhanced.dynamodb.model.BatchWriteItemEnhancedRequest; import software.amazon.awssdk.enhanced.dynamodb.model.WriteBatch; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.awssdk.services.dynamodb.model.DynamoDbException; import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneOffset;
Code
public static void putBatchRecords(DynamoDbEnhancedClient enhancedClient) { try { DynamoDbTable<Customer> customerMappedTable = enhancedClient.table("Customer", TableSchema.fromBean(Customer.class)); DynamoDbTable<Music> musicMappedTable = enhancedClient.table("Music", TableSchema.fromBean(Music.class)); LocalDate localDate = LocalDate.parse("2020-04-07"); LocalDateTime localDateTime = localDate.atStartOfDay(); Instant instant = localDateTime.toInstant(ZoneOffset.UTC); Customer record2 = new Customer(); record2.setCustName("Fred Pink"); record2.setId("id110"); record2.setEmail("fredp@noserver.com"); record2.setRegistrationDate(instant) ; Customer record3 = new Customer(); record3.setCustName("Susan Pink"); record3.setId("id120"); record3.setEmail("spink@noserver.com"); record3.setRegistrationDate(instant) ; Customer record4 = new Customer(); record4.setCustName("Jerry orange"); record4.setId("id101"); record4.setEmail("jorange@noserver.com"); record4.setRegistrationDate(instant) ; BatchWriteItemEnhancedRequest batchWriteItemEnhancedRequest = BatchWriteItemEnhancedRequest.builder() .writeBatches( WriteBatch.builder(Customer.class) // add items to the Customer table .mappedTableResource(customerMappedTable) .addPutItem(builder -> builder.item(record2)) .addPutItem(builder -> builder.item(record3)) .addPutItem(builder -> builder.item(record4)) .build(), WriteBatch.builder(Music.class) // delete an item from the Music table .mappedTableResource(musicMappedTable) .addDeleteItem(builder -> builder.key( Key.builder().partitionValue("Famous Band").build())) .build()) .build(); // Add three items to the Customer table and delete one item from the Music table enhancedClient.batchWriteItem(batchWriteItemEnhancedRequest); System.out.println("done"); } catch (DynamoDbException e) { System.err.println(e.getMessage()); System.exit(1); } }
See the complete example
Use a filtered query to get items from a table
You can get items from a table based on filterable queries, and then perform operations on the query results.
For the example below, assume the Customer
table contains the
following items:
id | custName | registrationDate | |
---|---|---|---|
id120 | spink@noserver.com | Susan Pink | 2020-04-07T00:00:00Z |
id101 | jorange@noserver.com | Jerry orange | 2020-04-07T00:00:00Z |
id101 | tred@noserver.com | Tom red | 2020-04-07T00:00:00Z |
ed110 | fredp@noserver.com | Fred Pink | 2020-04-07T00:00:00Z |
The following steps describes what is happening in the
queryTableFilter
method below.
-
Build an expression to filter the query.
-
You build a filter by first defining the value to match on as an AttributeValue object ("Tom red" in this example).
-
You create a
HashMap
to hold a token as the map's key (":value" in this example) and theAttributeValue
object as the map's value. -
You then build an
Expression
using a filter expression ("custName = :value") and the expression values in the map.
-
-
Build a
QueryConditional
object.-
You build a
QueryConditional
object to select items based on the primary key value "id101".
-
-
Build the query request and execute the query.
-
Pass a lambda parameter to the DynamoDbTable's
query()
method. The SDK uses the parameter's logic to build the final query request object to send to the DynamoDB service.
-
-
Process the results.
-
This example processes the iterable results returned from the query in a for-each loop by counting the number of items returned and printing out the
Customer
object.
-
The following log output shows the request that is sent to the DynamoDB service.
DEBUG org.apache.http.wire:87 - http-outgoing-0 >> "{"TableName":"Customer","FilterExpression":"custName = :value","KeyConditionExpression":"#AMZN_MAPPED_id = :AMZN_MAPPED_id","ExpressionAttributeNames":{"#AMZN_MAPPED_id":"id"},"ExpressionAttributeValues":{":AMZN_MAPPED_id":{"S":"id101"},":value":{"S":"Tom red"}}}"
The QueryConditional interface has several methods you can use to build your queries, including common conditional statements like greater than, less than, and in between.
Imports
import com.example.dynamodb.Customer; 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.Expression; import software.amazon.awssdk.enhanced.dynamodb.Key; import software.amazon.awssdk.enhanced.dynamodb.TableSchema; import software.amazon.awssdk.enhanced.dynamodb.model.QueryConditional; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.awssdk.services.dynamodb.model.AttributeValue; import software.amazon.awssdk.services.dynamodb.model.DynamoDbException; import java.util.HashMap; import java.util.Map;
Code
public static Integer queryTableFilter(DynamoDbEnhancedClient enhancedClient) { Integer countOfCustomers = 0; try { DynamoDbTable<Customer> mappedTable = enhancedClient.table("Customer", TableSchema.fromBean(Customer.class)); AttributeValue att = AttributeValue.builder() .s("Tom red") .build(); Map<String, AttributeValue> expressionValues = new HashMap<>(); expressionValues.put(":value", att); Expression expression = Expression.builder() .expression("custName = :value") .expressionValues(expressionValues) .build(); // Create a QueryConditional object to query by partitionValue. // Since the Customer table has a sort key attribute (email), we can use an expression // to filter the query results if multiple items have the same partition key value. QueryConditional queryConditional = QueryConditional .keyEqualTo(Key.builder().partitionValue("id101") .build()); // Perform the query for (Customer customer : mappedTable.query( r -> r.queryConditional(queryConditional) .filterExpression(expression) ).items()) { countOfCustomers++; System.out.println(customer); } } catch (DynamoDbException e) { System.err.println(e.getMessage()); System.exit(1); } System.out.println("Done"); return countOfCustomers;
The code returns one item. The QueryConditional
object matches items that
have an id of "id101" and the filter expression further restricts the items that are
returned to items where custName
is equal to "Tom red".
See the complete example
Retrieve (get) all items from a table
When you want to get all of the records in a given DynamoDB table, use the
scan()
method of your DynamoDbTable object and the items()
method to get access to
each item, against which you can execute various operations. For example, the following
code snippet prints out the id and customer name of each item in the
Customer
table.
Imports
import com.example.dynamodb.Customer; 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.TableSchema; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.awssdk.services.dynamodb.model.DynamoDbException; import java.util.Iterator;
Code
public static void scan( DynamoDbEnhancedClient enhancedClient) { try{ DynamoDbTable<Customer> custTable = enhancedClient.table("Customer", TableSchema.fromBean(Customer.class)); Iterator<Customer> results = custTable.scan().items().iterator(); while (results.hasNext()) { Customer rec = results.next(); System.out.println("The record id is "+rec.getId()); System.out.println("The name is " +rec.getCustName()); } } catch (DynamoDbException e) { System.err.println(e.getMessage()); System.exit(1); } System.out.println("Done"); }
See the complete example
For more information, see Working with items in DynamoDB in the Amazon DynamoDB Developer Guide.