#kotlin #spring-data-jpa #spring-repositories
Вопрос:
У меня есть User
сущность, которая содержит Character
сущность в @OneToOne
отношении. Однако я хочу, чтобы его Character
запись была удалена, как только она будет отделена от User
сущности.
Вот мой User.kt
класс сущностей:
// User.kt
@Entity
class User(
@Id
var id: String,
var email: String,
@OneToOne(cascade = [CascadeType.ALL], orphanRemoval = true)
var character: Character?,
var isAdmin: Boolean
) { // ... }
Это модульный тест, который я написал, чтобы проверить это поведение:
// UserRepositoryTest.kt
@Test
fun `should remove orphan character entity when being removed from user entity`() {
val user = UserTestTemplate.testUser()
val character = CharacterTestTemplate.testCharacter()
user.character = character
userRepository.save(user)
user.character = null
userRepository.save(user)
val actual = userRepository.findById(user.id).orElse(null)
assertThat(actual).isNotNull()
assertThat(actual.character).isNull()
val savedCharacter = characterRepository.findById(character.id)
assertThat(savedCharacter.get()).isNull() // fails
}
Я добавил опцию CascadeType.ALL
и orphanRemoval = true
, так как это единственное, что я читал о том, что связано с моим запросом.
Что я делаю в модульном тесте, так это создаю экземпляр пользователя и персонажа. Затем добавьте экземпляр персонажа пользователю и сохраните пользователя с помощью UserRepository
. Благодаря CascadeType.ALL
персонажу экземпляр будет сохранен автоматически. Теперь я хотел бы сделать то же самое в обратном порядке при удалении персонажа у пользователя. Однако это работает не так, как ожидалось, как вы можете видеть в последней строке модульного теста
Комментарии:
1. Является ли это двунаправленной связью?
Ответ №1:
Две вещи, о которых следует знать:
- транзакционная запись за шаблоном
- кэш первого уровня
@Test
fun `should remove orphan character entity entity`() {
val user = UserTestTemplate.testUser()
val character = CharacterTestTemplate.testCharacter()
user.character = character
userRepository.save(user)
user.character = null
//use saveAndFlush here to force immediate DB update
//otherwise may be deferred until transactional method returns
userRepository.saveAndFlush(user)
//clear the persistence context to ensure you will be reading from
//the database rather than first level cache
//entityManager is injected to test via @PersistenceContext annotation
entityManager.clear();
//now you are guaranteed a db read reflecting all flushed updates
val actual = userRepository.findById(user.id).orElse(null)
assertThat(actual).isNotNull()
assertThat(actual.character).isNull()
val savedCharacter = characterRepository.findById(character.id)
assertThat(savedCharacter.get()).isNull() // fails
}
Комментарии:
1. нужна ли мне
entityManager.clear()
и.saveAndFlush
в обычной бизнес-логике, а также или просто ради модульного теста здесь?2. Хотя могут быть причины для принудительного сброса или очистки контекста сохранения в обычной бизнес-логике, я бы сказал, что это вещи, о которых вам обычно не нужно беспокоиться.