5

I am trying to create a dynamoDBMapper annotation for the below case.

I have EmployeeLevelTrail which is a class of a Employee level record

@DynamoDBTable(tableName = TABLE_NAME)
public class EmployeeData {
    public final static String TABLE_NAME = “EmployeeDataRecord”;

    @DynamoDBAttribute(attributeName = “employeeID”)
    public String EmployeeID;

    @DynamoDBAttribute(attributeName = “EmployeeLevelDataRecords”)
    @DynamoDBTyped(DynamoDBMapperFieldModel.DynamoDBAttributeType.M)
    public EmployeeLevelTrail employeeLevelTrail

}


public class EmployeeLevelTrail {

    public final static String DDB_ATTR_EMPLOYEE_LEVEL_TRAIL = “employeeLevelTrail”;

    @DynamoDBAttribute(attributeName = DDB_ATTR_EMPLOYEE_LEVEL_TRAIL)
    private List<EmployeeLevelRecord> thisEmployeeLevelRecords;

    public void appendEmployeeLevelRecord(@NonNull EmployeeLevelRecord employeeLevelRecord) {

        thisEmployeeLevelRecords.add(employeeLevelRecord);

    }
}


public class EmployeeLevelRecord {

    private String Level;

    private String Manager;

    private Instant timeOfEvent;

}

This is my annotation but it is not correct as I am not able to save my DynamoDB data

Matthew Pope
  • 7,212
  • 1
  • 28
  • 49
constantLeaner
  • 157
  • 1
  • 4
  • 12

3 Answers3

11

To mark another class as part of the data model for DynamoDBMapper, you can annotate it with @DynamoDBDocument, which tells DynamoDBMapper that a class can be serialized as a DynamoDB document.

For classes that you aren't writing (ie. from the Java library or an external library) or if you need more control over how to serialize one of your own classes, you can use @DynamoDBTypeConverted, which allows you to map arbitrary data by providing your own DynamoDBTypeConverter implementation to convert from any Java object to any supported DynamoDB type.

Using your sample code, I've added in the appropriate @DynamoDBDocument and @DynamoDBTypeConverted annotations, as well as a sample implementation of a DynamoDBTypeConverter that converts an Instant to a ISO-8601 String. If employeeId is the hash key of your table, make sure you also add the @DynamoDBHashKey annotation to employeeId.

@DynamoDBTable(tableName = TABLE_NAME)
public class EmployeeData {
    public final static String TABLE_NAME = “EmployeeDataRecord”;

    @DynamoDBAttribute(attributeName = “employeeID”)
    public String EmployeeID;

    @DynamoDBAttribute(attributeName = “EmployeeLevelDataRecords”)
    public EmployeeLevelTrail employeeLevelTrail

}

@DynamoDBDocument
public class EmployeeLevelTrail {

    public final static String DDB_ATTR_EMPLOYEE_LEVEL_TRAIL = “employeeLevelTrail”;

    @DynamoDBAttribute(attributeName = DDB_ATTR_EMPLOYEE_LEVEL_TRAIL)
    private List<EmployeeLevelRecord> thisEmployeeLevelRecords;

    public void appendEmployeeLevelRecord(@NonNull EmployeeLevelRecord employeeLevelRecord) {

        thisEmployeeLevelRecords.add(employeeLevelRecord);

    }
}

@DynamoDBDocument
public class EmployeeLevelRecord {

    private String Level;

    private String Manager;

    @DynamoDBTypeConverted(converter = InstantToStringTypeConverter.class)
    private Instant timeOfEvent;

}

public class InstantToStringTypeConverter implements DynamoDBTypeConverter<String, Instant> {

    @Override
    public String convert(final Instant instant) {
        return instant.toString();
    }

