Skip to content

Oracle sessionless transaction implementation - #3887

Open
msupic wants to merge 44 commits into
5.2.xfrom
sessionless-transaction
Open

Oracle sessionless transaction implementation#3887
msupic wants to merge 44 commits into
5.2.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

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.

msupic added 2 commits June 22, 2026 12:34
@graemerocher
graemerocher requested a review from dstepanov July 6, 2026 11:45
@msupic
msupic changed the base branch from 5.1.x to 5.2.x July 23, 2026 13:22
msupic added 2 commits August 12, 2026 12:20
…into sessionless-transaction

# Conflicts:
#	data-tx-jdbc/build.gradle
#	data-tx/src/main/java/io/micronaut/transaction/annotation/OracleTransactional.java
#	data-tx/src/test/java/io/micronaut/transaction/support/DoRollbackOnCommitExceptionTest.java
#	src/main/docs/guide/shared/transactions.adoc
#	src/main/docs/guide/toc.yml
@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: Backlog

Development

Successfully merging this pull request may close these issues.

6 participants