Skip to content

Conversation

@lucamolteni
Copy link
Contributor

@lucamolteni lucamolteni commented Nov 17, 2025

Introduces a new extension enabling @transactional annotation support for
Hibernate Reactive. This allows developers to use familiar transaction semantics
with reactive database operations.

Key implementation details:

  • New quarkus-reactive-transactions extension with dedicated CDI interceptors
  • TransactionalContextPool wraps the SQL client pool to lazily open transactions
    based on Vert.x context flags set by the @transactional interceptor
  • TransactionalInterceptorBase manages the complete transaction lifecycle
    (begin, commit, rollback) within the reactive Uni pipeline
  • TransactionalContextConnection wrapper prevents premature connection closure
    before transaction commit/rollback
  • OpenedSessionState refactored from Panache to centrally manage session
    lifecycle and caching (possibly to be reused in Panache as well)
  • Validation prevents mixing @transactional with @WithTransaction and
    @WithSessionOnDemand annotations to avoid conflicting transaction semantics
  • Disable JTA interceptor execution for methods returning Uni to prevent
    mixups

Limitations:

  • Currently supports only Transactional.TxType.REQUIRED, other TxType values
    throw UnsupportedOperationException

Dependencies updated:

  • hibernate-orm.version → 7.2.0.CR1
  • hibernate-reactive.version → 3.2.0-SNAPSHOT

Fixes #47462
Fixes #47698

Support @transactional on Reactive methods as well
Injection of Mutiny.Session as injectable bean

Fixes: quarkusio#47698

Introduces a new extension enabling @transactional annotation support for
Hibernate Reactive. This allows developers to use familiar transaction semantics
with reactive database operations.

Key implementation details:

* New hibernate-reactive-transactions extension with dedicated CDI interceptors
* TransactionalContextPool wraps the SQL client pool to lazily open transactions
  based on Vert.x context flags set by the @transactional interceptor
* TransactionalInterceptorBase manages the complete transaction lifecycle
  (begin, commit, rollback) within the reactive Uni pipeline
* TransactionalContextConnection wrapper prevents premature connection closure
  before transaction commit/rollback
* OpenedSessionState refactored from Panache to centrally manage session
  lifecycle and caching (possibly to be reused in Panache as well)
* Validation prevents mixing @transactional with @WithTransaction and
  @WithSessionOnDemand annotations to avoid conflicting transaction semantics
* Disable JTA interceptor execution for methods returning Uni to prevent
  mixups

Limitations:
* Currently supports only Transactional.TxType.REQUIRED, other TxType values
  throw UnsupportedOperationException

Dependencies updated:
* hibernate-orm.version → 7.2.0.CR1
* hibernate-reactive.version → 3.2.0-SNAPSHOT
Renamed module name from
hibernate-reactive-transactions => quarkus-reactive-transactions

Removed panache dependency from transaction (was test)

Adds quarkus-reactive-transactions dependency to
hibernate-reactive-panache-common/runtime and
quarkus-reactive-transactions-deployment dependency to
hibernate-reactive-panache/deployment to support the relocated tests.
Move @transactional mixing tests to hibernate-reactive-panache
import static io.quarkus.hibernate.reactive.transactions.runtime.TransactionalInterceptorBase.isUniReturnType;
import static io.quarkus.hibernate.reactive.transactions.runtime.TransactionalInterceptorBase.proceedUni;

Moved the WITH_TRANSACTION_METHOD_KEY constant inside Transaction module
Moved the SESSION_ON_DEMAND_KEY constant inside Transaction module
@lucamolteni lucamolteni changed the title 47698 bis squash Support @Transactional for Hibernate Reactive Nov 17, 2025
@quarkus-bot quarkus-bot bot added area/dependencies Pull requests that update a dependency file area/hibernate-orm Hibernate ORM area/hibernate-reactive Hibernate Reactive area/narayana Transactions / Narayana area/panache labels Nov 17, 2025
@quarkus-bot
Copy link

quarkus-bot bot commented Nov 17, 2025

/cc @gsmet (hibernate-orm)

@lucamolteni
Copy link
Contributor Author

@yrodiere @FroMage this is the first Draft of the @transactional support on Reactive

Here's what it's currently missing on my todo list

  • Reduplicate the Vert.x context every time with have a @transactional annotation (be aware that doing this will break putting stuff inside the Vert.x context) -
  • TransactionalInterceptorBase: Handle checked exception vs runtime exception differently according to the spec copy the logic from io/quarkus/narayana/jta/runtime/interceptor/TransactionalInterceptorBase.java:363
  • withTransactionalSessionOnDemand check that there's no other session opened by session delegators for another PU
  • withTransactionalSessionOnDemand check that there's no statelessSession opened by statelessSession delegators
  • MixReactiveTransactional (old annotation)
  • MixStatelessStatefulSessionTest
  • In TransactionalContextPool we create a new transaction if needed, but nobody updates the reference inside the MutinySession (currentTransaction) so if a user access it it seems like transaction is null, while instead it's running
  • Test extra transactional state
  • Test also when you put @transactional on a method where you pass a Uni as a parameter, I'm not sure it works. If that's the case it'll break the experience for the users