    @Override
    public Instant unconvert(final String string) {
        return Instant.parse(string);
    }
}
Matthew Pope
  • 7,212
  • 1
  • 28
  • 49
  • 1
    This helps. Thanks. I just added @DynamoDBTypeConvertedJson instead of only using the below `@DynamoDBAttribute(attributeName = “EmployeeLevelDataRecords”) public EmployeeLevelTrail employeeLevelTrail ` – constantLeaner Mar 04 '19 at 22:55
  • Haha, I forgot about `@DynamoDBTypeConvertedJson` when I was writing my answer. If it works for you, make an answer explaining it and accept it so that others can see how you solved it. – Matthew Pope Mar 05 '19 at 01:36
  • The @DynamoDBTypeConvertedJson annotation does not help me in update as I get stuck in the below error com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMappingException: EmployeeData[EmployeeLevelTrail]; could not unconvert attribute Looks like. I will have to write a custom convertor for this case – constantLeaner Mar 05 '19 at 17:13
  • Can you provide the whole stack trace? I wouldn’t be surprised if the original cause is that the `Instant` can’t be deserialized. If so, that’s because the Jackson ObjectMapper (which DynamoDBTypeConvertedJson uses) does not by default support the Java 8 time library. The answer I gave will not have the same problem. – Matthew Pope Mar 05 '19 at 17:42
2

I reached to the solution using the below code. With only @DynamoDBTypeConvertedJSON, I was able to create an entry in the table but not update it as I was running into mapping Dynamo DB mapping exception.

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@DynamoDBTable(tableName = TABLE_NAME)
public class EmployeeData {

    public final static String TABLE_NAME = "EmployeeData";

    public final static String DDB_ATTR_ID = "Id";

    public final static String DDB_ATTR_EMPLOYEE_LEVEL_RECORD_TRAIL = "EmployeeLevelRecordTrail";

    @DynamoDBHashKey(attributeName = DDB_ATTR_ID)
    @DynamoDBAttribute(attributeName = DDB_ATTR_ID)
    private String id;

    @DynamoDBAttribute(attributeName = DDB_ATTR_EMPLOYEE_LEVEL_RECORD_TRAIL)
    @DynamoDBTypeConverted(converter = EmployeeLevelRecordTrailConverter.class)
    private  EmployeeLevelRecordTrail  employeeLevelRecordTrail;

    @DynamoDBAttribute(attributeName = DDB_ATTR_CREATED_TIME)
    @DynamoDBTypeConverted(converter = InstantConverter.class)
    private Instant joiningTime;
}



@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class EmployeeLevelRecordTrail {

    private List<EmployeeLevelRecord> thisEmployeeLevelRecords;

    public void appendEmployeeLevelRecord(@NonNull EmployeeLevelRecord employeeLevelRecord) {

        thisEmployeeLevelRecords.add(employeeLevelRecord);

    }
}


@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@DynamoDBDocument
public class EmployeeLevelRecord {

    private String Level;

    private String Manager;

    @DynamoDBTypeConverted(converter = InstantConverter.class)
    private Instant timeOfEvent;

}

public class EmployeeLevelRecordTrailConverter implements
        DynamoDBTypeConverter<List<EmployeeLevelRecord>, EmployeeLevelRecordTrail> {

    @Override
    public List<EmployeeLevelRecord> convert(EmployeeLevelRecordTrail employeeLevelRecordTrail) {
        return employeeLevelRecordTrail.getEmployeeLevelRecord();
    }

    @Override
    public EmployeeLevelRecordTrail unconvert(List<EmployeeLevelRecord> thisEmployeeLevelRecords) {
        return new EmployeeLevelRecordTrail(thisEmployeeLevelRecords);
    }
}

public class InstantConverter implements DynamoDBTypeConverter<String, Instant> {

    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ISO_INSTANT;

    @Override
    public String convert(Instant instant) {
        return instant == null ? null : DATE_TIME_FORMATTER.format(instant);
    }

    @Override
    public Instant unconvert(String str) {
        return str == null ? null : Instant.from(DATE_TIME_FORMATTER.parse(str));
    }
}

Reading the documentation on how to do mapping helped.

constantLeaner
  • 157
  • 1
  • 4
  • 12
0

You need to use the DynamoDBTypeConverted annotation. You will also need to create a converter class which defines how to get from your Java object to a DynamoDB primitive (probably a string).

https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBMapper.ArbitraryDataMapping.html

You can also check out an example from this answer:

DynamoDB JsonMarshaller cannot Deserialize List of Object

F_SO_K
  • 13,640
  • 5
  • 54
  • 83