如何将 Java 枚举与 Amazon DynamoDB 和 AWS SDK v2 结合使用?

How can I use Java Enums with Amazon DynamoDB and AWS SDK v2?

我正在尝试为 AWS 实施一个简单的 java 事件处理程序 lambda。它接收 sqs 事件并且应该对 dynamoDB table.

进行适当的更新

此 table 中的属性之一是具有 4 个已定义状态的状态字段;因此我想在 java 中使用枚举 class 并将其映射到此属性。

在 AWS SDK v1 下,我可以使用 @DynamoDBTypeConvertedEnum 注释。但它在 v2 中不再存在。相反,有一个 @DynamoDbConvertedBy() 接收转换器 class 引用。还有一个 EnumAttributeConverter class 应该可以很好地与它一起工作。

但由于某种原因,它不起作用。以下是我当前代码的片段:

@Data
@DynamoDbBean
@NoArgsConstructor
public class Task{

@Getter(onMethod_ = {@DynamoDbPartitionKey})  
    String id; 

...

@Getter(onMethod_ = {@DynamoDbConvertedBy(EnumAttributeConverter.class)})
    ExportTaskStatus status;
}

枚举如下所示:

@RequiredArgsConstructor
public enum TaskStatus {
    @JsonProperty("running") PROCESSING(1),
    @JsonProperty("succeeded") COMPLETED(2),
    @JsonProperty("cancelled") CANCELED(3),
    @JsonProperty("failed") FAILED(4);

    private final int order;
}

有了这个,我在启动应用程序时遇到以下异常:

Class 'class software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.EnumAttributeConverter' appears to have no default constructor thus cannot be used with the BeanTableSchema

我认为您的注释实际上可能是这里的问题所在。我会删除所有提及构造函数的注释,而是写出您自己的构造函数。对于 TaskTaskStatus.

尽管您提供的 documentation doesn't state it, the DynamoDbConvertedBy annotation requires any AttriuteConverter 包含一个无参默认构造函数

不幸的是你和我,无论谁写了很多内置 AttributeConverter classes 决定使用静态 create() 方法来实例化它们而不是构造函数(也许他们是单身人士吗?我不知道)。这意味着任何想要使用像 InstantAsStringAttributeConverter and EnumAttributeConverter 这样有用的无构造函数的 classes 的人都需要将它们包装在自定义包装器 classes 中,简单地模仿我们使用 create 实例化的转换器.对于像 InstantAsStringAttributeConverter 这样的非通用类型 class,这很容易。只需创建一个包装器 class 来模仿您使用 create() 新建的实例并引用它:

public class InstantAsStringAttributeConverterWithConstructor implements AttributeConverter<Instant> {
    private final static InstantAsStringAttributeConverter CONVERTER = InstantAsStringAttributeConverter.create();

    @Override
    public AttributeValue transformFrom(Instant instant) {
        return CONVERTER.transformFrom(instant);
    }

    @Override
    public Instant transformTo(AttributeValue attributeValue) {
        return CONVERTER.transformTo(attributeValue);
    }

    @Override
    public EnhancedType<Instant> type() {
        return CONVERTER.type();
    }

    @Override
    public AttributeValueType attributeValueType() {
        return CONVERTER.attributeValueType();
    }
}

然后更新注释以指向 class 而不是实际的基础库 class。

但是等等,EnumAttributeConverter 是一个泛型 class,这意味着您需要更进一步。首先,您需要创建一个包装官方版本的转换器版本,但依赖于接受类型而不是静态实例化的构造函数:

import software.amazon.awssdk.enhanced.dynamodb.AttributeConverter;
import software.amazon.awssdk.enhanced.dynamodb.AttributeValueType;
import software.amazon.awssdk.enhanced.dynamodb.EnhancedType;
import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.EnumAttributeConverter;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;

public class EnumAttributeConverterWithConstructor<T extends Enum<T>> implements AttributeConverter<T> {
    private final EnumAttributeConverter<T> converter;

    public CustomEnumAttributeConverter(final Class<T> enumClass) {
        this.converter = EnumAttributeConverter.create(enumClass);
    }

    @Override
    public AttributeValue transformFrom(T t) {
        return this.converter.transformFrom(t);
    }

    @Override
    public T transformTo(AttributeValue attributeValue) {
        return this.converter.transformTo(attributeValue);
    }

    @Override
    public EnhancedType<T> type() {
        return this.converter.type();
    }

    @Override
    public AttributeValueType attributeValueType() {
        return this.converter.attributeValueType();
    }
}

但这只让我们完成了一半——现在我们需要为我们想要转换的每个枚举类型生成一个版本,子class是我们的自定义 class:

public class ExportTaskStatusAttributeConverter extends EnumAttributeConverterWithConstructor<ExportTaskStatus> {
    public ExportTaskStatusAttributeConverter() {
        super(ExportTaskStatus.class);
    }
}
@DynamoDbConvertedBy(ExportTaskStatusAttributeConverter.class)
public ExportTaskStatus getStatus() { return this.status; }

或者 Lombok-y 方式:

@Getter(onMethod_ = {@DynamoDbConvertedBy(ExportTaskStatusAttributeConverter.class)})
ExportTaskStatus status;

这很痛苦。通过在 AWS SDK 中进行一些调整和一些反思就可以解决这个问题,但这就是我们现在所处的位置。

解决方案基于答案:

public class TaskStatusConverter implements AttributeConverter<TaskStatus> {
    @Delegate
    private final EnumAttributeConverter<TaskStatus> converter;

    public TaskStatusConverter() {
        converter = EnumAttributeConverter.create(TaskStatus.class);
    }
}

任务状态属性如下所示:

@Getter(onMethod_ = {@DynamoDbConvertedBy(TaskStatusConverter.class)})
TaskStatus status;

对于来到这里的任何其他人,在我看来,只要从枚举中删除注释就可以正常工作,即 SDK 隐式应用提供的属性转换器。这个Github issue里面也提到了。我自己的class是这样的(这里Brand是一个枚举),在获取物品的时候枚举转换没有任何问题。

@Value
@Builder(toBuilder = true)
@DynamoDbImmutable(builder = User.UserBuilder.class)
public class User {

    @Getter(onMethod = @__({@DynamoDbPartitionKey}))
    String id;

    Brand brand;
    ...
}

dynamodb-enhanced SDK 开箱即用。

当您声明 @DynamoDbBean 时,DefaultAttributeConverterProvider 会提供一长串在 java 类型之间转换属性的可能方法,包括 EnumAttributeConverter,如果 type.rawClass().isEnum() 是真的。所以你不用担心。

如果您想增加转换器的数量,您需要添加 converterProviders 注释参数,并声明默认的(或省略它)以及您想要的任何其他提供程序。

示例: @DynamoDbBean(converterProviders = { DefaultAttributeConverterProvider.class, MyCustomAttributeConverterProvider.class });