Skip to content

Oracle sessionless transaction implementation#3887

Open
msupic wants to merge 39 commits into
5.1.xfrom
sessionless-transaction
Open

Oracle sessionless transaction implementation#3887
msupic wants to merge 39 commits into
5.1.xfrom
sessionless-transaction

Conversation

@msupic

@msupic msupic commented May 27, 2026

Copy link
Copy Markdown
Contributor

Adds Oracle JDBC sessionless transaction support to Micronaut Data. The implementation introduces SUSPEND and REQUIRES_SUSPENDED transaction propagation modes. SUSPEND starts an Oracle sessionless transaction, stores the returned GTRID in propagated state, and suspends instead of committing. REQUIRES_SUSPENDED reads the propagated GTRID, resumes the Oracle transaction, then commits or rolls back normally and clears the state.

The feature includes opt-in HTTP propagation through a server filter. The filter owns request-scoped sessionless transaction state, imports the GTRID from the configured header, and exports a suspended GTRID on the response. The default header is Oracle-Sessionless-Transaction-Id.

Non-HTTP propagation is supported through OracleSessionlessTransactionPropagationOperations, allowing callers to create lexical propagation scopes, export the current encoded transaction id, and later import it for resume workflows.

GTRID string conversion is handled by OracleSessionlessTransactionIdCodec. The default codec uses URL-safe Base64 without padding, and applications can replace it to add signing, encryption, expiration metadata, or transport-specific encoding.

Unsupported transaction managers now fail fast for SUSPEND / REQUIRES_SUSPENDED instead of silently treating them as normal transactions.

msupic added 27 commits March 5, 2026 15:40

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces Oracle JDBC “sessionless transaction” support to Micronaut Data by adding new transaction propagation modes, a dedicated Oracle-aware JDBC transaction manager, and opt-in propagation of suspended transaction identifiers across HTTP and programmatic boundaries. It also adds guide documentation, a runnable doc-example, and unit/integration tests to demonstrate and validate the feature.

Changes:

  • Added SUSPEND and REQUIRES_SUSPENDED propagation modes to TransactionDefinition and wired them into the base transaction operations.
  • Implemented Oracle-specific JDBC transaction support (manager, propagated state, id codec, optional HTTP server filter, and programmatic propagation API).
  • Added documentation plus a new doc-example module and new test coverage (unit + Oracle XE integration).

Reviewed changes

