Thursday 20 June 2013

Introduction


  • Amazon DynamoDB is a fully managed NoSQL database service that provides fast and predictable performance with seamless scalability.
  • Amazon DynamoDB automatically spreads the data and traffic for the table over a sufficient number of servers to handle the request capacity specified by the customer and the amount of data stored, while maintaining consistent and fast performance.
  • All data items are stored on Solid State Disks (SSDs) and are automatically replicated across multiple Availability Zones in a Region to provide built-in high availability and data durability.
  • You can launch a new Amazon DynamoDB database table, scale up or down your request capacity for the table without downtime or performance degradation, and gain visibility into resource utilization and performance metrics, all through the AWS Management Console.
  • With Amazon DynamoDB, you can offload the administrative burdens of operating and scaling distributed databases to AWS, so you don’t have to worry about hardware provisioning, setup and configuration, replication, software patching, or cluster scaling.



Amazon DynamoDB Annotations



  • @DynamoDBTable


Identifies the target table in Amazon DynamoDB. For example, the following Java code snippet defines a class Developer and maps it to the People table in Amazon DynamoDB.
@DynamoDBTable(tableName="People")
public class Developer { ...}


  • @DynamoDBIgnore


Indicates to the DynamoDBMapper instance that the associated property should be ignored. When saving data to the table, the DynamoDBMapper does not save this property to the table.


  • @DynamoDBAttribute
Maps a property to a table attribute. By default, each class property maps to an item attribute with the same name. However, if the names are not the same, using this tag you can map a property to the attribute. In the following Java snippet, the DynamoDBAttribute maps the BookAuthors property to the Authors attribute name in the table.
@DynamoDBAttribute(attributeName = "Authors")
public List<String> getBookAuthors() { return BookAuthors; }
public void setBookAuthors(List<String> BookAuthors) { this.BookAuthors = BookAuthors; }


The DynamoDBMapper uses Authors as the attribute name when saving the object to the table.


  • @DynamoDBHashKey


Maps a class property to the hash attribute of the table. The property must be one of the supported String or Numeric type and cannot be a collection type.
Assume that you have a table, ProductCatalog, that has Id as the primary key. The following Java code snippet defines a CatalogItem class and maps itsId property to the primary key of the ProductCatalog table using the @DynamoDBHashKey tag.
@DynamoDBTable(tableName="ProductCatalog")
public class CatalogItem {
   private String Id;   
  @DynamoDBHashKey(attributeName="Id")
  public String getId() {
       return Id;
  }
  public void setId(String Id) {
       this.Id = Id;
  }
  // Additional properties go here.
}


  • @DynamoDBRangeKey


Maps a class property to the range key attribute of the table. If the primary key is made of both the hash and range key attributes, you can use this tag to map your class field to the range attribute. For example, assume that you have a Reply table that stores replies for forum threads. Each thread can have many replies. So the primary key of this table is both the ThreadId and ReplyDateTime. The ThreadId is the hash attribute and ReplyDateTime is the range attribute. The following Java code snippet defines a Reply class and maps it to the Reply table. It uses both the @DynamoDBHashKey and @DynamoDBRangeKeytags to identify class properties that map to the primary key.
@DynamoDBTable(tableName="Reply")
public class Reply {
   private String id;
   private String replyDateTime;

   @DynamoDBHashKey(attributeName="Id")
   public String getId() { return id; }
   public void setId(String id) { this.id = id; }

   @DynamoDBRangeKey(attributeName="ReplyDateTime")
   public String getReplyDateTime() { return replyDateTime; }
   public void setReplyDateTime(String replyDateTime) { this.replyDateTime = replyDateTime; }

  // Additional properties go here.
}



  • @DynamoDBAutoGeneratedKey


Marks a hash key or range key property as being auto-generated. The Object Persistence Model will generate a random UUID when saving these attributes. Only String properties can be marked as auto-generated keys.
The following snippet demonstrates using auto-generated keys.
@DynamoDBTable(tableName="AutoGeneratedKeysExample")
public class AutoGeneratedKeys {
   private String id;
   private String payload;
   