@lucamolteni
Copy link
Contributor Author

Also @DavideD take a look at this please

Copy link
Member

@yrodiere yrodiere left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't go through the tests but I feel they're WIP anyway, so I felt it would be better to just send this.

<artifactId>quarkus-reactive-transactions-parent</artifactId>
<version>999-SNAPSHOT</version>
</parent>
<artifactId>quarkus-reactive-transactions-deployment</artifactId>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The directory name is hibernate-reactive-transactions and that's wrong, it should be reactive-transactions to match the artifact ID.

Though I'm wondering if it makes sense to have this artifact ID if you're putting Hibernate-specific things and tests in here...

I wonder how much of this module can be moved directly to the Hibernate Reactive extensions?

Couldn't this module be just about the interceptor + setting some "transaction context", with the rest of the implementation, and tests, living directly in the Hibernate Reactive module?

Perhaps this is something that will be easier to discuss live... Feel free to hit me with a meeting if so.


@Override
protected Mutiny.Session newSessionMethod(Mutiny.SessionFactory sessionFactory) {
return sessionFactory.createSession();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This requires Davide to backport his patch to open connections lazily, correct?

return delegate.getConnection()
.compose(connection -> {
LOG.tracef("New connection, about to start transaction: %s", connection);
return connection.begin().map(t -> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed, if we open transactions "under the hood", we will need Hibernate Reactive to correctly detect that and expose Mutiny.Session#currentTransaction accordingly. Do you have an issue for that in Reactive already?

Comment on lines +189 to +198
@Override
public SharedStatelessSessionBuilder statelessWithOptions() {
return delegate.get().statelessWithOptions();
}

@Override
public SharedSessionBuilder sessionWithOptions() {
return delegate.get().sessionWithOptions();
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While the change 100% makes sense, I'm curious: how did you detect it was needed... ?

private static final String ALLOW_ENHANCEMENT_AS_PROXY = "hibernate.bytecode.allow_enhancement_as_proxy";

private static final CoreMessageLogger LOG = messageLogger(FastBootMetadataBuilder.class);
private static final CoreMessageLogger LOG = CoreMessageLogger.CORE_LOGGER; // TODO Luca review this
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review this indeed :)

Was this needed simply for the upgrade to Hibernate Reactive 3.2 or 4.2? If so we need to move it to a separate commit, at least, and eventually to a separate PR (like #50519)

Transaction transaction = connection.transaction();
LOG.tracef("Transaction started: %s", transaction);
Vertx.currentContext().putLocal(CURRENT_TRANSACTION_KEY, transaction);
return new TransactionalContextConnection(connection);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this wrapper, a call to close is a no-op.

Did you make sure the underlying connection gets closed after commit/rollback, somehow?

}
}

private static final Logger LOG = Logger.getLogger(TransactionalContextPool.class);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a general warning, take it or ignore it: some people here feel strongly about code style, and I've never seen code in Quarkus where attributes/constants are in the middle of methods, instead of at the top of the file. You might want to fix that.

Comment on lines +439 to +445
// Disable Interceptor on Reactive (uni) methods
// in The Reactive transaction module only REQUIRED is supported so far
if (ic.getMethod().getReturnType().equals(Uni.class)) {
log.debugf("method is annoted @Transactional but returns a Uni<?>, JTA transactions will be disabled");
return true;
}
return false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a feeling we may come to regret this method of detection...

For example:

  1. What if a method is meant to be actually "blocking transactional", but returns a Uni that when subscribed to, will execute code on the event loop?
  2. What if someone (wrongly) adds @Transactional to a method returning a Multi?

Etc.

Could it possibly make sense that, instead of looking at the return type, we check whether we're on an event loop (Context.isOnEventLoopThread()), and if so, we use reactive transactions, otherwise we use blocking transactions?

Then the Reactive interceptor can check the return type and fail if it's not Uni.

I think this would solve the two scenarios above.

Comment on lines +35 to +38
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-reactive-transactions</artifactId>
</dependency>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be a transitive dependency of quarkus-hibernate-reactive, which this module already depends on?

<artifactId>quarkus-integration-tests-parent</artifactId>
<version>999-SNAPSHOT</version>
</parent>
<artifactId>quarkus-reactive-transactions-integration-tests</artifactId>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you make quarkus-reactive-transactions a dependency of quarkus-hibernate-reactive, you can probably avoid this additional module and just test everything in the Hibernate Reactive integration tests.

Bonus points if this allows you to test transactions on multiple databases :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/dependencies Pull requests that update a dependency file area/hibernate-orm Hibernate ORM area/hibernate-reactive Hibernate Reactive area/narayana Transactions / Narayana area/panache

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hibernate Reactive and @Transactional quarkus-hibernate-reactive does not make StatelessSession available for injection

2 participants