'Kotlin + Spring boot + Spring data jpa + @Transactional = persistence paradox
I'm trying to refactor existing spring boot application kotlin so there are following entities:
@Entity
data class MainEntity(
@Id
val uuid: UUID = UUID.randomUUID(),
@OneToMany(fetch = FetchType.EAGER, orphanRemoval = true, cascade = [CascadeType.ALL], mappedBy = "mainEntity")
val nestedEntities: List<NestedEntity>,
@CreatedDate
val createdDate: Date = Date(),
@CreatedBy
val createdBy: String,
// other fields
) {// some methods}
@Entity
class NestedEntity(
@Id
val uuid: UUID = UUID.randomUUID(),
@ManyToOne
val mainEntity: MainEntity,
@OneToMany(mappedBy = "nestedEntity", fetch = FetchType.EAGER)
val auxiliaryEntities: List<AuxiliaryEntity> = listOf(),
// other fields
) {// some methods}
@Entity
class AuxiliaryEntity(
@Id
val uuid: UUID = UUID.randomUUID(),
@ManyToOne(fetch = FetchType.EAGER)
val nestedEntity: NestedEntity,
// other fields
) {// some methods}
And then i have two controllers which call same service method, but their calls have completely different results:
@RestController
class StuffController(
val nestedEnityRepo: NestedEnityRepo,
val auxiliaryService: AuxiliaryService
) {
@PostMapping("/api/v1/one/do-stuff")
fun proceed(@RequestBody auxiliaryDtos: List<AuxiliaryDto>) {
auxiliaryDtos.forEach {
val nestedEntity = nestedEnityRepo.findById(it.nestedEntityUuid)
// some checks performed over nestedEntity
auxiliaryService.doStuff(setOf(AuxiliaryEntity(nestedEnity = nestedEnity)))
}
}
}
@RestController
class AdminDoStuffController(
val nestedEnityRepo: NestedEnityRepo,
val auxiliaryService: AuxiliaryService
) {
@PostMapping("/api/v1/admin/do-stuff")
@Transactional
fun proceed(@RequestBody auxiliaryDtos: List<AuxiliaryDto>) {
auxiliaryDtos.forEach {
val nestedEntity = nestedEnityRepo.findById(it.nestedEntityUuid)
// some checks performed over nestedEntity
auxiliaryService.doStuff(setOf(AuxiliaryEntity(nestedEnity = nestedEnity)))
}
}
}
and service
@Service
class AuxiliaryService(
private val auxiliaryRepository: AuxiliaryRepository
) {
fun doStuff(auxiliaryEntities: Set<AuxiliaryEntity>) {
val byMainEntity = auxiliaryEntities.groupBy { it.nestedEntity.mainEntity }
byMainEntity.forEach {
// some business logic
auxiliaryRepository.saveAll(it.value)
}
}
}
The first thing confuses me is fact that call to AdminDoStuffController.doStuff (has @Transactional) makes hibernate persist all changes done to auxiliaryEntity, mainEntity, nestedEntity while call to StuffController.doStuff (hasn't @Transactional) persists only changes done to auxiliaryEntity and not to mainEntity, nestedEntity.
As an experiment i moved @Transactional to service AuxiliaryService.doStuff, so calls to both controllers stopped persisting changes done to mainEntity, nestedEntity. When i have @Transactional on both controllers methods, both calls make hibernate persist all changes for all entities.
The second thing - main class of spring boot application don't have @EnableTransactionManagement
Can someone, please, enlighten me about this behavior.
Solution 1:[1]
As many before you you stumbled over JPAs 1st level cache / dirty checking / delayed write cache, which are really all different aspects of the same thing.
JPA works on a persistence context that keeps track of all the entities that you loaded with it or, that you asked it to persist with it. That persistence context tries to delay read and write operations as long as possible.
Therefore
if you save an entity, it often does not get saved to the database, but only registered with the persistence context.
if you load an entity it also gets registered with the persistence context.
Only on a flush event data is guaranteed to get written to the database. But not only entities that you explicitly saved, but also those that got registered with the persistence context and got changed in any way will get written to the database.
A flush event occurs typically when a commit happens or when a query gets executed.
The end of a transaction also kills the persistence context, so entities that where referenced no longer are and therefore any changes to them won't get persisted anymore.
If you don't have explicit transactions, transactions will span a single repository call, and therefore entities returned by a repository will no longer be tracked by a persistence context and therefore not be persisted.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Jens Schauder |
