Introduce TransactionDefinition.withDefaults() shortcut

Such a static unmodifiable TransactionDefinition with defaults can be used for getTransaction(null) calls, now also possible for getReactiveTransaction. Furthermore, it can be used for a simple TransactionalOperator.create(ReactiveTransactionManager) method without an internal dependency on the transaction.support package.

See gh-22646
This commit is contained in:
Juergen Hoeller 2019-05-03 12:28:41 +02:00
parent eaa9a78d5d
commit a2a6bc3d47
8 changed files with 154 additions and 76 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -41,9 +41,9 @@ import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.ResourceLoader;
import org.springframework.lang.Nullable;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
/**
* Common base class for accessing a Quartz Scheduler, i.e. for registering jobs,
@ -207,7 +207,7 @@ public abstract class SchedulerAccessor implements ResourceLoaderAware {
protected void registerJobsAndTriggers() throws SchedulerException {
TransactionStatus transactionStatus = null;
if (this.transactionManager != null) {
transactionStatus = this.transactionManager.getTransaction(new DefaultTransactionDefinition());
transactionStatus = this.transactionManager.getTransaction(TransactionDefinition.withDefaults());
}
try {

View File

@ -18,6 +18,8 @@ package org.springframework.transaction;
import reactor.core.publisher.Mono;
import org.springframework.lang.Nullable;
/**
* This is the central interface in Spring's reactive transaction infrastructure.
* Applications can use this directly, but it is not primarily meant as API:
@ -27,6 +29,8 @@ import reactor.core.publisher.Mono;
* @author Mark Paluch
* @author Juergen Hoeller
* @since 5.2
* @see org.springframework.transaction.reactive.TransactionalOperator
* @see org.springframework.transaction.interceptor.TransactionInterceptor
*/
public interface ReactiveTransactionManager extends TransactionManager {
@ -53,7 +57,8 @@ public interface ReactiveTransactionManager extends TransactionManager {
* @see TransactionDefinition#getTimeout
* @see TransactionDefinition#isReadOnly
*/
Mono<ReactiveTransaction> getReactiveTransaction(TransactionDefinition definition) throws TransactionException;
Mono<ReactiveTransaction> getReactiveTransaction(@Nullable TransactionDefinition definition)
throws TransactionException;
/**
* Commit the given transaction, with regard to its status. If the transaction

View File

@ -0,0 +1,33 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.transaction;
/**
* A static unmodifiable transaction definition.
*
* @author Juergen Hoeller
* @since 5.2
* @see TransactionDefinition#withDefaults()
*/
final class StaticTransactionDefinition implements TransactionDefinition {
static final StaticTransactionDefinition INSTANCE = new StaticTransactionDefinition();
private StaticTransactionDefinition() {
}
}

View File

@ -195,11 +195,14 @@ public interface TransactionDefinition {
* Return the propagation behavior.
* <p>Must return one of the {@code PROPAGATION_XXX} constants
* defined on {@link TransactionDefinition this interface}.
* <p>The default is {@link #PROPAGATION_REQUIRED}.
* @return the propagation behavior
* @see #PROPAGATION_REQUIRED
* @see org.springframework.transaction.support.TransactionSynchronizationManager#isActualTransactionActive()
*/
int getPropagationBehavior();
default int getPropagationBehavior() {
return PROPAGATION_REQUIRED;
}
/**
* Return the isolation level.
@ -212,13 +215,16 @@ public interface TransactionDefinition {
* "true" on your transaction manager if you'd like isolation level declarations
* to get rejected when participating in an existing transaction with a different
* isolation level.
* <p>Note that a transaction manager that does not support custom isolation levels
* will throw an exception when given any other level than {@link #ISOLATION_DEFAULT}.
* <p>The default is {@link #ISOLATION_DEFAULT}. Note that a transaction manager
* that does not support custom isolation levels will throw an exception when
* given any other level than {@link #ISOLATION_DEFAULT}.
* @return the isolation level
* @see #ISOLATION_DEFAULT
* @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setValidateExistingTransaction
*/
int getIsolationLevel();
default int getIsolationLevel() {
return ISOLATION_DEFAULT;
}
/**
* Return the transaction timeout.
@ -228,9 +234,12 @@ public interface TransactionDefinition {
* transactions.
* <p>Note that a transaction manager that does not support timeouts will throw
* an exception when given any other timeout than {@link #TIMEOUT_DEFAULT}.
* <p>The default is {@link #TIMEOUT_DEFAULT}.
* @return the transaction timeout
*/
int getTimeout();
default int getTimeout() {
return TIMEOUT_DEFAULT;
}
/**
* Return whether to optimize as a read-only transaction.
@ -245,10 +254,13 @@ public interface TransactionDefinition {
* A transaction manager which cannot interpret the read-only hint will
* <i>not</i> throw an exception when asked for a read-only transaction.
* @return {@code true} if the transaction is to be optimized as read-only
* ({@code false} by default)
* @see org.springframework.transaction.support.TransactionSynchronization#beforeCommit(boolean)
* @see org.springframework.transaction.support.TransactionSynchronizationManager#isCurrentTransactionReadOnly()
*/
boolean isReadOnly();
default boolean isReadOnly() {
return false;
}
/**
* Return the name of this transaction. Can be {@code null}.
@ -256,11 +268,27 @@ public interface TransactionDefinition {
* transaction monitor, if applicable (for example, WebLogic's).
* <p>In case of Spring's declarative transactions, the exposed name will be
* the {@code fully-qualified class name + "." + method name} (by default).
* @return the name of this transaction
* @return the name of this transaction ({@code null} by default}
* @see org.springframework.transaction.interceptor.TransactionAspectSupport
* @see org.springframework.transaction.support.TransactionSynchronizationManager#getCurrentTransactionName()
*/
@Nullable
String getName();
default String getName() {
return null;
}
// Static builder methods
/**
* Return an unmodifiable {@code TransactionDefinition} with defaults.
* <p>For customization purposes, use the modifiable
* {@link org.springframework.transaction.support.DefaultTransactionDefinition}
* instead.
* @since 5.2
*/
static TransactionDefinition withDefaults() {
return StaticTransactionDefinition.INSTANCE;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -34,7 +34,6 @@ import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionSystemException;
import org.springframework.transaction.support.CallbackPreferringPlatformTransactionManager;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.DefaultTransactionStatus;
import org.springframework.transaction.support.SmartTransactionObject;
import org.springframework.transaction.support.TransactionCallback;
@ -233,17 +232,15 @@ public class WebSphereUowTransactionManager extends JtaTransactionManager
public <T> T execute(@Nullable TransactionDefinition definition, TransactionCallback<T> callback)
throws TransactionException {
if (definition == null) {
// Use defaults if no transaction definition given.
definition = new DefaultTransactionDefinition();
}
// Use defaults if no transaction definition given.
TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());
if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());
}
UOWManager uowManager = obtainUOWManager();
int pb = definition.getPropagationBehavior();
int pb = def.getPropagationBehavior();
boolean existingTx = (uowManager.getUOWStatus() != UOWSynchronizationRegistry.UOW_STATUS_NONE &&
uowManager.getUOWType() != UOWSynchronizationRegistry.UOW_TYPE_LOCAL_TRANSACTION);
@ -292,19 +289,19 @@ public class WebSphereUowTransactionManager extends JtaTransactionManager
boolean debug = logger.isDebugEnabled();
if (debug) {
logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);
logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
}
SuspendedResourcesHolder suspendedResources = (!joinTx ? suspend(null) : null);
UOWActionAdapter<T> action = null;
try {
if (definition.getTimeout() > TransactionDefinition.TIMEOUT_DEFAULT) {
uowManager.setUOWTimeout(uowType, definition.getTimeout());
if (def.getTimeout() > TransactionDefinition.TIMEOUT_DEFAULT) {
uowManager.setUOWTimeout(uowType, def.getTimeout());
}
if (debug) {
logger.debug("Invoking WebSphere UOW action: type=" + uowType + ", join=" + joinTx);
}
action = new UOWActionAdapter<>(
definition, callback, (uowType == UOWManager.UOW_TYPE_GLOBAL_TRANSACTION), !joinTx, newSynch, debug);
def, callback, (uowType == UOWManager.UOW_TYPE_GLOBAL_TRANSACTION), !joinTx, newSynch, debug);
uowManager.runUnderUOW(uowType, joinTx, action);
if (debug) {
logger.debug("Returned from WebSphere UOW action: type=" + uowType + ", join=" + joinTx);

View File

@ -95,7 +95,12 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran
* @see #doBegin
*/
@Override
public final Mono<ReactiveTransaction> getReactiveTransaction(TransactionDefinition definition) throws TransactionException {
public final Mono<ReactiveTransaction> getReactiveTransaction(@Nullable TransactionDefinition definition)
throws TransactionException {
// Use defaults if no transaction definition given.
TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());
return TransactionSynchronizationManager.currentTransaction()
.flatMap(synchronizationManager -> {
@ -106,22 +111,22 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran
if (isExistingTransaction(transaction)) {
// Existing transaction found -> check propagation behavior to find out how to behave.
return handleExistingTransaction(synchronizationManager, definition, transaction, debugEnabled);
return handleExistingTransaction(synchronizationManager, def, transaction, debugEnabled);
}
// Check definition settings for new transaction.
if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
return Mono.error(new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout()));
if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
return Mono.error(new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout()));
}
// No existing transaction found -> check propagation behavior to find out how to proceed.
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
return Mono.error(new IllegalTransactionStateException(
"No existing transaction found for transaction marked with propagation 'mandatory'"));
}
else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
return TransactionContextManager.currentContext()
.map(TransactionSynchronizationManager::new)
@ -131,14 +136,14 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran
.defaultIfEmpty(Optional.empty())
.flatMap(suspendedResources -> {
if (debugEnabled) {
logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);
logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
}
return Mono.defer(() -> {
GenericReactiveTransaction status = newReactiveTransaction(
nestedSynchronizationManager, definition, transaction, true,
nestedSynchronizationManager, def, transaction, true,
debugEnabled, suspendedResources.orElse(null));
return doBegin(nestedSynchronizationManager, transaction, definition)
.doOnSuccess(ignore -> prepareSynchronization(nestedSynchronizationManager, status, definition))
return doBegin(nestedSynchronizationManager, transaction, def)
.doOnSuccess(ignore -> prepareSynchronization(nestedSynchronizationManager, status, def))
.thenReturn(status);
}).onErrorResume(ErrorPredicates.RUNTIME_OR_ERROR,
ex -> resume(nestedSynchronizationManager, null, suspendedResources.orElse(null))
@ -147,11 +152,11 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran
}
else {
// Create "empty" transaction: no actual transaction, but potentially synchronization.
if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
if (def.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
logger.warn("Custom isolation level specified but no actual transaction initiated; " +
"isolation level will effectively be ignored: " + definition);
"isolation level will effectively be ignored: " + def);
}
return Mono.just(prepareReactiveTransaction(synchronizationManager, definition, null, true, debugEnabled, null));
return Mono.just(prepareReactiveTransaction(synchronizationManager, def, null, true, debugEnabled, null));
}
});
}

