I have a User
entity that holds a Character
entity in a @OneToOne
relation. However I wantt he Character
record to be removed as soon as it gets detached from the User
entity.
Here is my User.kt
entity class:
// User.kt
@Entity
class User(
@Id
var id: String,
var email: String,
@OneToOne(cascade = [CascadeType.ALL], orphanRemoval = true)
var character: Character?,
var isAdmin: Boolean
) { // ... }
This is the unit test I wrote to test this behaviour:
// 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
}
I added the CascadeType.ALL
and orphanRemoval = true
option since those are the only things I read about being related to my request.
What I do in the unit test is creating a user and character instance. Then adding the character instance to the user and saving the user via the UserRepository
. Thanks to CascadeType.ALL
the character instance will be saved automatically. Now I'd like to have the same thing in reverse when removing the character from the user. This however does not work as expected as you can see in the last line of the unit test
Two things to be aware of:
@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
}