Skip to content

Document that @TransactionalEventListener only works with non-reactive transactions #25805

@codependent

Description

@codependent

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"

Metadata

Metadata

Assignees

No one assigned

    Labels

    in: dataIssues in data modules (jdbc, orm, oxm, tx)status: supersededAn issue that has been superseded by anothertype: documentationA documentation task

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions