From 001fc8e5852c2ad8eceb309fba74b5a5b4dc0057 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 14 Oct 2025 13:25:13 +0200 Subject: [PATCH] Add early support for JTA 2.1 read-only attribute Closes gh-35633 --- .../JtaTransactionAnnotationParser.java | 4 ++ .../jta/JtaTransactionManager.java | 37 +++++++++++++++++++ .../transaction/jta/JtaTransactionObject.java | 2 + 3 files changed, 43 insertions(+) diff --git a/spring-tx/src/main/java/org/springframework/transaction/annotation/JtaTransactionAnnotationParser.java b/spring-tx/src/main/java/org/springframework/transaction/annotation/JtaTransactionAnnotationParser.java index d922bd96cf4..26a7932a307 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/annotation/JtaTransactionAnnotationParser.java +++ b/spring-tx/src/main/java/org/springframework/transaction/annotation/JtaTransactionAnnotationParser.java @@ -78,6 +78,10 @@ public class JtaTransactionAnnotationParser implements TransactionAnnotationPars } rbta.setRollbackRules(rollbackRules); + if (attributes.containsKey("readOnly")) { // JTA 2.1 + rbta.setReadOnly(attributes.getBoolean("readOnly")); + } + return rbta; } diff --git a/spring-tx/src/main/java/org/springframework/transaction/jta/JtaTransactionManager.java b/spring-tx/src/main/java/org/springframework/transaction/jta/JtaTransactionManager.java index 2c7c080859b..a4bea6b17a9 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/jta/JtaTransactionManager.java +++ b/spring-tx/src/main/java/org/springframework/transaction/jta/JtaTransactionManager.java @@ -19,6 +19,8 @@ package org.springframework.transaction.jta; import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.List; import java.util.Properties; @@ -52,6 +54,8 @@ import org.springframework.transaction.support.AbstractPlatformTransactionManage import org.springframework.transaction.support.DefaultTransactionStatus; import org.springframework.transaction.support.TransactionSynchronization; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; /** @@ -139,6 +143,10 @@ public class JtaTransactionManager extends AbstractPlatformTransactionManager "java:comp/TransactionSynchronizationRegistry"; + // JTA 2.1 UserTransaction#setReadOnly(boolean) method available? + private static final @Nullable Method setReadOnlyMethod = + ClassUtils.getMethodIfAvailable(UserTransaction.class, "setReadOnly", boolean.class); + private transient JndiTemplate jndiTemplate = new JndiTemplate(); private transient @Nullable UserTransaction userTransaction; @@ -858,6 +866,12 @@ public class JtaTransactionManager extends AbstractPlatformTransactionManager applyIsolationLevel(txObject, definition.getIsolationLevel()); int timeout = determineTimeout(definition); applyTimeout(txObject, timeout); + + if (definition.isReadOnly()) { + setReadOnlyIfPossible(txObject.getUserTransaction(), true); + txObject.resetReadOnly = true; + } + txObject.getUserTransaction().begin(); } @@ -904,6 +918,21 @@ public class JtaTransactionManager extends AbstractPlatformTransactionManager } } + private void setReadOnlyIfPossible(UserTransaction ut, boolean readOnly) throws SystemException { + if (setReadOnlyMethod != null) { + try { + setReadOnlyMethod.invoke(ut, readOnly); + } + catch (Exception ex) { + if (ex instanceof InvocationTargetException ute && + ute.getTargetException() instanceof SystemException se) { + throw se; + } + ReflectionUtils.handleReflectionException(ex); + } + } + } + @Override protected Object doSuspend(Object transaction) { @@ -1161,6 +1190,14 @@ public class JtaTransactionManager extends AbstractPlatformTransactionManager @Override protected void doCleanupAfterCompletion(Object transaction) { JtaTransactionObject txObject = (JtaTransactionObject) transaction; + if (txObject.resetReadOnly) { + try { + setReadOnlyIfPossible(txObject.getUserTransaction(), false); + } + catch (SystemException ex) { + logger.debug("Failed to reset read-only flag after after JTA completion", ex); + } + } if (txObject.resetTransactionTimeout) { try { txObject.getUserTransaction().setTransactionTimeout(0); diff --git a/spring-tx/src/main/java/org/springframework/transaction/jta/JtaTransactionObject.java b/spring-tx/src/main/java/org/springframework/transaction/jta/JtaTransactionObject.java index d3cb2afeac1..531b9ff98e5 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/jta/JtaTransactionObject.java +++ b/spring-tx/src/main/java/org/springframework/transaction/jta/JtaTransactionObject.java @@ -41,6 +41,8 @@ public class JtaTransactionObject implements SmartTransactionObject { boolean resetTransactionTimeout = false; + boolean resetReadOnly = false; + /** * Create a new JtaTransactionObject for the given JTA UserTransaction.