在 Kotlin 中为 DynamoDB 建模复杂类型

Modelling Complex Types for DynamoDB in Kotlin

我有一个 DynamoDB table,我需要 read/write。我正在尝试使用 Kotlin 创建一个从 DynamoDB 读取和写入的模型。但是当我 运行 dynamoDBMapper.scanPage(...) 时,我总是遇到 com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMappingException: MyModelDB[myMap]; could not unconvert attribute。有时 myMap 会变成 MyListOfMaps,但我猜这是因为迭代了 Map 的键。

我的代码如下:

@DynamoDBTable(tableName = "") // Non-issue, I am assigning the table name in the DynamoDBMapper
data class MyModelDB(

    @DynamoDBHashKey(attributeName = "id")
    var id: String,

    @DynamoDBAttribute(attributeName = "myMap")
    var myMap: MyMap,

    @DynamoDBAttribute(attributeName = "MyListOfMapItems")
    var myListOfMapItems: List<MyMapItem>,
) {
    constructor() : this(id = "", myMap = MyMap(), myListOfMaps = mutableListOf())

    @DynamoDBDocument
    class MyMap {
        @get:DynamoDBAttribute(attributeName = "myMapAttr")
        var myMapAttr: MyMapAttr = MyMapAttr()

        @DynamoDBDocument
        class MyMapAttr {
            @get:DynamoDBAttribute(attributeName = "stringValue")
            var stringValue: String = ""
        }
    }

    @DynamoDBDocument
    class MyMapItem {
        @get:DynamoDBAttribute(attributeName = "myMapItemAttr")
        var myMapItemAttr: String = ""
    }
}

我正在使用 com.amazonaws:aws-java-sdk-dynamodb:1.11.500 包,我的 dynamoDBMapper 是用 DynamoDBMapperConfig.Builder().build() 初始化的(以及一些其他配置)。

我的问题是我做错了什么,为什么?我还看到一些 Java 实现使用 DynamoDBTypeConverter。它更好吗?我应该改用它吗?

如有任何示例,我们将不胜感激!

这里有几条评论。首先,您没有使用 AWS SDK for Kotlin。您正在使用另一个 SDK 并简单地编写 Kotlin 代码。使用此 SDK,您无法获得 Kotlin 的全部优势,例如对协程的支持。

适用于 Kotlin 的 AWS SDK(确实提供对 Kotlin 功能的全面支持)本周刚刚作为开发预览版发布。请参阅开发指南:

Setting up the AWS SDK for Kotlin

但是这个SDK目前不支持这个映射。要使用 AWS SDK for Kotlin 将项目放入 Amazon DynamoDB table,您需要使用:

mutableMapOf

Full example here.

要将 Java 对象映射到 DynamoDB table,您应该考虑使用 AWS SDK 的一部分 DynamoDbEnhancedClient for Java V2。请参阅 适用于 Java V2 开发人员指南的 AWS SDK:

Mapping items in DynamoDB tables

您可以在 AWS Github repo.

中找到使用 增强客户端 的其他示例

好的,多亏了一些帮助,我终于成功了。在更好地理解之后,我稍微编辑了这个问题。这是我的数据 class 最终的结果。对于 Java 用户,Kotlin 编译为 Java,因此如果您能弄清楚转换的工作原理,您的使用思路应该也是一样的。

data class MyModelDB(

    @DynamoDBHashKey(attributeName = "id")
    var id: String = "",

    @DynamoDBAttribute(attributeName = "myMap")
    @DynamoDBTypeConverted(converter = MapConverter::class)
    var myMap: Map<String, AttributeValue> = mutableMapOf(),

    @DynamoDBAttribute(attributeName = "myList")
    @DynamoDBTypeConverted(converter = ListConverter::class)
    var myList: List<AttributeItem> = mutableListOf(),
) {
    constructor() : this(id = "", myMap = MyMap(), myList = mutableListOf())
}

class MapConverter : DynamoDBTypeConverter<AttributeValue, Map<String,AttributeValue>> {
    override fun convert(map: Map<String,AttributeValue>>): AttributeValue {
        return AttributeValue().withM(map)
    }

    override fun unconvert(itemMap: AttributeValue?): Map<String,AttributeValue>>? {
        return itemMap?.m
    }

}

class ListConverter : DynamoDBTypeConverter<AttributeValue, List<AttributeValue>> {
    override fun convert(list: List<AttributeValue>): AttributeValue {
        return AttributeValue().withL(list)
    }

    override fun unconvert(itemList: AttributeValue?): List<AttributeValue>? {
        return itemList?.l
    }

}

这至少让我可以使用我的自定义转换器从 DynamoDB 中获取我的数据。我将继续定义一个单独的数据容器 class 以供在我自己的应用程序中使用,并且我创建了一个方法来在这两个数据对象之间进行序列化和反序列化。这更像是您希望如何处理数据的偏好,但对我来说就是这样。

// For reading and writing to DynamoDB
class MyModelDB {
    ...
    fun toMyModel(): MyModel {
        ...
    }
}

// For use in my application
class MyModel {
    var id: String = ""
    var myMap: CustomObject = CustomObject()
    var myList<CustomObject2> = mutableListOf()

    fun toMyModelDB():MyModelDB {
        ...
    }
}

最后,我们来实现 2 个 toMyModel.*() 方法。让我们从输入开始,这是我的专栏的样子:

我的地图:

{
    "key1": {
        "M": {
            "subKey1": {
                "S": "some"
            },
            "subKey2": {
                "S": "string"
            }
        }
    },
    "key2": {
        "M": {
            "subKey1": {
                "S": "other"
            },
            "subKey2": {
                "S": "string"
            }
        }
    }
}

我的列表:

[
    {
        "M": {
            "key1": {
                "S": "some"
            },
            "key2": {
                "S": "string"
            }
        }
    },
    {
        "M": {
            "key1": {
                "S": "some string"
            },
            "key3": {
                "M": {
                    "key4": {
                        "S": "some string"
                    }
                }
            }
        }
    }
]

技巧是使用com.amazonaws.services.dynamodbv2.model.AttributeValue 转换JSON 中的每个字段。所以如果我想在 myMapkey1 字段中访问 subKey2 的值,我会这样做:

myModelDB.myMap["key1"]
        ?.m // Null check and get the value of key1, a map
        ?.get("subKey2") // Get the AttributeValue associated with the "subKey2" key
        ?.s // Get the value of "subKey2" as a String

同样适用于myList

myModelDB.myList.foreach {
        it?.m // Null check and get the map at the current index
        ?.get("key1") // Get the AttributeValue associated with the "key1"
        ...
}

编辑:怀疑这会是一个很大的问题,但我也将我的 DynamoDB 依赖项更新为 com.amazonaws:aws-java-sdk-dynamodb:1.12.126