Reputation: 35
Spring Boot tries to auto commit changes in manuell transaction for entity personTransaction1 without calling save method in repository. Changes in personTransaction1 are commited. The method manuellTransaction throw org.springframework.orm.ObjectOptimisticLockingFailureException. The same code with annotation based transaction handling in method transactionByAnnotation work as expected and no changes for variable personTransaction1 were commited. What is the reason why spring try to commit?
@SpringBootApplication
@EnableJpaAuditing
class DbOptimiticLockingApplication : CommandLineRunner {
@Autowired
private lateinit var personRepository: PersonRepository
@Autowired
private lateinit var platformTransactionManager: PlatformTransactionManager
override fun run(vararg args: String?) {
executeInNewTransaction {
personRepository.deleteAll()
}
val person = createPerson()
transactionByAnnotation(person.id)
manuellTransaction(person.id)
}
@Transactional
fun createPerson(): Person {
val person = Person()
person.name = "Max"
return personRepository.save(person)
}
@Transactional(Transactional.TxType.REQUIRES_NEW)
fun transactionByAnnotation(id: Long) {
var personTransaction1 = personRepository.findById(id).get()
// don't trigger commit
personTransaction1.name = "Tom"
transaction11ByAnnotation(personTransaction1.id)
transaction12ByAnnotation(personTransaction1.id)
}
@Transactional(Transactional.TxType.REQUIRES_NEW)
fun transaction11ByAnnotation(id: Long) {
businessLogic1(id)
}
@Transactional(Transactional.TxType.REQUIRES_NEW)
fun transaction12ByAnnotation(id: Long) {
businessLogic2(id)
}
fun manuellTransaction(id: Long) {
executeInNewTransaction {
var personTransaction1 = personRepository.findById(id).get()
// trigger commit
personTransaction1.name = "Tom"
manuellTransaction11(personTransaction1.id)
manuellTransaction12(personTransaction1.id)
}
}
fun manuellTransaction11(id: Long) {
executeInNewTransaction {
businessLogic1(id)
}
}
fun manuellTransaction12(id: Long) {
executeInNewTransaction {
businessLogic2(id)
}
}
private fun businessLogic1(id: Long) {
val person = personRepository.findById(id).get()
person.name = "Martin"
personRepository.saveAndFlush(person)
}
private fun businessLogic2(id: Long) {
val person = personRepository.findById(id).get()
person.name = "Joe"
personRepository.saveAndFlush(person)
}
private fun <T> executeInNewTransaction(action: () -> T): T {
val transactionTemplate = TransactionTemplate(platformTransactionManager)
transactionTemplate.propagationBehavior = TransactionDefinition.PROPAGATION_REQUIRES_NEW
return try {
transactionTemplate.execute {
action()
}!!
} catch (e: Exception) {
throw e
}
}
}
@Entity
@EntityListeners
class Person {
@Id
@GeneratedValue
var id: Long = 0L
@Column
var name: String = ""
@Version
var version: Long = 0L
}
interface PersonRepository : JpaRepository<Person, Long>
fun main(args: Array<String>) {
runApplication<DbOptimiticLockingApplication>(*args)
}
Upvotes: 2
Views: 1367
Reputation: 19193
What is the reason why spring try to commit?
When an entity is read from Database it becomes for the JPA layer a persistent
or otherwise called managed
entity.
Entities on persistent/managed
state are observed by the ORM vendor and any changes being done on them are passed in the database layer automatically.
Perquisite for this to happen is that the method where the entity is considered as persistent/managed
finishes without any errors and the method belongs to a JPA transaction!
For the following quote that you describe, no it does not work as expected and the behavior that you observe and what you expect to happen is just a coincidence.
transactionByAnnotation work as expected and no changes for variable personTransaction1 were commited
@Transactional(Transactional.TxType.REQUIRES_NEW)
fun transactionByAnnotation(id: Long) {
var personTransaction1 = personRepository.findById(id).get()
@Transactional(Transactional.TxType.REQUIRES_NEW)
-> is completely ignored in this case that you mention here since the invocation of that method is from the same instance from method run
, so the annotation is not considered at all. More info here in an old SO question
So since this method finishes wihout any errors but does not belong to any JPA transaction, the changes being done on managed entity are not passed automatically in Database.
If it belonged into a JPA transaction and the @Transactinal
was not ignored because of self invocation it would have persisted the changes in Database.
Upvotes: 2