Copilot reviewed 32 out of 32 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/main/docs/guide/toc.yml Adds a docs navigation entry for the new Oracle sessionless transactions guide page.
src/main/docs/guide/dbc/jdbc/oracleSessionlessTransactions.adoc New guide page describing sessionless transaction propagation, HTTP filter, and programmatic API.
settings.gradle Includes the new doc-example subproject.
doc-examples/jdbc-sessionless-transaction-booking-java/build.gradle New example build with Oracle test-resources configuration.
doc-examples/jdbc-sessionless-transaction-booking-java/src/main/resources/application.yml Enables HTTP propagation and configures an Oracle datasource for the example.
doc-examples/jdbc-sessionless-transaction-booking-java/src/main/resources/logback.xml Logging configuration for the example app.
doc-examples/jdbc-sessionless-transaction-booking-java/src/main/java/example/Application.java Example application entrypoint.
doc-examples/jdbc-sessionless-transaction-booking-java/src/main/java/example/BookingController.java Example HTTP endpoints demonstrating suspend/resume across requests.
doc-examples/jdbc-sessionless-transaction-booking-java/src/main/java/example/BookingService.java Example service using the new propagation modes.
doc-examples/jdbc-sessionless-transaction-booking-java/src/main/java/example/Seat.java Example entity persisted inside suspended/resumed transactions.
doc-examples/jdbc-sessionless-transaction-booking-java/src/main/java/example/SeatRepository.java Repository used by the example workflow.
doc-examples/jdbc-sessionless-transaction-booking-java/src/test/java/example/BookingControllerTest.java Example test demonstrating HTTP header propagation.
doc-examples/jdbc-sessionless-transaction-booking-java/src/test/java/example/BookingServiceTest.java Example test demonstrating programmatic propagation operations.
data-tx/src/main/java/io/micronaut/transaction/TransactionDefinition.java Adds SUSPEND / REQUIRES_SUSPENDED propagation enum values and Javadoc.
data-tx/src/main/java/io/micronaut/transaction/support/AbstractTransactionOperations.java Treats the new propagation modes as “new transaction” cases in the core orchestration.
data-tx-jdbc/src/main/java/io/micronaut/transaction/jdbc/DataSourceTransactionManager.java Makes the JDBC manager extensible and adds validation/rejection for unsupported sessionless propagation.
data-tx-jdbc/src/main/java/io/micronaut/transaction/jdbc/oracle/package-info.java New Oracle JDBC tx package configuration/requirements.
data-tx-jdbc/src/main/java/io/micronaut/transaction/jdbc/oracle/OracleSessionlessTransactionState.java PropagatedContext element holding the Oracle GTRID.
data-tx-jdbc/src/main/java/io/micronaut/transaction/jdbc/oracle/OracleSessionlessTransactionPropagationOperations.java Public API for non-HTTP propagation scopes.
data-tx-jdbc/src/main/java/io/micronaut/transaction/jdbc/oracle/DefaultOracleSessionlessTransactionPropagationOperations.java Default implementation of programmatic propagation scopes.
data-tx-jdbc/src/main/java/io/micronaut/transaction/jdbc/oracle/OracleSessionlessTransactionIdCodec.java Public API for encoding/decoding transaction identifiers.
data-tx-jdbc/src/main/java/io/micronaut/transaction/jdbc/oracle/DefaultOracleSessionlessTransactionIdCodec.java Default Base64-url codec implementation.
data-tx-jdbc/src/main/java/io/micronaut/transaction/jdbc/oracle/OracleSessionlessTransactionHttpConfiguration.java Configuration properties for HTTP propagation.
data-tx-jdbc/src/main/java/io/micronaut/transaction/jdbc/oracle/OracleSessionlessTransactionHttpServerFilter.java Optional HTTP filter bridging headers ↔ propagated context.
data-tx-jdbc/src/main/java/io/micronaut/transaction/jdbc/oracle/OracleSessionlessTransactionManager.java Oracle JDBC transaction manager implementing suspend/resume semantics.
data-tx-jdbc/build.gradle Adds Micronaut HTTP (compileOnly/test) and Oracle driver test dependency for new HTTP/filter tests.
data-tx-jdbc/src/test/groovy/io/micronaut/transaction/jdbc/DataSourceTransactionManagerSpec.groovy Verifies non-Oracle JDBC manager rejects new propagation modes.
data-tx-jdbc/src/test/groovy/io/micronaut/transaction/jdbc/oracle/OracleSessionlessTransactionIdCodecSpec.groovy Tests default/custom codec bean selection.
data-tx-jdbc/src/test/groovy/io/micronaut/transaction/jdbc/oracle/OracleSessionlessTransactionPropagationOperationsSpec.groovy Tests programmatic propagation scoping behavior.
data-tx-jdbc/src/test/groovy/io/micronaut/transaction/jdbc/oracle/OracleSessionlessTransactionHttpServerFilterSpec.groovy Tests HTTP header read/write and error handling behavior.
data-tx-jdbc/src/test/groovy/io/micronaut/transaction/jdbc/oracle/OracleSessionlessTransactionManagerSpec.groovy Tests Oracle manager begin/commit/rollback behaviors and listener integration.
data-jdbc/src/test/groovy/io/micronaut/data/jdbc/oraclexe/sessionless/OracleSessionlessTransactionPropagationSpec.groovy Oracle XE integration test covering HTTP + programmatic propagation scenarios.

