Monday 24 October 2016

DynamoDB High Level API-DynamoDBMapper (Java)

DynamoDB provides a high level API DynamoDBMapper. This approach can reduce the amount of code comparing with low level APIs(putItem, getItemOutcome, updateItem, query and scan).

AWS has detailed document to explain how to use it, this article is focus on how to use it for custom object and list of custom object.

Before going to deeper, let's have a simple example first (From https://aws.amazon.com/blogs/developer/using-the-savebehavior-configuration-for-the-dynamodbmapper/ ).

Map<String, String> expressionAttributeNames = new HashMap<String, String>();
expressionAttributeNames.put("#P", "Price");

@DynamoDBTable(tableName="TestTable")
public class TestTableItem {

   private int key;
   private String modeledScalar;
   private Set<String> modeledSet;

   @DynamoDBHashKey(attributeName="key")
   public int getKey() { return key; }
   public void setKey(int key) { this.key = key; }

   @DynamoDBAttribute(attributeName="modeled_scalar")
   public String getModeledScalar() { return modeledScalar; }
   public void setModeledScalar(String modeledScalar) { this.modeledScalar = modeledScalar; }
    
   @DynamoDBAttribute(attributeName="modeled_set")
   public Set<String> getModeledSet() { return modeledSet; }
   public void setModeledSet(Set<String> modeledSet) { this.modeledSet = modeledSet; }

}


The approach is simple, you just need to put the annotation to specify the property methods is DynamoDBHashKey, DynamoDBRangeKey or DynamoDBAttribute. If you don't need to save some property into database, you should put annotation DynamoDBIngore on top of property methods.

One thing to remember is the naming of your property methods, you must use the standard name start with "get" and "set". Don't use other prefix, otherwise DynamoDB mapper cannot "reflect" your attribute name to your methods.

To store a custom object of complex class, you need to use DynamoDBTypeConverter(DynamoDBMarshaller is deprecated already).
For example, we have a complex class PhoneNumber
public class PhoneNumber {
    private String areaCode;
    private String exchange;
    private String subscriberLineIdentifier;
 
    public String getAreaCode() { return areaCode; }  
    public void setAreaCode(String areaCode) { this.areaCode = areaCode; }
 
    public String getExchange() { return exchange; }
    public void setExchange(String exchange) { this.exchange = exchange; }
 
    public String getSubscriberLineIdentifier() { return subscriberLineIdentifier; }  
    public void setSubscriberLineIdentifier(String subscriberLineIdentifier) { this.subscriberLineIdentifier = subscriberLineIdentifier; }    
}


And we add a custom object to the above example.

@DynamoDBTable(tableName="TestTable")
public class TestTableItem {

   private int key;
   private String modeledScalar;
   private Set<String> modeledSet;
   private PhoneNumber phoneNumber;

   @DynamoDBHashKey(attributeName="key")
   public int getKey() { return key; }
   public void setKey(int key) { this.key = key; }

   @DynamoDBAttribute(attributeName="modeled_scalar")
   public String getModeledScalar() { return modeledScalar; }
   public void setModeledScalar(String modeledScalar) { this.modeledScalar = modeledScalar; }
    
   @DynamoDBAttribute(attributeName="modeled_set")
   public Set<String> getModeledSet() { return modeledSet; }
   public void setModeledSet(Set<String> modeledSet) { this.modeledSet = modeledSet; }

    @DynamoDBTypeConverted(converter = PhoneNumberConverter.class)
    public PhoneNumber getPhoneNumber() { return phoneNumber; }  
    public void setPhoneNumber(PhoneNumber phoneNumber) { this.phoneNumber = phoneNumber; }

}


Instead of using standard annotation DynamoDBAttribute, now we need to use @DynamoDBTypeConverted(converter = PhoneNumberConverter.class), and PhoneNumberConverter here is a class implementing DynamoDBTypeConverter interface.

Here is how the interface looks like. It just a interface to convert the object to a string and convert it back.
public class PhoneNumberConverterimplements DynamoDBTypeConverter<String, PhoneNumber>
{
    @Override
    public String convert(PhoneNumber number) {
        return "(" + number.getAreaCode() + ") " + number.getExchange() + "-" + number.getSubscriberLineIdentifier();
    }

    @Override
    public PhoneNumber unconvert(String s) {
        String[] areaCodeAndNumber = s.split(" ");
        String areaCode = areaCodeAndNumber[0].substring(1,4);
        String[] exchangeAndSlid = areaCodeAndNumber[1].split("-");
        PhoneNumber number = new PhoneNumber();
        number.setAreaCode(areaCode);
        number.setExchange(exchangeAndSlid[0]);
        number.setSubscriberLineIdentifier(exchangeAndSlid[1]);
        return number;
    }  
}


Having the above basic knowledge, now let's see how to do it for a List of custom object.
For example, we have a list of phone numbers.
@DynamoDBTable(tableName="TestTable")
public class TestTableItem {

   private int key;
   private String modeledScalar;
   private Set<String> modeledSet;
   private List<PhoneNumber> phoneNumbers;
 
   ......

   @DynamoDBTypeConverted(converter = PhoneNumberConverter.class)
   public List<PhoneNumber> getPhoneNumbers() { return phoneNumbers; }  
   public void setPhoneNumbers(List<PhoneNumber> phoneNumbers) { this.phoneNumbers = phoneNumbers; }

}


What we need to do is to convert the list of object to string and convert it back.

public class PhoneNumberConverterimplements DynamoDBTypeConverter<String, PhoneNumber>
{
    private static final ObjectMapper mapper = new ObjectMapper();
    private static final ObjectWriter writer = mapper.writerWithType(new TypeReference<List<PhoneNumber>>(){});
    @Override
    public String convert(List<PhoneNumber> numbers) {
               try {
            return writer.writeValueAsString(numbers);
        } catch (JsonProcessingException e) {
            System.out.println(
                    "Unable to marshall the instance of " + numbers.getClass()
                    + "into a string");
            return null;
        }
    }

    @Override
    public List<PhoneNumber> unconvert(String s) {
        TypeReference<List<PhoneNumber>> type = new TypeReference<List<PhoneNumber>>() {};
        try {
            List<PhoneNumber> list = mapper.readValue(s, type);
            return list;
        } catch (Exception e) {
            System.out.println("Unable to unmarshall the string " + s
                             + "into " + s);
            return null;
        }
    }  
}


7 comments: