JCR-5239: Skip direct JDBC tx-control on managed XA connection during active JTA transaction#350
Open
torsten-liermann wants to merge 1 commit intoapache:trunkfrom
Conversation
8a8a272 to
165f8b6
Compare
… active JTA transaction When ConnectionHelper runs inside a Jakarta/Java EE container (WildFly, JBoss, ...) the DataSource is typically a JCA-managed connection pool. While the application thread holds an active container-managed JTA transaction, the wrapped Connection refuses explicit setAutoCommit(false), commit() and rollback() calls (e.g. IronJacamar IJ031017 / IJ031018 / IJ031019) because the transaction manager owns the connection's transactional lifecycle. This change resolves a TransactionSynchronizationRegistry once via JNDI (java:comp/TransactionSynchronizationRegistry) and uses its getTransactionStatus() to detect a managed transaction on the current thread. When such a transaction is active, the three direct JDBC transaction-control calls in startBatch(), endBatch() and getConnection() are skipped — the TM commits/rolls back the underlying XAResource as part of the global transaction. When no managed transaction is in progress, the historical behaviour is preserved (auto-commit toggling and explicit commit/rollback as before). The TSR is referenced through reflection so the same code compiles and runs against javax.transaction (Java EE 8) and jakarta.transaction (Jakarta EE 9+) container stacks. Signed-off-by: Torsten Liermann <mist@liermann.biz>
165f8b6 to
21ec132
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Issue
JIRA: JCR-5239
ConnectionHelperinjackrabbit-dataissues three direct JDBC transaction-control calls (setAutoCommit(false)instartBatch(),commit()/rollback()inendBatch(),setAutoCommit(true)ingetConnection()from JCR-1013). On a JCA-managed connection pool inside an active container-managed JTA transaction these calls are rejected by the JCA contract — IronJacamar reportsIJ031017/IJ031018/IJ031019because the transaction manager owns the connection's transactional lifecycle. This blocks every Jackrabbit-Classic deployment that wants to share its DataSource with a Jakarta-EE JTA boundary (e.g. JPA + JCR 2PC on the same backend).Fix
A nested static
JtaContextdiscovers the ambientTransactionSynchronizationRegistrylazily on first use via JNDI (java:comp/TransactionSynchronizationRegistry) and exposes two operations toConnectionHelper:hasManagedTransaction()— returnstruefor every JTAStatusother thanSTATUS_NO_TRANSACTION.STATUS_UNKNOWNis treated as managed because per the JTA contract it means a transaction is associated but its current status cannot be determined; running direct JDBC tx-control on a possibly enrolled connection is the very failure mode this PR addresses. Fail-closed in three layers: TSR present + status method unavailable ->true; reflective invocation failure ->true; non-integer return ->true.markRollbackOnly()— invoked fromendBatch(false)when a managed JTA tx is in progress. The localConnection.rollback()cannot run on a JCA-managed XA connection, so the caller's rollback intent is mirrored onto the global JTA transaction via the TSR; the TM aborts the global transaction on completion. Returnsboolean;endBatch(false)throwsSQLExceptionwhenfalseis returned, so a requested rollback never disappears silently.Method binding tries
jakarta.transaction.TransactionSynchronizationRegistryfirst, thenjavax.transaction.TransactionSynchronizationRegistry, then falls back to the concrete TSR class — so the same compiled class works against javax.transaction (Java EE 8) and jakarta.transaction (Jakarta EE 9+) without conditional code paths and without depending on a package-private container implementation type.When no TSR is bound (standalone JSE / unit-test setups), the historical behaviour is fully preserved:
setAutoCommit/commit/rollbackrun directly on the JDBCConnectionexactly as before.Mid-batch JTA-state changes
The JTA state is sampled per call site (
startBatch,endBatch,getConnection), not captured once atstartBatchand replayed atendBatch. The reasoning:startBatch/endBatchpair is executed within one persistence-manager operation, which runs entirely inside whichever JTA scope the caller established. The state the helper sees atstartBatchis the same state it sees atendBatch.startBatch outside managed JTA + endBatch inside managed JTA:startBatchalready issuedsetAutoCommit(false)on the localConnection.endBatchunder managed JTA must not callcommit()/rollback()(the JCA pool would reject them); forcommit==falsethe rollback intent is still mirrored onto the global transaction viasetRollbackOnly(). The localConnection'sautoCommitflag is left untouched and the pool resets it on connection return.Diff scope
jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/ConnectionHelper.java— JTA detection logic in a new package-privateJtaContextstatic nested class, lazy lookup, three call-site adjustments instartBatch(),endBatch()andgetConnection(boolean).jackrabbit-data/src/test/java/org/apache/jackrabbit/core/util/db/ConnectionHelperJtaContextTest.java— JUnit unit tests around theJtaContextdecision logic: no TSR /STATUS_NO_TRANSACTION/STATUS_ACTIVE/STATUS_MARKED_ROLLBACK/STATUS_UNKNOWN/ TSR present butgetTransactionStatus()throws / TSR present butgetTransactionStatusmethod missing /markRollbackOnly()delegation, failure, absent-TSR, missing-method cases.jackrabbit-data/src/test/java/org/apache/jackrabbit/core/util/db/ConnectionHelperJtaIT.java— tests againstConnectionHelperitself with a hand-rolledDataSource/Connectionstand-in:startBatch()skips/callssetAutoCommit(false)based on JTA state;endBatch(true)skipscommit()under managed JTA;endBatch(false)callssetRollbackOnly()under managed JTA, throwsSQLExceptionif that fails, callsConnection.rollback()outside;getConnection(false)skips/calls the JCR-1013 auto-commit flip based on JTA state.No behavioural change outside an active managed JTA transaction.
Verification
ConnectionHelperJtaContextTest, 7 inConnectionHelperJtaIT).<xa-data-source>SQL Server 2022 viaSQLServerXADataSource, Spring Boot 3.5.14 WAR using stockjackrabbit-core2.22.2 with this fix overlaid onjackrabbit-data. 2PC scenarios across JPA + JCR-Session XAResources within one Narayana JTA transaction (4 rollback modes + 2 DataStore-rollback modes), all green;com.arjuna.ats.jtaTRACE log shows multipleenlistResource/prepare/commit-or-abortcalls per Tx with matchingtx_uid.