Весенние данные: Как автоматически удалить дочернее отношение, когда оно удаляется из родительского

#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. Хотя могут быть причины для принудительного сброса или очистки контекста сохранения в обычной бизнес-логике, я бы сказал, что это вещи, о которых вам обычно не нужно беспокоиться.