View File

@ -46,20 +46,6 @@ import org.springframework.transaction.TransactionException;
*/
public interface TransactionalOperator {
/**
* Create a new {@link TransactionalOperator} using {@link ReactiveTransactionManager}
* and {@link TransactionDefinition}.
* @param transactionManager the transaction management strategy to be used
* @param transactionDefinition the transaction definition to apply
* @return the transactional operator
*/
static TransactionalOperator create(
ReactiveTransactionManager transactionManager, TransactionDefinition transactionDefinition){
return new TransactionalOperatorImpl(transactionManager, transactionDefinition);
}
/**
* Wrap the functional sequence specified by the given Flux within a transaction.
* @param flux the Flux that should be executed within the transaction
@ -95,4 +81,30 @@ public interface TransactionalOperator {
*/
<T> Flux<T> execute(TransactionCallback<T> action) throws TransactionException;
// Static builder methods
/**
* Create a new {@link TransactionalOperator} using {@link ReactiveTransactionManager},
* using a default transaction.
* @param transactionManager the transaction management strategy to be used
* @return the transactional operator
*/
static TransactionalOperator create(ReactiveTransactionManager transactionManager){
return create(transactionManager, TransactionDefinition.withDefaults());
}
/**
* Create a new {@link TransactionalOperator} using {@link ReactiveTransactionManager}
* and {@link TransactionDefinition}.
* @param transactionManager the transaction management strategy to be used
* @param transactionDefinition the transaction definition to apply
* @return the transactional operator
*/
static TransactionalOperator create(
ReactiveTransactionManager transactionManager, TransactionDefinition transactionDefinition){
return new TransactionalOperatorImpl(transactionManager, transactionDefinition);
}
}

