Kotlin中AWS SDK v2的DynamoDB二级索引注解未被识别问题
问题场景
使用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)
问题原因
- 表名称不匹配:实体类定义的表名为
my-entity,但Repository中使用MyEntity::class.java.simpleName得到的是MyEntity,导致客户端操作的表与实际创建的表不一致,索引定义在正确表中但客户端找不到。 - Kotlin val属性的注解解析问题:AWS SDK v2的
TableSchema.fromBean在处理Kotlin data class的不可变属性(val)时,可能无法正确识别@DynamoDbSecondaryPartitionKey和@DynamoDbSecondarySortKey注解,导致索引元数据未被加载。 - 泛型类型错误:查询代码中泛型使用
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

