-
Notifications
You must be signed in to change notification settings - Fork 38.8k
Description
Affects: 6.1.13
Transaction synchronization mechanism works incorrectly when used with nested (in terms of invocation) transactions controlled by multiple transaction managers. Assume having service like this:
class ServiceImpl(
private val eventPublisher: ApplicationEventPublisher
) {
private fun doWork(name: String, block: () -> Unit) {
eventPublisher.publishEvent(name)
block()
}
@Transactional("txm1")
fun useTxManager1(name: String, block: () -> Unit) {
doWork(name, block)
}
@Transactional("txm2")
fun useTxManager2(name: String, block: () -> Unit) {
doWork(name, block)
}
}Then attempt to run «nested» transactions like this
@TransactionalEventListener(phase = AFTER_COMMIT)
fun afterCommitListener(e: Any) {
events.add("after-commit:$e")
}
@TransactionalEventListener(phase = AFTER_ROLLBACK)
fun afterRollbackListener(e: Any) {
events.add("after-rollback:$e")
}
fun test() {
serviceImpl.useTxManager1("txm1-outer") {
serviceImpl.useTxManager2("txm2") {
serviceImpl.useTxManager1("txm1-inner") { }
}
throw NullPointerException()
}
}leads to an invalid sequence of events:
after-commit:txm2
after-commit:txm1-inner
after-rollback:txm1-outer
Event published inside txm1-inner scope gets caught by after-commit listener despite whole transaction is rolled back. Transaction mechanism itself works correctly, I've tested it using real tx managers implementations with H2 as datasource. This issue is related to the synchronization mechanism only.
I took a look at the corresponding source code and believe the main reason for such behavior is that synchronization is not suspended when participating in existing transaction. I'm not sure what proper fix should be, but it's clear that participating in existing transaction does not mean current synchronization should be used because in «nested» scenario synchronization may be left from enclosing transaction managed by another transaction manager.
Full problem reproducer may be found in the file attached to this issue. Just run mvn test to run all tests. Take a look at the syncId value printed during test to find relation between tx scope and synchronization in use. Feel free to ask if more details are required.