Kotlin操作MongoDB查询Author报Jackson无setter属性set调用异常
MongoDB 数组字段追加操作报setterless属性实例化异常
问题场景
authors为MongoDB中使用的集合,结构定义如下:
authors = [ { name: "Author1", books: [ {title:"Book1"}, {title:"Book2"} ] } ]
需求为向指定作者的books数组中追加新的book对象,追加前需要先根据作者名称校验对应记录是否存在,因此编写了findOneByAuthorname方法。调用该方法时抛出如下异常:
Unhandled: POST - /v1/api/addBooks. Exception class com.fasterxml.jackson.databind.exc.ValueInstantiationException: Cannot construct instance of `com.sample.models.schema.Author`, problem: Should never call `set()` on setterless property ('books')
使用Postman发送POST请求时传入的参数为{title: "book3"},原有业务代码如下:
@kotlinx.serialization.Serializable data class Author ( val name: String , val books : MutableList<Bookitem>) @kotlinx.serialization.Serializable data class Bookitem (val title :String) private var collection : CoroutineCollection<Author> = MongoConfig.getDatabase().getCollection("authors") suspend fun findOneByAuthorname(Name:String) : Author { collection.findOne(Author::name eq Name) } suspend fun addBooks(Name:String,book:Bookitem) : Bookitem?{ collection.findOneandUpdate(Author::name eq Name, ,push(Author::books ,Bookitem(book))) }
已确认排查线索:完全移除books数据类相关定义后,find方法可以正常运行。
问题根因
- 序列化适配缺失:KMongo默认使用Jackson做POJO反序列化,未加载Kotlin适配模块时,Jackson不会通过类的主构造器实例化对象,而是尝试寻找无参构造+字段setter注入查询结果。
Author类中books字段用val修饰,不会生成setter方法,Jackson尝试对该字段赋值时就会抛出无setter的实例化异常,这也是移除books字段后查询恢复正常的核心原因。同时类上标注了kotlinx.serialization的序列化注解但未配置KMongo使用对应序列化器,也会导致序列化逻辑混乱。 - 代码存在语法与逻辑错误:
addBooks方法中findOneAndUpdate的参数列表多了一个多余的逗号;push操作中重复对入参book做Bookitem构造包装,入参本身已经是目标类型;findOneByAuthorname方法缺少return关键字,查询结果无法正常返回。 - 传参格式不规范:Postman传入的JSON没有给key加双引号,属于非标准JSON格式,也可能触发序列化解析问题。
修复方案
- 补全序列化适配
最简处理方式是给Mongo客户端配置Jackson Kotlin模块,让Jackson可以正确识别Kotlin data class的主构造器,支持对val修饰的只读字段赋值,不需要将字段改为var。如果需要使用kotlinx.serialization做序列化,初始化KMongo时显式指定使用kotlinx序列化Mapper即可。 - 修正业务代码错误,参考实现如下:
import org.litote.kmongo.* import org.litote.kmongo.coroutine.CoroutineCollection @kotlinx.serialization.Serializable data class Author(val name: String, val books: MutableList<Bookitem>) @kotlinx.serialization.Serializable data class Bookitem(val title: String) // 初始化集合时显式声明泛型,避免序列化器推断错误 private val collection: CoroutineCollection<Author> = MongoConfig.getDatabase().getCollection<Author>("authors") suspend fun findOneByAuthorname(name: String): Author? { // 补充return关键字,返回值设为可空类型适配作者不存在的场景 return collection.findOne(Author::name eq name) } suspend fun addBooks(name: String, book: Bookitem) { collection.findOneAndUpdate( filter = Author::name eq name, // 移除多余逗号,直接传入已有的book对象,不需要重复构造 update = push(Author::books, book) ) }
- 规范接口传参
Postman发送请求时使用标准JSON格式,所有key用双引号包裹,正确参数格式为:{"title": "book3"}
内容的提问来源于stack exchange,提问作者Anandakrishnan
相关产品推荐
相关产品推荐

