Skip to content

Inconsistent Transaction and TransactionSynchronizationManager Attributes with Nested Transactions in Multi-Transaction Manager Environment #35039

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

teahyuk
Copy link

@teahyuk teahyuk commented Jun 12, 2025

Hi !

situation

In a multi-transaction manager environment, when @Transactional annotations from different transaction managers are nested, the attributes of TransactionSynchronizationManager do not align with the current transaction if the inner @Transactional method has a propagation level of SUPPORTS, NOT_SUPPORTS, or NEVER.

Specifically, TransactionSynchronizationManager inherits the attributes (e.g., readOnly status) of the outer/parent's other transaction manager, rather than reflecting the characteristics of the "empty" or "non-participating" transaction created by the inner @Transactional method under these propagation settings. This leads to a mismatch between the expected transaction state and the state reported by TransactionSynchronizationManager.

Steps to Reproduce

Consider the following simplified example:

class TransactionManager1 {

    private TransactionManager2 transactionManager2;

    @Transactional(transactionManager = "manager1", readOnly = true)
    public void transactionFirst() {
       transactionManager2.transactionSecond();
    }
}

class TransactionManager2 {

    // Assuming propagation is one of SUPPORTS, NOT_SUPPORTS, or NEVER implicitly or explicitly
    @Transactional(transactionManager = "manager2", readOnly = false, propagation = Propagation.SUPPORTS)
    public void transactionSecond() { // Changed method name for clarity based on example
        // Inside this method, TransactionSynchronizationManager.isCurrentTransactionReadOnly()
        // is expected to be 'false' (based on manager2's readOnly=false)
        // However, it currently returns 'true', inheriting from manager1.
        assert TransactionSynchronizationManager.isCurrentTransactionReadOnly() : "readOnly must be false";
    }
}

Expected Behavior

When transactionSecond() is invoked, TransactionSynchronizationManager.isCurrentTransactionReadOnly() should accurately reflect the characteristics of the current transaction context established by manager2's @transactional, irrespective of any parent transaction's attributes.

This is especially critical when the parent transaction originates from a different transaction manager. While it might seem that PROPAGATION_SUPPORTS, PROPAGATION_NOT_SUPPORTED, or PROPAGATION_NEVER shouldn't be affected as they don't create a new transaction, @Transactional attributes like readOnly can be set regardless of whether a physical transaction is actually active. Therefore, these attributes should be applied and reflected independently.

Indeed, if @Transactional(transactionManager = "manager2", readOnly = false, propagation = Propagation.SUPPORTS) were to execute without a parent transaction, TransactionSynchronizationManager.isCurrentTransactionReadOnly() would correctly return false. This demonstrates that @Transactional attributes should be precisely reflected, regardless of the presence of other transaction managers.

Therefore, whether transactionFirst() is invoked or transactionSecond() is called directly, assert TransactionSynchronizationManager.isCurrentTransactionReadOnly() should not throw an exception.

Actual Behavior

However, when transactionFirst() is invoked, an AssertionError is thrown when transactionSecond() is called. Conversely, if transactionSecond() is invoked directly, no AssertionError occurs.

Proposed Changes

In AbstractPlatformTransactionManager.getTransaction(Definition), we've added suspend logic at the very end of the method. This ensures that when a transaction context is established with propagations like PROPAGATION_SUPPORTS, PROPAGATION_NOT_SUPPORTED, or PROPAGATION_NEVER, any previously active transaction (potentially from another TransactionManager) is correctly suspended. This guarantees that TransactionSynchronizationManager accurately reflects the characteristics of the current transaction context, preventing inconsistencies in readOnly status and other attributes.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Jun 12, 2025
@sbrannen sbrannen added the in: data Issues in data modules (jdbc, orm, oxm, tx) label Jun 12, 2025
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: waiting-for-triage An issue we've not yet triaged or decided on
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants