-
Notifications
You must be signed in to change notification settings - Fork 38.9k
Description
Affects: 5.2.9
The problem described here can be reproduced using the following sample project: https://github.com/codependent/transactional-event-sample
In a Spring Boot Webflux application with Reactive Mongodb as repository, I would like to take advantage of Spring's event publishing in a transactional way, thus I tried using TransactionalEventListener. The problem is even though the ReactiveTransactionManager has started an actual transaction, when dealing with the event, ApplicationListenerMethodTransactionalAdapter.onApplicationEvent() considers both TransactionSynchronizationManager.isSynchronizationActive() and TransactionSynchronizationManager.isActualTransactionActive() as false.
Below you can see the logs of this process:
2020-09-23 09:46:14.206 TRACE 32760 --- [ctor-http-nio-2] t.a.AnnotationTransactionAttributeSource : Adding transactional method 'com.codependent.sample.service.UserServiceImpl.create' with attribute: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2020-09-23 09:46:14.286 TRACE 32760 --- [ctor-http-nio-2] .s.t.r.TransactionSynchronizationManager : Bound value [org.springframework.data.mongodb.ReactiveMongoResourceHolder@d351c8c7] for key [org.springframework.data.mongodb.core.SimpleReactiveMongoDatabaseFactory@209f1e30] to context [2b9f14a3-bcbf-4b45-9c9b-3f9dc689d1af]
2020-09-23 09:46:14.286 TRACE 32760 --- [ctor-http-nio-2] .s.t.r.TransactionSynchronizationManager : Initializing transaction synchronization
2020-09-23 09:46:14.286 TRACE 32760 --- [ctor-http-nio-2] o.s.t.i.TransactionInterceptor : Getting transaction for [com.codependent.sample.service.UserServiceImpl.create]
2020-09-23 09:46:14.288 INFO 32760 --- [ctor-http-nio-2] c.c.sample.service.UserServiceImpl : create() isSyncActive false - isTxActive false
2020-09-23 09:46:14.311 DEBUG 32760 --- [ctor-http-nio-2] cationListenerMethodTransactionalAdapter : No transaction is active - skipping org.springframework.context.PayloadApplicationEvent[source=org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext@f0ba5602, started on Wed Sep 23 09:37:21 CEST 2020]
2020-09-23 09:46:14.313 DEBUG 32760 --- [ctor-http-nio-2] cationListenerMethodTransactionalAdapter : No transaction is active - skipping org.springframework.data.mongodb.core.mapping.event.BeforeConvertEvent[source=User(id=null, name=John Doe)]
2020-09-23 09:46:14.323 DEBUG 32760 --- [ctor-http-nio-2] cationListenerMethodTransactionalAdapter : No transaction is active - skipping org.springframework.data.mongodb.core.mapping.event.BeforeSaveEvent[source=User(id=null, name=John Doe)]
2020-09-23 09:46:14.328 TRACE 32760 --- [ctor-http-nio-2] .s.t.r.TransactionSynchronizationManager : Retrieved value [org.springframework.data.mongodb.ReactiveMongoResourceHolder@d351c8c7] for key [org.springframework.data.mongodb.core.SimpleReactiveMongoDatabaseFactory@209f1e30] bound to context [2b9f14a3-bcbf-4b45-9c9b-3f9dc689d1af: com.codependent.sample.service.UserServiceImpl.create]
2020-09-23 09:46:14.917 INFO 32760 --- [ntLoopGroup-3-4] org.mongodb.driver.connection : Opened connection [connectionId{localValue:4, serverValue:198137}] to insight-dev-shard-00-00-otcb8.gcp.mongodb.net:27017
2020-09-23 09:46:14.979 DEBUG 32760 --- [ntLoopGroup-3-4] cationListenerMethodTransactionalAdapter : No transaction is active - skipping org.springframework.data.mongodb.core.mapping.event.AfterSaveEvent[source=User(id=5f6afd4626e83e41147e429a, name=John Doe)]
2020-09-23 09:46:14.979 TRACE 32760 --- [ntLoopGroup-3-4] o.s.t.i.TransactionInterceptor : Completing transaction for [com.codependent.sample.service.UserServiceImpl.create]
2020-09-23 09:46:15.041 TRACE 32760 --- [ntLoopGroup-3-4] .s.t.r.TransactionSynchronizationManager : Clearing transaction synchronization
2020-09-23 09:46:15.043 TRACE 32760 --- [ntLoopGroup-3-4] .s.t.r.TransactionSynchronizationManager : Removed value [org.springframework.data.mongodb.ReactiveMongoResourceHolder@d351c8c7] for key [org.springframework.data.mongodb.core.SimpleReactiveMongoDatabaseFactory@209f1e30] from context [2b9f14a3-bcbf-4b45-9c9b-3f9dc689d1af]
The first four lines show that there is an actual transaction, however, inside my transactional method I print the following (5th log line):
logger.info("create() isSyncActive {} - isTxActive {}",
TransactionSynchronizationManager.isSynchronizationActive(),
TransactionSynchronizationManager.isActualTransactionActive())
which shows create() isSyncActive false - isTxActive false. I seems that TransactionSynchronizationManager isn't considering the ongoing Reactive Mongo transaction and in the 6th log line, when ApplicationListenerMethodTransactionalAdapter.onApplicationEvent() kicks in, it doesn't synchronize the transaction, and just skips it.
MongoDb Transaction config:
@EnableTransactionManagement
@Configuration
class MongoDbConfiguration {
@Bean
fun mongoTransactionManager(dbFactory: ReactiveMongoDatabaseFactory) =
ReactiveMongoTransactionManager(dbFactory)
}
Service and event listener:
@Service
@Transactional
class UserServiceImpl(private val userRepository: UserRepository) : UserService {
private val logger = LoggerFactory.getLogger(javaClass)
override fun create(user: User): Mono<User> {
logger.info("create() isSyncActive {} - isTxActive {}",
TransactionSynchronizationManager.isSynchronizationActive(),
TransactionSynchronizationManager.isActualTransactionActive())
return userRepository.save(user.complete())
}
@Service
class UserEventListener {
private val logger = LoggerFactory.getLogger(javaClass)
@TransactionalEventListener
@Transactional
fun userCreated(event: UserCreatedEvent){
logger.info("userCreated({})", event)
}
@Document(collection = "sample-user")
data class User (var id: String?, var name: String) : AbstractAggregateRoot<User>(){
fun complete(): User {
registerEvent(UserCreatedEvent(name))
return this
}
}
In order to use the sample you need a Mongodb 4.x instance with replication enabled (to support transactions), e.g. Mongo Atlas, configuring the appropriate value in application.yml:
spring:
data:
mongodb:
uri: xxx
After starting the application just call: curl -X POST localhost:8080/users -d '{"name": "John Doe"}' -H "content-type: application/json"