让我们说我上了一堂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
的问题?
答案 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
}
}
希望可以帮助遇到类似问题的人。