   @DynamoDBHashKey(attributeName = "Id")
   @DynamoDBAutoGeneratedKey
   public String getId() { return id; }
   public void setId(String id) { this.id = id; }

   @DynamoDBAttribute(attributeName="payload")
   public String getPayload() { return this.payload };
   public String setPayload(String payload) { this.payload = payload };   
  
   public static void saveItem() {
       AutoGeneratedKeys obj = new AutoGeneratedKeys();
       obj.setPayload("abc123");
       
       // id field is null at this point       
       DynamoDBMapper mapper = new DynamoDBMapper(dynamoDBClient);
       mapper.save(obj);
       
       System.out.println("Object was saved with id " + obj.getId());
   }
}


  • @DynamoDBVersionAttribute


Identifies a class property for storing an optimistic locking version number. DynamoDBMapper assigns a version number to this property when it saves a new item, and increments it each time you update the item. Only number scalar types are supported.


DynamoDBMapper Class




The DynamoDBMapper class is the entry point to Amazon DynamoDB. It provides a connection to Amazon DynamoDB and enables you to access your data in various tables, perform various CRUD operations on items, and execute queries and scans against tables. This class provides the following key operations for you to work with Amazon DynamoDB.


  • save
    • Saves the specified object to the table. The object that you wish to save is the only required parameter for this method. You can provide optional configuration parameters using the DynamoDBMapperConfig object.
    • If an item that has the same primary key does not exist, this method creates a new item in the table. If an item that has the same primary key exists, it updates the existing item. String hash and range keys annotated with @DynamoDBAutoGeneratedKey are given a random universally unique identifier (UUID) if left uninitialized. Version fields annotated with @DynamoDBVersionAttribute will be incremented by one. Additionally, if a version field is updated or a key generated, the object passed in is updated as a result of the operation.


mapper.save(obj, new DynamoDBMapperConfig(DynamoDBMapperConfig.SaveBehavior.CLOBBER));



  • load
    • Retrieves an item from a table. You must provide the primary key of the item that you wish to retrieve.


CatalogItem item = mapper.load(CatalogItem.class, item.getId(),
               new DynamoDBMapperConfig(DynamoDBMapperConfig.ConsistentReads.CONSISTENT));



  • delete
    • Deletes an item from the table. You must pass in an object instance of the mapped class.


  • query
    • Enables the querying of a table. You can query a table only if its primary key is made of both a hash and a range attribute. This method requires you to provide a hash attribute value and a query filter that is applied on the range attribute. A filter expression includes a condition and a value.


String forumName = "Amazon DynamoDB";
String forumSubject = "DynamoDB Thread 1";
String hashKey = forumName + "#" + forumSubject;

long twoWeeksAgoMilli = (new Date()).getTime() - (14L*24L*60L*60L*1000L);
Date twoWeeksAgo = new Date();
twoWeeksAgo.setTime(twoWeeksAgoMilli);
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
String twoWeeksAgoStr = df.format(twoWeeksAgo);


Condition rangeKeyCondition = new Condition()
   .withComparisonOperator(ComparisonOperator.GT.toString())
   .withAttributeValueList(new AttributeValue().withS(twoWeeksAgoStr.toString()));

Reply replyKey = new Reply();
replyKey.setId(hashKey);

DynamoDBQueryExpression<Reply> queryExpression = new DynamoDBQueryExpression<Reply>()
   .withHashKeyValues(replyKey)
   .withRangeKeyCondition("ReplyDateTime", rangeKeyCondition);

List<Reply> latestReplies = mapper.query(Reply.class, queryExpression);


The query returns a collection of Reply objects.


  • scan
    • Scans an entire table. You can specify optional condition filters items based on one or more Condition instances, and you can specify a filter expression for any item attributes.


DynamoDBScanExpression scanExpression = new DynamoDBScanExpression();
       
Map<String, Condition> scanFilter = new HashMap<String, Condition>();
Condition scanCondition = new Condition()
   .withComparisonOperator(ComparisonOperator.EQ.toString())
   .withAttributeValueList(new AttributeValue().withN("0"));

scanFilter.put("Answered", scanCondition);
       
scanExpression.setScanFilter(scanFilter);

