derjust / spring-data-dynamodb

This module deals with enhanced support for a data access layer built on AWS DynamoDB.
https://derjust.github.io/spring-data-dynamodb/
Apache License 2.0
403 stars 141 forks source link

not supported; requires @DynamoDBTyped or @DynamoDBTypeConverted #258

Open ivyblossom opened 5 years ago

ivyblossom commented 5 years ago

Expected Behavior

The API should be able to persist an ArrayList that is nested within another entity when the wrapper class for this ArrayList has been annotated with @DynamoDBTypeConverted.

Actual Behavior

A runtime exception is thrown with this message: not supported; requires @DynamoDBTyped or @DynamoDBTypeConverted

Steps to Reproduce the Problem

  1. Followed https://github.com/derjust/spring-data-dynamodb/wiki/Use-Hash-Range-keys Domain class:
    
    @NoArgsConstructor
    @Data
    @DynamoDBTable(tableName = DynamoDbTablesConfiguration.JOURNAL_EVALUATION_TABLE)
    public class JournalEvaluationEntity {
    @Id
    private JournalEvaluationEntityKey id;

// Other fields that will be annotated with @DynamoDBAttribute

private ImpactEvaluationsEntity impactEvaluations;

@DynamoDBHashKey(attributeName = "journalId")
public String getJournalId() {
    return id != null ? id.getJournalId() : null;
}

public void setJournalId(String journalId) {
    if (id == null) {
        this.id = new JournalEvaluationEntityKey();
    }
    id.setJournalId(journalId);
}

@DynamoDBRangeKey(attributeName = "evaluationSequence")
public Integer getEvaluationSequence() {
    return id != null ? id.getEvaluationSequence() : null;
}

public void setEvaluationSequence(Integer evaluationSequence) {
    if (id == null) {
        id = new JournalEvaluationEntityKey();
    }
    id.setEvaluationSequence(evaluationSequence);
}

// Getter and setter methods of other fields annotated with @DynamoDBAttribute

@DynamoDBAttribute
public ImpactEvaluationsEntity getImpactEvaluations() {
    return impactEvaluations;
}

public void setImpactEvaluations(ImpactEvaluationsEntity impactEvaluations) {
    this.impactEvaluations = impactEvaluations;
}

}


Key class
```java
public class JournalEvaluationEntityKey implements Serializable {

    private static final long serialVersionUID = 1L;

    private String journalId;
    private Integer evaluationSequence;

    @DynamoDBHashKey
    public String getJournalId() {
        return journalId;
    }

    public void setJournalId(String journalId) {
        this.journalId = journalId;
    }

    @DynamoDBRangeKey
    public Integer getEvaluationSequence() {
        return evaluationSequence;
    }

    public void setEvaluationSequence(Integer evaluationSequence) {
        this.evaluationSequence = evaluationSequence;
    }

}

Repository Interface:

@EnableScan
public interface DynamoDBJournalEvaluationRepository extends CrudRepository<JournalEvaluationEntity, JournalEvaluationEntityKey> {

    List<JournalEvaluationEntity> findByJournalId(String journalId);

    Optional<JournalEvaluationEntity> findById(JournalEvaluationEntityKey id);

    // @Override
    Iterable<JournalEvaluationEntity> findAll();
}
  1. In the Domain class, there is one attribute like this:

    @DynamoDBAttribute
    public ImpactEvaluationsEntity getImpactEvaluations() {
        return impactEvaluations;
    }
    public void setImpactEvaluations(ImpactEvaluationsEntity impactEvaluations) {
        this.impactEvaluations = impactEvaluations;
    }
  2. This attribute is defined in another class which has a field with the @DynamoDBTypeConverted

    @DynamoDBDocument
    public class ImpactEvaluationsEntity extends ImpactEvaluations {
    @Override
    @DynamoDBTypeConverted(converter = ImpactEvaluationsListConverter.class)
    public ImpactEvaluationsList getImpactEvaluationsList() {
        return super.getImpactEvaluationsList();
    }
    @Override
    public String getTimestamp() {
        return super.getTimestamp();
    }
    @Override
    public String getDecision() {
        return super.getDecision();
    }
    }
  1. This is the Converter class:

    public class ImpactEvaluationsListConverter implements DynamoDBTypeConverter<String, ImpactEvaluationsList> {
    
    private final Logger log = LoggerFactory.getLogger(this.getClass());
    
    /****
     * Method name : convert
     * Descriptiom : ImpactEvaluationsList convert method will be called whenever getImpactEvaluationsList() is called in the QualityEvaluationEntity class
     * @param object
     * @return JSON String
     */
    
    @Override
    public String convert(ImpactEvaluationsList object) {
    
        // This method will convert ImpactEvaluations to JSON string object
    
        ImpactEvaluationsList impactEvaluationsList = (ImpactEvaluationsList) object;
        ObjectMapper objectMapper = new ObjectMapper();
        String jsonStr = null;
    
        try {
            jsonStr = objectMapper.writeValueAsString(impactEvaluationsList);
        } catch (JsonProcessingException e) {
            log.error(e.getMessage());
        }
    
      return jsonStr;
    }
    
    /****
     * Method name : UNconvert
     * Descriptiom : This method will convert JSON string to ImpactEvaluationsList
     * @param s
     * @return JSON String
     */
    
    @Override
    public ImpactEvaluationsList unconvert(String s) {
    
        // This method will convert JSON string object to ImpactEvaluationsList
        ObjectMapper objectMapper = new ObjectMapper();
        ImpactEvaluationsList impactEvaluationsList = new ImpactEvaluationsList();
        try {
            impactEvaluationsList = objectMapper.readValue(s, ImpactEvaluationsList.class);
        } catch (Exception e) {
            log.error(e.getMessage());
        }
    
        return impactEvaluationsList;
    }
    }
  2. There's another attribute that is defined in another class which has a field with the @DynamoDBTypeConverted. That gets converted on save and unconverted on retrieve with no issues. Its Converter class is similar to the one I provided above. However for the ImpactEvaluationsList, only the unconvert works, the convert seems to not be called at all during save.

Specifications

All those information are logged by org.socialsignin.spring.data.dynamodb.repository.support.DynamoDBRepositoryFactory on INFO level on startup. Or use java -version and mvn dependency:tree | grep -E 'spring|aws' to provide those version numbers.

ivyblossom commented 5 years ago

Seems like this is the solution to my issue: https://www.hellojava.com/a/67524.html which is really from https://stackoverflow.com/questions/55150630/aws-dynamodbmapper-save-method-keeps-throwing-dynamodbmappingexception-not-sup

This has a side effect of persisting the composite key into another field of the table: “id”: “{\“journalId\“:\“91604760023\“,\“evaluationSequence\“:1}“. Redundant data but doesn’t really do any harm.