Comment thread src/main/docs/guide/dbc/jdbc/oracleSessionlessTransactions.adoc Outdated
Comment thread src/main/docs/guide/dbc/jdbc/oracleSessionlessTransactions.adoc Outdated
Comment thread src/main/docs/guide/dbc/jdbc/oracleSessionlessTransactions.adoc Outdated
Comment thread src/main/docs/guide/dbc/jdbc/oracleSessionlessTransactions.adoc Outdated
Comment thread src/main/docs/guide/dbc/jdbc/oracleSessionlessTransactions.adoc Outdated
Comment thread data-tx/src/main/java/io/micronaut/transaction/TransactionDefinition.java Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 39 out of 39 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

data-tx/src/main/java/io/micronaut/transaction/support/AbstractTransactionOperations.java:167

  • If transaction begin fails after acquiring a new connection from the SynchronousConnectionManager, the connection is never completed because the completion synchronization is registered only after createAndBeginTransaction() returns. Wrap begin in try/catch and ensure synchronousConnectionManager.complete(newConnectionStatus) is called on failure to avoid leaking the connection and leaving it in a non-default state.
        TransactionUtil.validateOracleSessionlessPropagation(definition, supportsOracleSessionlessTransactions());
        boolean debugEnabled = logger.isDebugEnabled();
        if (debugEnabled) {
            logger.debug("Getting transaction for definition [{}]", definition);
        }

