mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-09-17 08:24:13 +00:00
Introduce enforceReadOnly flag for JTA 2.1 read-only mode
Closes gh-35915
This commit is contained in:
+2
-2
@@ -78,8 +78,8 @@ public class JtaTransactionAnnotationParser implements TransactionAnnotationPars
|
||||
}
|
||||
rbta.setRollbackRules(rollbackRules);
|
||||
|
||||
if (attributes.containsKey("readOnly")) { // JTA 2.1
|
||||
rbta.setReadOnly(attributes.getBoolean("readOnly"));
|
||||
if (attributes.containsKey("isReadOnly")) { // JTA 2.1
|
||||
rbta.setReadOnly(attributes.getBoolean("isReadOnly"));
|
||||
}
|
||||
|
||||
return rbta;
|
||||
|
||||
+55
-2
@@ -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#begin(boolean) method available?
|
||||
private static final @Nullable Method beginWithReadOnlyMethod =
|
||||
ClassUtils.getMethodIfAvailable(UserTransaction.class, "begin", boolean.class);
|
||||
|
||||
private transient JndiTemplate jndiTemplate = new JndiTemplate();
|
||||
|
||||
private transient @Nullable UserTransaction userTransaction;
|
||||
@@ -165,6 +173,8 @@ public class JtaTransactionManager extends AbstractPlatformTransactionManager
|
||||
|
||||
private boolean allowCustomIsolationLevels = false;
|
||||
|
||||
private boolean enforceReadOnly = false;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new JtaTransactionManager instance, to be configured as bean.
|
||||
@@ -418,6 +428,20 @@ public class JtaTransactionManager extends AbstractPlatformTransactionManager
|
||||
this.allowCustomIsolationLevels = allowCustomIsolationLevels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether a read-only transaction should be exposed to the JTA transaction manager,
|
||||
* enforcing strict read-only access to resources as per the JTA 2.1 specification.
|
||||
* <p>This requires JTA 2.1. The default is "false". By default, the read-only status
|
||||
* of a Spring transaction is just exposed to transaction synchronization (for example,
|
||||
* suppressing a Hibernate flush), while the JTA transaction itself will regularly commit.
|
||||
* Turn this flag on if your transactional XA resources are known to support the JTA 2.1
|
||||
* {@code ExtendedXAResource} SPI, operating in a read-only mode with eventual rollback.
|
||||
* @since 7.1
|
||||
*/
|
||||
public void setEnforceReadOnly(boolean enforceReadOnly) {
|
||||
this.enforceReadOnly = enforceReadOnly;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialize the UserTransaction as well as the TransactionManager handle.
|
||||
@@ -858,7 +882,29 @@ public class JtaTransactionManager extends AbstractPlatformTransactionManager
|
||||
applyIsolationLevel(txObject, definition.getIsolationLevel());
|
||||
int timeout = determineTimeout(definition);
|
||||
applyTimeout(txObject, timeout);
|
||||
txObject.getUserTransaction().begin();
|
||||
|
||||
if (this.enforceReadOnly && definition.isReadOnly()) {
|
||||
if (beginWithReadOnlyMethod == null) {
|
||||
throw new NotSupportedException("enforceReadOnly requires JTA 2.1");
|
||||
}
|
||||
try {
|
||||
beginWithReadOnlyMethod.invoke(txObject.getUserTransaction(), true);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
if (ex instanceof InvocationTargetException ite) {
|
||||
if (ite.getTargetException() instanceof NotSupportedException nse) {
|
||||
throw nse;
|
||||
}
|
||||
if (ite.getTargetException() instanceof SystemException se) {
|
||||
throw se;
|
||||
}
|
||||
}
|
||||
ReflectionUtils.handleReflectionException(ex);
|
||||
}
|
||||
}
|
||||
else {
|
||||
txObject.getUserTransaction().begin();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1007,7 +1053,14 @@ public class JtaTransactionManager extends AbstractPlatformTransactionManager
|
||||
}
|
||||
throw new UnexpectedRollbackException("JTA transaction already rolled back (probably due to a timeout)");
|
||||
}
|
||||
txObject.getUserTransaction().commit();
|
||||
|
||||
if (this.enforceReadOnly && status.isReadOnly()) {
|
||||
// JTA 2.1 isReadOnly enforces a rollback call
|
||||
txObject.getUserTransaction().rollback();
|
||||
}
|
||||
else {
|
||||
txObject.getUserTransaction().commit();
|
||||
}
|
||||
}
|
||||
catch (RollbackException ex) {
|
||||
throw new UnexpectedRollbackException(
|
||||
|
||||
+72
@@ -16,15 +16,22 @@
|
||||
|
||||
package org.springframework.transaction.jta;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import jakarta.transaction.HeuristicMixedException;
|
||||
import jakarta.transaction.HeuristicRollbackException;
|
||||
import jakarta.transaction.NotSupportedException;
|
||||
import jakarta.transaction.RollbackException;
|
||||
import jakarta.transaction.SystemException;
|
||||
import jakarta.transaction.Transaction;
|
||||
import jakarta.transaction.TransactionManager;
|
||||
import jakarta.transaction.UserTransaction;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Adapter for a JTA UserTransaction handle, taking a JTA
|
||||
@@ -40,11 +47,22 @@ import org.springframework.util.Assert;
|
||||
* <p>Used internally by Spring's {@link JtaTransactionManager} for certain
|
||||
* scenarios. Not intended for direct use in application code.
|
||||
*
|
||||
* <p>As of Spring Framework 7.1, this adapter supports the JTA 2.1
|
||||
* read-only methods as well.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 1.1.5
|
||||
*/
|
||||
public class UserTransactionAdapter implements UserTransaction {
|
||||
|
||||
// JTA 2.1 TransactionManager#begin(boolean) method available?
|
||||
private static final @Nullable Method beginWithReadOnlyMethod =
|
||||
ClassUtils.getMethodIfAvailable(TransactionManager.class, "begin", boolean.class);
|
||||
|
||||
// JTA 2.1 Transaction#begin(boolean) method available?
|
||||
private static final @Nullable Method isReadOnlyMethod =
|
||||
ClassUtils.getMethodIfAvailable(Transaction.class, "isReadOnly");
|
||||
|
||||
private final TransactionManager transactionManager;
|
||||
|
||||
|
||||
@@ -75,10 +93,41 @@ public class UserTransactionAdapter implements UserTransaction {
|
||||
this.transactionManager.begin();
|
||||
}
|
||||
|
||||
/**
|
||||
* JTA 2.1 begin(boolean) method.
|
||||
* @since 7.1
|
||||
*/
|
||||
// @Override - on JTA 2.1
|
||||
public void begin(boolean isReadOnly) throws NotSupportedException, SystemException {
|
||||
if (beginWithReadOnlyMethod == null) {
|
||||
if (isReadOnly) {
|
||||
throw new NotSupportedException("begin(true) requires JTA 2.1");
|
||||
}
|
||||
this.transactionManager.begin();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
beginWithReadOnlyMethod.invoke(this.transactionManager, isReadOnly);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
if (ex instanceof InvocationTargetException ite) {
|
||||
if (ite.getTargetException() instanceof NotSupportedException nse) {
|
||||
throw nse;
|
||||
}
|
||||
if (ite.getTargetException() instanceof SystemException se) {
|
||||
throw se;
|
||||
}
|
||||
}
|
||||
ReflectionUtils.handleReflectionException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit()
|
||||
throws RollbackException, HeuristicMixedException, HeuristicRollbackException,
|
||||
SecurityException, SystemException {
|
||||
|
||||
this.transactionManager.commit();
|
||||
}
|
||||
|
||||
@@ -97,4 +146,27 @@ public class UserTransactionAdapter implements UserTransaction {
|
||||
return this.transactionManager.getStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* JTA 2.1 isReadOnly() method.
|
||||
* @since 7.1
|
||||
*/
|
||||
// @Override - on JTA 2.1
|
||||
public boolean isReadOnly() throws SystemException {
|
||||
if (isReadOnlyMethod != null) {
|
||||
Transaction transaction = this.transactionManager.getTransaction();
|
||||
try {
|
||||
return (Boolean) isReadOnlyMethod.invoke(transaction);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
if (ex instanceof InvocationTargetException ite &&
|
||||
ite.getTargetException() instanceof SystemException se) {
|
||||
throw se;
|
||||
}
|
||||
ReflectionUtils.handleReflectionException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+10
@@ -558,6 +558,8 @@ class JtaTransactionManagerTests {
|
||||
given(tm.suspend()).willReturn(tx);
|
||||
|
||||
final JtaTransactionManager ptm = newJtaTransactionManager(ut, tm);
|
||||
// ptm.setEnforceReadOnly(true);
|
||||
|
||||
TransactionTemplate tt = new TransactionTemplate(ptm);
|
||||
tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
||||
tt.setName("txName");
|
||||
@@ -584,8 +586,16 @@ class JtaTransactionManagerTests {
|
||||
});
|
||||
assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isFalse();
|
||||
|
||||
// Traditional JTA transaction
|
||||
verify(ut, times(2)).begin();
|
||||
verify(ut, times(2)).commit();
|
||||
|
||||
// JTA 2.1 with setEnforceReadOnly(true)
|
||||
// verify(ut, times(1)).begin();
|
||||
// verify(ut, times(1)).begin(true);
|
||||
// verify(ut, times(1)).rollback();
|
||||
// verify(ut, times(1)).commit();
|
||||
|
||||
verify(tm).resume(tx);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user