Kotlin: обратная ссылка MongoDB и @DBRef -> исключение Stackoverflow - PullRequest
0 голосов
/ 11 июня 2019

Допустим, у меня есть класс 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 голосов
/ 12 июня 2019

Основная проблема заключается в том, что Spring Data Mongo не может обработать первичный конструктор в классе Parent, когда он видит свойство Child и попадает в циклический цикл, следовательно, Stackoverflow.Тем не менее, у Jackson Mapper нет проблем с сериализацией и десериализацией!

Я открыл проблему Spring Data MongoDB JIRA DATA MONGO 2299 , где мне предложили Решение 1. Однако это не так.действительно Kotlin idiomatic.

Я надеюсь, что MongoDB имеет более нативное решение из коробки, так как Джексон может хорошо обрабатывать циклические ссылки.

Тем временем я немного исследовал время и нашел еще две альтернативы.Все 3 решения работают для сериализации и десериализации с обеими платформами: Spring Data MongoDB и Jackson Mapper.

Решение 1
Плюсы: очень лаконично
Минусы: не совсем идиоматически Kotlin (var id вместо val id, openкласс 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
    }
}

Надеюсь, это поможет кому-то с подобными проблемами.

0 голосов
/ 11 июня 2019

Вот суть.

@JsonIgnore для сериализации.В вашем случае вам нужны @JsonManagedReference и @JsonBackReference.

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

И в вашем родительском классе, но это должно быть что-то вроде этого:

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

Вывозможно, потребуется немного изменить мой пример, но это идея.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...