Kotlin:MongoDB和@DBRef返回参考-> Stackoverflow异常

时间:2019-06-11 11:06:22

标签: mongodb kotlin

让我们说我上了一堂Parent

class Parent(val id: String, val child: Child) {

    init {
        child.parent = this
    }
}

还有一个Child类,它带有对父级的反向引用!

class Child {

    @DBRef
    @JsonIgnore
    lateinit var parent: Parent
}

但是,当我想使用以下方式保存并检索父项时

@Autowired
lateinit var mongo: MongoOperations

val parent = Parent("1", Child())
mongo.save(parent)
mongo.findById<Parent>("1")

我在StackOverflow Exception通话中得到mongo.findById

在“异常”堆栈中,它清楚地表明MongoDB在解决DBRef方面存在问题

at org.springframework.data.mongodb.core.convert.DefaultDbRefResolver.resolveDbRef(DefaultDbRefResolver.java:103)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.readAssociation(MappingMongoConverter.java:400)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.readProperties(MappingMongoConverter.java:354)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.populateProperties(MappingMongoConverter.java:295)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:275)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:245)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.readValue(MappingMongoConverter.java:1491)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$MongoDbPropertyValueProvider.getPropertyValue(MappingMongoConverter.java:1389)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$AssociationAwareMongoDbPropertyValueProvider.getPropertyValue(MappingMongoConverter.java:1438)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$AssociationAwareMongoDbPropertyValueProvider.getPropertyValue(MappingMongoConverter.java:1401)
at org.springframework.data.mapping.model.PersistentEntityParameterValueProvider.getParameterValue(PersistentEntityParameterValueProvider.java:71)
at org.springframework.data.mapping.model.SpELExpressionParameterValueProvider.getParameterValue(SpELExpressionParameterValueProvider.java:49)
at org.springframework.data.convert.ClassGeneratingEntityInstantiator$EntityInstantiatorAdapter.extractInvocationArguments(ClassGeneratingEntityInstantiator.java:250)
at org.springframework.data.convert.ClassGeneratingEntityInstantiator$EntityInstantiatorAdapter.createInstance(ClassGeneratingEntityInstantiator.java:223)
at org.springframework.data.convert.ClassGeneratingEntityInstantiator.createInstance(ClassGeneratingEntityInstantiator.java:84)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:272)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:245)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.bulkReadAndConvertDBRefs(MappingMongoConverter.java:1556)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.readAndConvertDBRef(MappingMongoConverter.java:1516)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.potentiallyReadOrResolveDbRef(MappingMongoConverter.java:1509)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.readValue(MappingMongoConverter.java:1487)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$MongoDbPropertyValueProvider.getPropertyValue(MappingMongoConverter.java:1389)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.getValueInternal(MappingMongoConverter.java:991)

显示的堆栈一直持续到Stackoverflow。

那么,如何解决Kotlin和MongoDB处理Back-References的问题?

2 个答案:

答案 0 :(得分:0)

这是要点。

@JsonIgnore用于序列化。在您的情况下,您需要的是@JsonManagedReference@JsonBackReference

class Child {
    @DBRef
    @JsonBackReference
    lateinit var parent: Parent
}

在您的父类中,但应该遵循以下原则:

class Parent(val id: String, @JsonManagedReference val child: Child) {
    init {
        child.parent = this
    }
}

您可能需要稍微更改一下我的示例,但这就是想法。

答案 1 :(得分:0)

主要问题是,当Spring Data Mongo看到Child属性并进入循环循环(因此出现Stackoverflow)时,Spring Data Mongo无法处理Parent类中的主要构造函数。但是,Jackson Mapper的序列化和反序列化没有问题!

我在Spring Data MongoDB JIRA DATA MONGO 2299上提出了一个问题,他们向我建议了解决方案1。但是,这并不是Kotlin惯用的。

我希望MongoDB可以提供一个更本地化的解决方案,因为Jackson可以很好地处理循环引用。

与此同时,我调查了一点时间,发现了另外两个选择。所有这三个解决方案都可以使用这两个框架进行序列化和反序列化:Spring Data MongoDB和Jackson Mapper。

解决方案1 ​​
优点:非常简洁
缺点:并不是Kotlin惯用的(用var id代替val id,开放类Parent)

class Child {

    // In order to workaround the StackOverflow problem we lazy initialise the property.
    @DBRef(lazy = true)
    @JsonIgnore
    lateinit var parent: Parent
}

// However, laziness requires Spring Data Mongo framework to subclass our Parent, hence we have to delcare it open
open class Parent(var id: String, val child: Child) {
    init { child.parent = this }
}

解决方案2
优点:受保护的主构造函数,用于两个框架均可处理的所有属性

class Child {
    @DBRef
    @JsonBackReference
    lateinit var parent: Parent
}


// Primary constructor called by MongoDB & Jackson for de-serialisation
class Parent protected constructor(val id: String) {

    // Secondary constructor called by us
    constructor(id: String, child: Child): this(id) {
        this.child = child
        this.child.parent = this
    }

    // We need @JsonManagedReference/@JsonBackReference on the child.
    // Ohterwise we get `UninitializedPropertyAccessException`if Jackson de-serialize it and we try to access the parent property on the child
    @JsonManagedReference
    lateinit var child: Child
}

解决方案3

优点:对构造函数的完全控制
缺点:详细

class Child {
    @DBRef
    @JsonIgnore
    lateinit var parent: Parent
}

class Parent {
    val id: String
    lateinit var child: Child


    // Called by MongoDB
    @PersistenceConstructor
    protected constructor(id: String) {
        this.id = id
    }

    // Called by Jackson Mapper & us
    constructor(id: String, child: Child) {
        this.id = id
        this.child = child
        this.child.parent = this
    }
}

希望可以帮助遇到类似问题的人。