Как указать Spring Data MongoDB хранить вложенные поля документа, которые также являются документами в их собственной коллекции?

#spring #mongodb #kotlin #spring-data-mongodb

#spring #mongodb #kotlin #spring-data-mongodb

Вопрос:

У меня есть две коллекции, называемые persons and addresses . Идея состоит в том, чтобы у пользователя был адрес в поле address . Я использую Spring Data MongoDB для сохранения упомянутых документов.

Мой обычный способ создания «отношения» между Person> Address заключался в том, чтобы сохранить идентификатор адреса и передать его объекту person. Позже, когда я find() человек, я разрешаю объект address по его идентификатору, и вуаля, у меня есть мой person address.

Однако я нахожу это несколько громоздким, поскольку в моем коде я просто хочу добавить Address объект целиком, а не только его идентификатор, чтобы я мог работать с ним, сохраняя его в хранилище в любой момент времени.

Поэтому я начал небольшой модульный тест, чтобы увидеть, как Spring Data MongoDB сохраняет Address объект, если это просто поле Person и не сохраняется его собственным Repository .

Это то, что я придумал:

 import org.springframework.data.mongodb.core.mapping.Document
import org.springframework.data.mongodb.repository.MongoRepository
import org.springframework.stereotype.Repository

@Document("person")
data class Person(
    val id: String,
    val name: String,
    val age: Int,
    var address: Address
)

@Document("addresses")
data class Address(
    val id: String,
    val street: String?,
    val number: Int?
)

@Repository
interface PersonRepository : MongoRepository<Person, String>

@Repository
interface AddressRepository : MongoRepository<Address, String>

  

И это модульный тест, который завершается неудачно с последними шагами, как я и ожидал:

 internal class FooTest @Autowired constructor(
    private val personRepository: PersonRepository,
    private val addressRepository: AddressRepository
) {

    @Test
    fun `some experiment`() {
        val testPerson = Person("001", "Peter", 25, Address("011","Lumberbumber", 12))
        personRepository.save(testPerson)

        val person = personRepository.findAll()[0]

        assertThat(person).isNotNull
        assertThat(person.address).isNotNull
        assertThat(person.address.street).isEqualTo("Lumberbumber")
        assertThat(person.address.number).isEqualTo(12)
        // works because address was just copied into the object structure
        // of `person` and was not seen as a standalone document

        val address = addressRepository.findAll()[0]

        assertThat(address.street).isEqualTo("Lumberbumber") // fails
        assertThat(address.number).isEqualTo(12) // fails
        // As expected `address` was not persisted alongside the `person` document.

    }
}
  

Итак, я подумал об использовании AbstractMongoEventListener<Person> для перехвата процесса сохранения и выбора Address объекта из Person здесь и некоторое addressRepository.save(addressDocument) время помещал облегченный объект address (имеющий только идентификатор) обратно в Person документ.
То же самое я бы сделал в обратном порядке, когда выполняю поиск для Person и снова собираю Person и Address вместе.

 @Component
class MongoSaveInterceptor(
    val addressRepository: AddressRepository

) : AbstractMongoEventListener<Person>() {
    override fun onBeforeConvert(event: BeforeConvertEvent<Person>) {
        val personToSave = event.source
        val extractedAddress = personToSave.address
        val idOfAddress = addressRepository.save(extractedAddress).id
        personToSave.address = Address(idOfAddress, null, null)
    }

    override fun onAfterConvert(event: AfterConvertEvent<Person>) {
        val person = event.source
        val idOfAddress = person.address.id
        val foundAddress = addressRepository.findById(idOfAddress)
        foundAddress.ifPresent {
            person.address = it
        }
    }
}
  

Это работает таким образом и может быть обходным решением для моего требования.

НО

Я чувствую, что должно быть что-то подобное, что уже работает, и мне, возможно, просто нужно найти правильную конфигурацию для этого.

Вот где я застрял в банкомате и нуждаюсь в некотором руководстве.

Комментарии:

1. @Document("addresses") addresses Это другая коллекция?

2. Идея состоит в том, чтобы разделить person и address на две отдельные коллекции. Пожалуйста, не спрашивайте о причине, поскольку это минимальный пример, и я не хотел раздувать вопросы с подробностями концепции, лежащей в основе требования

3. Я просто читаю о @DBRef, который выглядит именно так, как мне нужно. Я переформулирую ответ, если он будет работать так, как мне нужно.

4. Я думаю, что дизайн должен заключаться в том, что адрес встроен в документ person. Это одна из основных функций модели документа. Не рекомендуется иметь коллекцию адресов. Смотрите Этот дизайн модели данных . Это согласуется с принципом, согласно которому связанные данные должны храниться вместе и могут быть доступны вместе.

5. @prasad_ Проблема с этим подходом заключается в том, что если я хочу изменить «адрес», я должен изменить его в каждом документе, в который он встроен, чтобы иметь согласованность. Я знаю, что это то, что реляционные базы данных решают по дизайну, но поскольку я хочу MongoDB для других функций, а не для дизайна документа, у меня нет другого пути, кроме как вернуться к реляционному дизайну

Ответ №1:

Другое исследование показало мне, что это примерно @DBRef (https://www.baeldung.com/cascading-with-dbref-and-lifecycle-events-in-spring-data-mongodb ) Я должен использовать. Таким образом, Spring Data MongoDB сохраняет внедренный класс документа и разрешает его при загрузке родительского объекта document из базы данных.