#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 из базы данных.