List<Thread> unansweredThreads = mapper.scan(Thread.class, scanExpression);


  • The scan method returns a "lazy-loaded" collection. It initially returns only one page of results, and then makes a service call for the next page if needed. To obtain all the matching items, you only need to iterate over the unansweredThreads collection.


  • batchDelete
    • Deletes objects from one or more tables using one or more calls to the AmazonDynamoDB.batchWriteItem method. This method does not provide transaction guarantees.
Book book1 = mapper.load(Book.class, 901);
Book book2 = mapper.load(Book.class, 902);
mapper.batchDelete(Arrays.asList(book1, book2));



  • batchSave
    • Saves objects to one or more tables using one or more calls to the AmazonDynamoDB.batchWriteItem method. This method does not provide transaction guarantees.


Book book1 = new Book();
book1.id = 901;
book1.productCategory = "Book";
book1.title = "Book 901 Title";

Book book2 = new Book();
book2.id = 902;
book2.productCategory = "Book";
book2.title = "Book 902 Title";

mapper.batchSave(Arrays.asList(book1, book2));



  • batchWrite
    • Saves objects to and deletes objects from one or more tables using one or more calls to the AmazonDynamoDB.batchWriteItem method. This method does not provide transaction guarantees or support versioning (conditional puts or deletes).


// Create a Forum item to save
Forum forumItem = new Forum();
forumItem.name = "Test BatchWrite Forum";
       
// Create a Thread item to save
Thread threadItem = new Thread();
threadItem.forumName = "AmazonDynamoDB";
threadItem.subject = "My sample question";
       
// Load a ProductCatalog item to delete
Book book3 = mapper.load(Book.class, 903);
       
List<Object> objectsToWrite = Arrays.asList(forumItem, threadItem);
List<Book> objectsToDelete = Arrays.asList(book3);
      
mapper.batchWrite(objectsToWrite, objectsToDelete);



  • count
    • Evaluates the specified scan expression and returns the count of matching items. No item data is returned.


  • marshallIntoObject
    • Utility method to transform a result from the low-level API into a domain object.



Supported Data Types




Amazon DynamoDB supports the following primitive data types and primitive wrapper classes.
  • String
  • Boolean, boolean
  • Byte, byte
  • Date (as ISO8601 millisecond-precision string, shifted to UTC)
  • Calendar (as ISO8601 millisecond-precision string, shifted to UTC)
  • Long, long
  • Integer, int
  • Double, double
  • Float, float
  • BigDecimal
  • BigInteger
Amazon DynamoDB supports the Java Set collection types. If your mapped collection property is not a Set, then an exception is thrown.
The following table summarizes how the preceding Java types map to the Amazon DynamoDB types.
Java type
Amazon DynamoDB type
All number types
N (number type)
Strings
S (string type)
boolean
N (number type), 0 or 1.
ByteBuffer
B (binary type)
Date
S (string type). The Date values are stored as ISO-8601 formatted strings.
Set collection types
SS (string set) type, NS (number set) type, or BS (binary set) type.



Java Example : CRUD Operations


CatalogItem.java


import java.util.Set;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable;
@DynamoDBTable(tableName="ProductCatalog")
public class CatalogItem {
    private Integer id;
    private String title;
    private String ISBN;
    private Set bookAuthors;
   
    @DynamoDBHashKey(attributeName="Id")
    public Integer getId() { return id; }
    public void setId(Integer id) { this.id = id; }
   
    @DynamoDBAttribute(attributeName="Title")
    public String getTitle() { return title; }    
    public void setTitle(String title) { this.title = title; }

    @DynamoDBAttribute(attributeName="ISBN")
    public String getISBN() { return ISBN; }    
    public void setISBN(String ISBN) { this.ISBN = ISBN;}
   
    @DynamoDBAttribute(attributeName = "Authors")
    public Set getBookAuthors() { return bookAuthors; }    
    public void setBookAuthors(Set bookAuthors) { this.bookAuthors = bookAuthors; }

    @Override
    public String toString() {
       return "Book [ISBN=" + ISBN + ", bookAuthors=" + bookAuthors
       + ", id=" + id + ", title=" + title + "]";            
    }
}
ObjectPersistenceCRUDExample.java