Comment on lines 160 to 166
@NonNull
@Override
public T getTransaction(TransactionDefinition definition) throws TransactionException {
TransactionUtil.validateOracleSessionlessPropagation(definition, supportsOracleSessionlessTransactions());
boolean debugEnabled = logger.isDebugEnabled();
if (debugEnabled) {
logger.debug("Getting transaction for definition [{}]", definition);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@copilot this seems unrelated to this PR, can you provide another PR resolving this issue

@msupic msupic changed the base branch from 5.0.x to 5.1.x May 27, 2026 14:24

Copilot AI commented May 27, 2026

Copy link
Copy Markdown
Contributor

@graemerocher I've opened a new pull request, #3890, to work on those changes. Once the pull request is ready, I'll request review from you.

@graemerocher graemerocher requested a review from dstepanov May 28, 2026 09:04
@graemerocher graemerocher moved this from Backlog to In review in 5.1.0 Release May 28, 2026
@graemerocher graemerocher added the type: enhancement New feature or request label May 28, 2026
@graemerocher

Copy link
Copy Markdown
Contributor

please update the PR description with more detail

* @param definition The transaction definition
* @param supported Whether the transaction manager supports Oracle sessionless transaction propagation
*/
public static void validateOracleSessionlessPropagation(TransactionDefinition definition, boolean supported) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe this can be implemented using a TransactionSynchronization?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I don’t think TransactionSynchronization is a good fit for this validation because it runs after a transaction status already exists. The purpose of this check is to reject Oracle sessionless modes before an unsupported transaction manager can treat them as a normal transaction.

If we waited until a synchronization could be registered, the unsupported manager may already have selected a connection definition, created a transaction status, or begun a normal transaction.

Even if TransactionSynchronization had an earlier callback, it would still need transaction-manager capability information to know whether the current manager supports Oracle sessionless transactions.

NESTED
NESTED,
/**
* Start an Oracle sessionless transaction and suspend it instead of committing when the

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not sure I like the new Oracle only TX types; Maybe this should be a new annotation like for this logic or OracleTransactional?

@msupic msupic Jun 22, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I moved those to OracleTransaction annotation. Now it looks like:

public @interface OracleTransactional {

    enum Sessionless {
        /**
         * Do not apply Oracle sessionless transaction semantics.
         */
        NONE,
        /**
         * Start an Oracle sessionless transaction and suspend it instead of committing when the
         * transactional boundary completes.
         * <p>The {@link OracleTransactional#timeout()} value is passed to Oracle when the
         * sessionless transaction is started.
         */
        SUSPEND,
        /**
         * Resume an Oracle sessionless transaction from the current propagation context and complete
         * it when the transactional boundary completes.
         */
        REQUIRES_SUSPENDED
    }

    /**
     * The desired Oracle sessionless transaction mode.
     *
     * @return The sessionless transaction mode
     * @since 5.1.0
     */
    Sessionless sessionless() default Sessionless.NONE;

* applications can replace the codec without changing propagation mechanics.</p>
*/
@Singleton
final class DefaultOracleSessionlessTransactionPropagationOperations implements OracleSessionlessTransactionPropagationOperations {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe this should be in a special data-tx-jdbc-oracle module

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

How this is going to work if we have multiple OracleSessionlessTransactionPropagationOperations?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

micronaut-data-tx-jdbc already contains code related to the Oracle Transaction Priority implementation. If we introduced a new module for Oracle-specific transaction support, we would ideally move all Oracle transaction functionality there, including both transaction priority and sessionless transactions. However, moving the existing priority implementation would be a breaking change. Moving only the sessionless transaction implementation would avoid that break, but it would make the module layout inconsistent because Oracle transaction features would be split across modules.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The intended usage is to create one propagation scope for the suspend operation, export the transaction id from that scope, and then create a separate propagation scope later when resuming:

SuspendedSeat suspendedSeat = transactionPropagationOperations.withPropagation(() -> {
    Long seatId = bookingService.holdSeat(new Seat("JU502", "3a", "msid"));
    String transactionId = transactionPropagationOperations.currentTransactionId().orElseThrow();
    return new SuspendedSeat(seatId, transactionId);
});

// do something else

transactionPropagationOperations.withPropagation(suspendedSeat.transactionId(), () -> {
    bookingService.ticketSeat(suspendedSeat.seatId());
    return null;
});

OracleSessionlessTransactionPropagationOperations itself does not own transaction state. It only installs or reads OracleSessionlessTransactionState from the current PropagatedContext. The transaction manager also reads that propagated state directly. OracleSessionlessTransactionState contains only transaction id retrieved from oracle jdbc when a transaction is suspended.

Multiple suspended transaction ids in the same active context are not supported by this model.

this.expenseReportRepository = expenseReportRepository
}

@Transactional(propagation = TransactionDefinition.Propagation.SUSPEND, timeout = 3600)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe suspend-resume would be implicit if Oracle session state is present?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

SUSPEND and REQUIRES_SUSPENDED have just been moved out of the core propagation enum and into @OracleTransactional so the Oracle-specific behavior is explicit. I don’t think suspend/resume should be implicit from the presence of Oracle sessionless state, because @OracleTransactional can also be used for other Oracle transaction options, such as transaction priority, where the transaction should still behave like a normal transaction.

The propagated sessionless state should only carry the GTRID. The annotation should decide whether the method starts/suspends or resumes/completes a sessionless transaction.

* @since 5.1.0
*/
@Experimental
public interface OracleSessionlessTransactionIdCodec {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do we need this as a interface? Can we have multiple implementations?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The reason for the interface is to let applications replace the default wire format/security policy for the GTRID. The GTRID becomes a capability token when it is propagated over HTTP or another transport, so some applications may want to:

  • sign the encoded value to detect tampering
  • encrypt it so the raw Oracle GTRID is not exposed
  • bind it to a tenant/user/workflow
  • add expiry metadata

The default implementation is simple: URL-safe Base64 without padding. A custom implementation would replace it as the single codec used by both HTTP propagation and programmatic propagation.
So no, multiple codecs are not intended to be active at the same time. Micronaut bean resolution should select one.

propagation values with OracleTransactional.Sessionless modes
@sonarqubecloud

Copy link
Copy Markdown

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

Labels

type: enhancement New feature or request

Projects

Status: In review

Development

Successfully merging this pull request may close these issues.

5 participants