Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
codependent opened this issue Sep 23, 2020 · 5 comments
Labels
in: data Issues in data modules (jdbc, orm, oxm, tx) status: superseded An issue that has been superseded by another type: documentation A documentation task

Comments

@codependent
Copy link

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"

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Sep 23, 2020
@mp911de
Copy link
Member

mp911de commented Sep 24, 2020

Duplicate of https://jira.spring.io/browse/DATAMONGO-2632.

Reactive event listeners do not participate in the transaction that published the event because there's no mechanism to propagate the transaction context to ApplicationEventMulticaster. Any component that wants to participate in the Reactor Context (holding contextual details) must return a Publisher type. ApplicationEventPublisher.publishEvent(…) returns void hence there's no way how this invocation could get hold of the Reactor Context.

@snicoll snicoll added type: documentation A documentation task and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Sep 24, 2020
@snicoll snicoll added this to the 5.2.10 milestone Sep 24, 2020
@snicoll snicoll changed the title TransactionalEventListener not working for Webflux applications with Reactive Mongodb Document that TransactionalEventListener only work with non-reactive transactions Sep 24, 2020
@snicoll snicoll changed the title Document that TransactionalEventListener only work with non-reactive transactions Document that TransactionalEventListener only works with non-reactive transactions Sep 24, 2020
@snicoll
Copy link
Member

snicoll commented Sep 24, 2020

Thanks for letting us know about the duplicate @mp911de. We'll use this issue to document the support a bit more explicitly.

@pkgonan
Copy link

pkgonan commented Jun 26, 2022

@snicoll @mp911de @jhoeller
Hi.
I am trying to manage transactions through Spring @transactional annotation by utilizing ReactiveTransactionManager in R2DBC. If I use @eventlistener in this case, can I use transaction with @transaction annotation? I'm not talking about the @TransactionalEventListener, I'm talking about the @eventlistener annotation.

@jhoeller
Copy link
Contributor

jhoeller commented Aug 1, 2023

As of 6.1, @TransactionalEventListener can be triggered with reactive transactions through adding the transaction context as the event source: #27515 (comment)

@sbrannen
Copy link
Member

sbrannen commented Aug 2, 2023

@sbrannen sbrannen closed this as not planned Won't fix, can't repro, duplicate, stale Aug 2, 2023
@sbrannen sbrannen removed this from the 5.2.10 milestone Aug 2, 2023
@sbrannen sbrannen added the status: superseded An issue that has been superseded by another label Aug 2, 2023
@sbrannen sbrannen changed the title Document that TransactionalEventListener only works with non-reactive transactions Document that @TransactionalEventListener only works with non-reactive transactions Aug 2, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: data Issues in data modules (jdbc, orm, oxm, tx) status: superseded An issue that has been superseded by another type: documentation A documentation task
Projects
None yet
Development

No branches or pull requests

7 participants