import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Random;

import com.amazonaws.AmazonClientException;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.ClasspathPropertiesFileCredentialsProvider;
import com.amazonaws.regions.Region;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperConfig;
import com.amazonaws.services.dynamodbv2.model.AttributeDefinition;
import com.amazonaws.services.dynamodbv2.model.AttributeValue;
import com.amazonaws.services.dynamodbv2.model.ComparisonOperator;
import com.amazonaws.services.dynamodbv2.model.Condition;
import com.amazonaws.services.dynamodbv2.model.CreateTableRequest;
import com.amazonaws.services.dynamodbv2.model.DescribeTableRequest;
import com.amazonaws.services.dynamodbv2.model.KeySchemaElement;
import com.amazonaws.services.dynamodbv2.model.KeyType;
import com.amazonaws.services.dynamodbv2.model.ProvisionedThroughput;
import com.amazonaws.services.dynamodbv2.model.ScalarAttributeType;
import com.amazonaws.services.dynamodbv2.model.ScanRequest;
import com.amazonaws.services.dynamodbv2.model.ScanResult;
import com.amazonaws.services.dynamodbv2.model.TableDescription;
import com.amazonaws.services.dynamodbv2.model.TableStatus;

public class ObjectPersistenceCRUDExample {
 static AmazonDynamoDBClient client;
 private DynamoDBMapper mapper;
 private static int PRODUCT_ID;

 public static void main(String[] args) throws IOException {
  ObjectPersistenceCRUDExample demo = new ObjectPersistenceCRUDExample();
  demo.init();
  demo.createTable("ProductCatalog");
  for (int i = 0; i < 100; i++) {
   System.out.println(i);
   PRODUCT_ID = PRODUCT_ID + i;
   demo.insert();
  }
  demo.getAllRows();
  CatalogItem itemRetrieved = demo.load(PRODUCT_ID);
  demo.update(itemRetrieved);
  CatalogItem updatedItem = demo.load(PRODUCT_ID);
  demo.delete(updatedItem);
  demo.load(updatedItem.getId());
  System.out.println("Example complete!");
 }

 private void init() {
  PRODUCT_ID = new Random().nextInt(1000);
  AWSCredentials credentials = new ClasspathPropertiesFileCredentialsProvider()
    .getCredentials();
  client = new AmazonDynamoDBClient(credentials);
  Region usWest2 = Region.getRegion(Regions.US_WEST_2);
  client.setRegion(usWest2);
  mapper = new DynamoDBMapper(client);
 }

 private void createTable(String tableName) {
  try {
   CreateTableRequest createTableRequest = new CreateTableRequest()
     .withTableName(tableName);
   createTableRequest.withKeySchema(new KeySchemaElement()
     .withAttributeName("Id").withKeyType(KeyType.HASH));
   createTableRequest
     .withAttributeDefinitions(new AttributeDefinition()
       .withAttributeName("Id").withAttributeType(
         ScalarAttributeType.N));
   createTableRequest
     .withProvisionedThroughput(new ProvisionedThroughput()
       .withReadCapacityUnits(10L).withWriteCapacityUnits(
         10L));
   TableDescription createdTableDescription = client.createTable(
     createTableRequest).getTableDescription();
   System.out.println("Created Table: " + createdTableDescription);
   // Wait for it to become active
   waitForTableToBecomeAvailable(tableName);
  } catch (AmazonServiceException e) {
   e.printStackTrace();
  } catch (AmazonClientException e) {
   e.printStackTrace();
  }
 }

 private void waitForTableToBecomeAvailable(String tableName) {
  System.out.println("Waiting for " + tableName + " to become ACTIVE...");
  long startTime = System.currentTimeMillis();
  long endTime = startTime + (10 * 60 * 1000);
  while (System.currentTimeMillis() < endTime) {
   try {
    Thread.sleep(1000 * 20);
   } catch (Exception e) {
   }
   try {
    DescribeTableRequest request = new DescribeTableRequest()
      .withTableName(tableName);
    TableDescription tableDescription = client.describeTable(
      request).getTable();
    String tableStatus = tableDescription.getTableStatus();
    System.out.println("  - current state: " + tableStatus);
    if (tableStatus.equals(TableStatus.ACTIVE.toString()))
     return;
   } catch (AmazonServiceException ase) {
    if (ase.getErrorCode().equalsIgnoreCase(
      "ResourceNotFoundException") == false)
     throw ase;
   }
  }
  throw new RuntimeException("Table " + tableName + " never went active");
 }