View File

@ -338,45 +338,43 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran
* @see #doBegin
*/
@Override
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException {
// Use defaults if no transaction definition given.
TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());
Object transaction = doGetTransaction();
// Cache debug flag to avoid repeated checks.
boolean debugEnabled = logger.isDebugEnabled();
if (definition == null) {
// Use defaults if no transaction definition given.
definition = new DefaultTransactionDefinition();
}
if (isExistingTransaction(transaction)) {
// Existing transaction found -> check propagation behavior to find out how to behave.
return handleExistingTransaction(definition, transaction, debugEnabled);
return handleExistingTransaction(def, transaction, debugEnabled);
}
// Check definition settings for new transaction.
if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());
}
// No existing transaction found -> check propagation behavior to find out how to proceed.
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
throw new IllegalTransactionStateException(
"No existing transaction found for transaction marked with propagation 'mandatory'");
}
else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
SuspendedResourcesHolder suspendedResources = suspend(null);
if (debugEnabled) {
logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);
logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
}
try {
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
doBegin(transaction, definition);
prepareSynchronization(status, definition);
def, transaction, true, newSynchronization, debugEnabled, suspendedResources);
doBegin(transaction, def);
prepareSynchronization(status, def);
return status;
}
catch (RuntimeException | Error ex) {
@ -386,12 +384,12 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran
}
else {
// Create "empty" transaction: no actual transaction, but potentially synchronization.
if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
if (def.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
logger.warn("Custom isolation level specified but no actual transaction initiated; " +
"isolation level will effectively be ignored: " + definition);
"isolation level will effectively be ignored: " + def);
}
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
}
}