You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin中AWS SDK v2的DynamoDB二级索引注解未被识别问题

AWS DynamoDB Enhanced Async Client 无法识别二级索引的问题解决

问题场景

使用Spring Boot + AWS Java SDK v2的DynamoDbEnhancedAsyncClient,结合Testcontainers和LocalStack运行DynamoDB,Kotlin代码实现。主分区键查询功能正常,但查询全局二级索引(GSI)时抛出异常,实体类已通过注解定义该索引,但客户端未识别。

实体类代码

@DynamoDbBean
data class MyEntity (
    @get:DynamoDbPartitionKey
    @get:DynamoDbAttribute("entityId")
    var entityId: String = "",

    @get:DynamoDbAttribute("customerId")
    @get:DynamoDbSecondaryPartitionKey(indexNames = ["CustomerIdIndex"])
    val customerId: String = "",

    @get:DynamoDbSecondarySortKey(indexNames = ["CustomerIdIndex"])
    @get:DynamoDbAttribute("createdAt")
    val createdAt: LocalDateTime = LocalDateTime.now()
) {
    companion object {
        val tableName: String = "my-entity"
    }
}

查询实现代码

@Repository
class MyEntityRepository(client: DynamoDbEnhancedAsyncClient) {
    companion object {
        private val tableSchema = TableSchema.fromBean(MyEntity::class.java)
        private val tableName = MyEntity::class.java.simpleName
    }

    private val table = client.table(tableName, tableSchema)

    override suspend fun findByCustomerId(customerId: String): Flow<MyEntity> {
        val conditional: QueryConditional = QueryConditional.keyEqualTo(
            Key.builder()
                .partitionValue(customerId)
                .build()
        )

        val request = QueryEnhancedRequest.builder()
            .queryConditional(conditional)
            .scanIndexForward(false)
            .build()

        val queryPublisher: SdkPublisher<Page<PaymentMethod>> = table
            .index("CustomerIdIndex")
            .query(request)
        // 此处省略Flow转换逻辑
    }
}

错误信息

Attempt to execute an operation that requires a secondary index without defining the index attributes in the table metadata. Index name: CustomerIdIndex
java.lang.IllegalArgumentException: Attempt to execute an operation that requires a secondary index without defining the index attributes in the table metadata. Index name: CustomerIdIndex
at software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableMetadata.getIndex(StaticTableMetadata.java:150)

问题原因

  1. 表名称不匹配:实体类定义的表名为my-entity,但Repository中使用MyEntity::class.java.simpleName得到的是MyEntity,导致客户端操作的表与实际创建的表不一致,索引定义在正确表中但客户端找不到。
  2. Kotlin val属性的注解解析问题:AWS SDK v2的TableSchema.fromBean在处理Kotlin data class的不可变属性(val)时,可能无法正确识别@DynamoDbSecondaryPartitionKey和@DynamoDbSecondarySortKey注解,导致索引元数据未被加载。
  3. 泛型类型错误:查询代码中泛型使用Page<PaymentMethod>而非Page<MyEntity>,虽不直接导致索引问题,但会引发后续类型错误。

解决办法

1. 统一表名称

修改Repository中的表名称引用,使用实体类中定义的常量:

companion object {
    private val tableSchema = TableSchema.fromBean(MyEntity::class.java)
    private val tableName = MyEntity.tableName // 使用实体类定义的表名
}

2. 修复注解解析问题

有两种方式可选:

方式一:将val属性改为var

将实体类中的不可变属性改为可变属性,确保SDK能正确解析注解:

@get:DynamoDbAttribute("customerId")
@get:DynamoDbSecondaryPartitionKey(indexNames = ["CustomerIdIndex"])
var customerId: String = "",

@get:DynamoDbSecondarySortKey(indexNames = ["CustomerIdIndex"])
@get:DynamoDbAttribute("createdAt")
var createdAt: LocalDateTime = LocalDateTime.now()

方式二:手动构建TableMetadata

显式指定索引元数据,不依赖注解自动解析:

private val tableSchema = TableSchema.fromBean(
    MyEntity::class.java,
    TableMetadata.builder()
        .addIndex(
            IndexMetadata.builder()
                .indexName("CustomerIdIndex")
                .addIndexPartitionKey(
                    IndexKey.builder()
                        .attributeName("customerId")
                        .build()
                )
                .addIndexSortKey(
                    IndexKey.builder()
                        .attributeName("createdAt")
                        .build()
                )
                .build()
        )
        .build()
)

3. 修正泛型类型

将查询代码中的泛型修正为对应实体类型:

val queryPublisher: SdkPublisher<Page<MyEntity>> = table
    .index("CustomerIdIndex")
    .query(request)

4. 确保表创建时包含索引

如果通过代码创建表,使用Enhanced Client的createTable方法,确保索引被正确生成:

suspend fun createTable() {
    table.createTable { 
        it.provisionedThroughput { pt -> 
            pt.readCapacityUnits(1)
               .writeCapacityUnits(1) 
        } 
    }
}

内容的提问来源于stack exchange,提问作者Wrench

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 20:35:14