 private void insert() {
  CatalogItem item = new CatalogItem();
  item.setId(PRODUCT_ID);
  item.setTitle("Book PRODUCT_ID");
  item.setISBN("611-1111111111");
  item.setBookAuthors(new HashSet(Arrays.asList("Author1",
    "Author2")));
  // Save the item (book).
  mapper.save(item);
 }

 private void update(CatalogItem itemRetrieved) {
  itemRetrieved.setISBN("622-2222222222");
  itemRetrieved.setBookAuthors(new HashSet(Arrays.asList(
    "Author1", "Author3")));
  mapper.save(itemRetrieved);
  System.out.println("Item updated:");
  System.out.println(itemRetrieved);
 }

 private void delete(CatalogItem updatedItem) {
  // Delete the item.
  mapper.delete(updatedItem);
 }

 private CatalogItem load(int id) {
  // Retrieve the updated item.
  DynamoDBMapperConfig config = new DynamoDBMapperConfig(
    DynamoDBMapperConfig.ConsistentReads.CONSISTENT);
  CatalogItem updatedItem = mapper.load(CatalogItem.class, id, config);
  if (updatedItem == null) {
   System.out.println("Done - Sample item is deleted.");
  } else {
   System.out.println("Retrieved item:");
   System.out.println(updatedItem);
  }
  return updatedItem;
 }

 private void getAllRows() {
  ScanRequest scanRequest = new ScanRequest()
    .withTableName("ProductCatalog");
  scanRequest.setLimit(10);
  HashMap scanFilter = new HashMap();
  Condition condition = new Condition().withComparisonOperator(
    ComparisonOperator.EQ.toString()).withAttributeValueList(
    new AttributeValue().withS("611-1111111111"));
  scanFilter.put("ISBN", condition);
  Condition condition2 = new Condition().withComparisonOperator(
    ComparisonOperator.LE.toString()).withAttributeValueList(
    new AttributeValue().withN("1000"));
  scanFilter.put("Id", condition2);
  scanRequest.withScanFilter(scanFilter);
  try {
   System.out.println("Scan Request: " + scanRequest);
   ScanResult scanResponse = client.scan(scanRequest);
   for (Map item : scanResponse.getItems()) {
    System.out.println(item.get("Id").getN() + " , " +
    item.get("ISBN").getS() + " , " +
    item.get("Authors").getSS() + " , " +
    item.get("Title").getS());
   }
   System.out.println("Scan Response: " + scanResponse);
   System.out.println("Count: " + scanResponse.getCount());
   System.out.println("Scanned Count: "
     + scanResponse.getScannedCount());
   System.out.println("Items: " + scanResponse.getItems());
  } catch (AmazonServiceException e) {
   e.printStackTrace();
  } catch (AmazonClientException e) {
   e.printStackTrace();
  }
 }
}
Categories: , , , , ,

4 comments:

  1. Thanks, This post is useful to me.

    ReplyDelete
  2. Great post! What would be the implementation if you need to query by Title? e.g. findByTitle(String title) ?

    ReplyDelete
  3. Hi There,

    Thank you for making your blogs an embodiment of perfection and simplicity. You make everything so easy to follow.
    I have a system in which I did a login page with JavaScript to validate some fields. It works fine when I click on the send button, but I want the same thing in the enter button, do you know how can I add the validation function to the event of the enter button?
    Apart from coding constructs, design approach plays a significant role in application performance. Application Design is something which is very hard to change, once application is done.
    Excellent tutorials - very easy to understand with all the details. I hope you will continue to provide more such tutorials.

    Merci,
    Daniel

    ReplyDelete

Find me on Facebook! Follow me on Twitter!