Introduce configurable default rollback rules
Includes rollbackOn annotation attribute on @EnableTransactionManagement and addDefaultRollbackRule method on AnnotationTransactionAttributeSource, as well as publicMethodsOnly as instance-level flag (also on AnnotationCacheOperationSource). Closes gh-23473
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -30,6 +30,8 @@ import org.springframework.lang.Nullable;
|
||||
import org.springframework.transaction.TransactionManager;
|
||||
import org.springframework.transaction.config.TransactionManagementConfigUtils;
|
||||
import org.springframework.transaction.event.TransactionalEventListenerFactory;
|
||||
import org.springframework.transaction.interceptor.RollbackRuleAttribute;
|
||||
import org.springframework.transaction.interceptor.TransactionAttributeSource;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
@@ -38,6 +40,7 @@ import org.springframework.util.CollectionUtils;
|
||||
*
|
||||
* @author Chris Beams
|
||||
* @author Stephane Nicoll
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.1
|
||||
* @see EnableTransactionManagement
|
||||
*/
|
||||
@@ -77,6 +80,18 @@ public abstract class AbstractTransactionManagementConfiguration implements Impo
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
public TransactionAttributeSource transactionAttributeSource() {
|
||||
// Accept protected @Transactional methods on CGLIB proxies, as of 6.0
|
||||
AnnotationTransactionAttributeSource tas = new AnnotationTransactionAttributeSource(false);
|
||||
// Apply default rollback rule, as of 6.2
|
||||
if (this.enableTx != null && this.enableTx.getEnum("rollbackOn") == RollbackOn.ALL_EXCEPTIONS) {
|
||||
tas.addDefaultRollbackRule(RollbackRuleAttribute.ROLLBACK_ON_ALL_EXCEPTIONS);
|
||||
}
|
||||
return tas;
|
||||
}
|
||||
|
||||
@Bean(name = TransactionManagementConfigUtils.TRANSACTIONAL_EVENT_LISTENER_FACTORY_BEAN_NAME)
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
public static TransactionalEventListenerFactory transactionalEventListenerFactory() {
|
||||
|
||||
@@ -19,13 +19,14 @@ package org.springframework.transaction.annotation;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.transaction.interceptor.AbstractFallbackTransactionAttributeSource;
|
||||
import org.springframework.transaction.interceptor.RollbackRuleAttribute;
|
||||
import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute;
|
||||
import org.springframework.transaction.interceptor.TransactionAttribute;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
@@ -57,20 +58,23 @@ import org.springframework.util.CollectionUtils;
|
||||
public class AnnotationTransactionAttributeSource extends AbstractFallbackTransactionAttributeSource
|
||||
implements Serializable {
|
||||
|
||||
private static final boolean jta12Present;
|
||||
private static final boolean jtaPresent;
|
||||
|
||||
private static final boolean ejb3Present;
|
||||
|
||||
static {
|
||||
ClassLoader classLoader = AnnotationTransactionAttributeSource.class.getClassLoader();
|
||||
jta12Present = ClassUtils.isPresent("jakarta.transaction.Transactional", classLoader);
|
||||
jtaPresent = ClassUtils.isPresent("jakarta.transaction.Transactional", classLoader);
|
||||
ejb3Present = ClassUtils.isPresent("jakarta.ejb.TransactionAttribute", classLoader);
|
||||
}
|
||||
|
||||
private final boolean publicMethodsOnly;
|
||||
|
||||
private final Set<TransactionAnnotationParser> annotationParsers;
|
||||
|
||||
private boolean publicMethodsOnly = true;
|
||||
|
||||
@Nullable
|
||||
private Set<RollbackRuleAttribute> defaultRollbackRules;
|
||||
|
||||
|
||||
/**
|
||||
* Create a default AnnotationTransactionAttributeSource, supporting
|
||||
@@ -78,24 +82,10 @@ public class AnnotationTransactionAttributeSource extends AbstractFallbackTransa
|
||||
* or the EJB3 {@link jakarta.ejb.TransactionAttribute} annotation.
|
||||
*/
|
||||
public AnnotationTransactionAttributeSource() {
|
||||
this(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a custom AnnotationTransactionAttributeSource, supporting
|
||||
* public methods that carry the {@code Transactional} annotation
|
||||
* or the EJB3 {@link jakarta.ejb.TransactionAttribute} annotation.
|
||||
* @param publicMethodsOnly whether to support public methods that carry
|
||||
* the {@code Transactional} annotation only (typically for use
|
||||
* with proxy-based AOP), or protected/private methods as well
|
||||
* (typically used with AspectJ class weaving)
|
||||
*/
|
||||
public AnnotationTransactionAttributeSource(boolean publicMethodsOnly) {
|
||||
this.publicMethodsOnly = publicMethodsOnly;
|
||||
if (jta12Present || ejb3Present) {
|
||||
if (jtaPresent || ejb3Present) {
|
||||
this.annotationParsers = CollectionUtils.newLinkedHashSet(3);
|
||||
this.annotationParsers.add(new SpringTransactionAnnotationParser());
|
||||
if (jta12Present) {
|
||||
if (jtaPresent) {
|
||||
this.annotationParsers.add(new JtaTransactionAnnotationParser());
|
||||
}
|
||||
if (ejb3Present) {
|
||||
@@ -107,12 +97,26 @@ public class AnnotationTransactionAttributeSource extends AbstractFallbackTransa
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a custom AnnotationTransactionAttributeSource, supporting
|
||||
* public methods that carry the {@code Transactional} annotation
|
||||
* or the EJB3 {@link jakarta.ejb.TransactionAttribute} annotation.
|
||||
* @param publicMethodsOnly whether to support public methods that carry
|
||||
* the {@code Transactional} annotation only (typically for use
|
||||
* with proxy-based AOP), or protected/private methods as well
|
||||
* (typically used with AspectJ class weaving)
|
||||
* @see #setPublicMethodsOnly
|
||||
*/
|
||||
public AnnotationTransactionAttributeSource(boolean publicMethodsOnly) {
|
||||
this();
|
||||
this.publicMethodsOnly = publicMethodsOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a custom AnnotationTransactionAttributeSource.
|
||||
* @param annotationParser the TransactionAnnotationParser to use
|
||||
*/
|
||||
public AnnotationTransactionAttributeSource(TransactionAnnotationParser annotationParser) {
|
||||
this.publicMethodsOnly = true;
|
||||
Assert.notNull(annotationParser, "TransactionAnnotationParser must not be null");
|
||||
this.annotationParsers = Collections.singleton(annotationParser);
|
||||
}
|
||||
@@ -122,19 +126,40 @@ public class AnnotationTransactionAttributeSource extends AbstractFallbackTransa
|
||||
* @param annotationParsers the TransactionAnnotationParsers to use
|
||||
*/
|
||||
public AnnotationTransactionAttributeSource(TransactionAnnotationParser... annotationParsers) {
|
||||
this.publicMethodsOnly = true;
|
||||
Assert.notEmpty(annotationParsers, "At least one TransactionAnnotationParser needs to be specified");
|
||||
this.annotationParsers = new LinkedHashSet<>(Arrays.asList(annotationParsers));
|
||||
this.annotationParsers = Set.of(annotationParsers);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set whether transactional methods are expected to be public.
|
||||
* <p>The default is {@code true}.
|
||||
* @since 6.2
|
||||
* @see #AnnotationTransactionAttributeSource(boolean)
|
||||
*/
|
||||
public void setPublicMethodsOnly(boolean publicMethodsOnly) {
|
||||
this.publicMethodsOnly = publicMethodsOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a custom AnnotationTransactionAttributeSource.
|
||||
* @param annotationParsers the TransactionAnnotationParsers to use
|
||||
* Add a default rollback rule, to be applied to all rule-based
|
||||
* transaction attributes returned by this source.
|
||||
* <p>By default, a rollback will be triggered on unchecked exceptions
|
||||
* but not on checked exceptions. A default rule may override this
|
||||
* while still respecting any custom rules in the transaction attribute.
|
||||
* @param rollbackRule a rollback rule overriding the default behavior,
|
||||
* e.g. {@link RollbackRuleAttribute#ROLLBACK_ON_ALL_EXCEPTIONS}
|
||||
* @since 6.2
|
||||
* @see RuleBasedTransactionAttribute#getRollbackRules()
|
||||
* @see EnableTransactionManagement#rollbackOn()
|
||||
* @see Transactional#rollbackFor()
|
||||
* @see Transactional#noRollbackFor()
|
||||
*/
|
||||
public AnnotationTransactionAttributeSource(Set<TransactionAnnotationParser> annotationParsers) {
|
||||
this.publicMethodsOnly = true;
|
||||
Assert.notEmpty(annotationParsers, "At least one TransactionAnnotationParser needs to be specified");
|
||||
this.annotationParsers = annotationParsers;
|
||||
public void addDefaultRollbackRule(RollbackRuleAttribute rollbackRule) {
|
||||
if (this.defaultRollbackRules == null) {
|
||||
this.defaultRollbackRules = new LinkedHashSet<>();
|
||||
}
|
||||
this.defaultRollbackRules.add(rollbackRule);
|
||||
}
|
||||
|
||||
|
||||
@@ -175,6 +200,9 @@ public class AnnotationTransactionAttributeSource extends AbstractFallbackTransa
|
||||
for (TransactionAnnotationParser parser : this.annotationParsers) {
|
||||
TransactionAttribute attr = parser.parseTransactionAnnotation(element);
|
||||
if (attr != null) {
|
||||
if (this.defaultRollbackRules != null && attr instanceof RuleBasedTransactionAttribute ruleAttr) {
|
||||
ruleAttr.getRollbackRules().addAll(this.defaultRollbackRules);
|
||||
}
|
||||
return attr;
|
||||
}
|
||||
}
|
||||
@@ -183,6 +211,7 @@ public class AnnotationTransactionAttributeSource extends AbstractFallbackTransa
|
||||
|
||||
/**
|
||||
* By default, only public methods can be made transactional.
|
||||
* @see #setPublicMethodsOnly
|
||||
*/
|
||||
@Override
|
||||
protected boolean allowPublicMethodsOnly() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -163,10 +163,10 @@ import org.springframework.core.Ordered;
|
||||
public @interface EnableTransactionManagement {
|
||||
|
||||
/**
|
||||
* Indicate whether subclass-based (CGLIB) proxies are to be created ({@code true}) as
|
||||
* opposed to standard Java interface-based proxies ({@code false}). The default is
|
||||
* {@code false}. <strong>Applicable only if {@link #mode()} is set to
|
||||
* {@link AdviceMode#PROXY}</strong>.
|
||||
* Indicate whether subclass-based (CGLIB) proxies are to be created ({@code true})
|
||||
* as opposed to standard Java interface-based proxies ({@code false}).
|
||||
* The default is {@code false}. <strong>Applicable only if {@link #mode()}
|
||||
* is set to {@link AdviceMode#PROXY}</strong>.
|
||||
* <p>Note that setting this attribute to {@code true} will affect <em>all</em>
|
||||
* Spring-managed beans requiring proxying, not just those marked with
|
||||
* {@code @Transactional}. For example, other beans marked with Spring's
|
||||
@@ -195,4 +195,25 @@ public @interface EnableTransactionManagement {
|
||||
*/
|
||||
int order() default Ordered.LOWEST_PRECEDENCE;
|
||||
|
||||
/**
|
||||
* Indicate the rollback behavior for rule-based transactions without
|
||||
* custom rollback rules: default is rollback on unchecked exception,
|
||||
* this can be switched to rollback on any exception (including checked).
|
||||
* <p>Note that transaction-specific rollback rules override the default
|
||||
* behavior but retain the chosen default for unspecified exceptions.
|
||||
* This is the case for Spring's {@link Transactional} as well as JTA's
|
||||
* {@link jakarta.transaction.Transactional} when used with Spring here.
|
||||
* <p>Unless you rely on EJB-style business exceptions with commit behavior,
|
||||
* it is advisable to switch to {@link RollbackOn#ALL_EXCEPTIONS} for a
|
||||
* consistent rollback even in case of a (potentially accidental) checked
|
||||
* exception. Also, it is advisable to make that switch for Kotlin-based
|
||||
* applications where there is no enforcement of checked exceptions at all.
|
||||
* @since 6.2
|
||||
* @see Transactional#rollbackFor()
|
||||
* @see Transactional#noRollbackFor()
|
||||
* @see jakarta.transaction.Transactional#rollbackOn()
|
||||
* @see jakarta.transaction.Transactional#dontRollbackOn()
|
||||
*/
|
||||
RollbackOn rollbackOn() default RollbackOn.RUNTIME_EXCEPTIONS;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -55,13 +55,6 @@ public class ProxyTransactionManagementConfiguration extends AbstractTransaction
|
||||
return advisor;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
public TransactionAttributeSource transactionAttributeSource() {
|
||||
// Accept protected @Transactional methods on CGLIB proxies, as of 6.0.
|
||||
return new AnnotationTransactionAttributeSource(false);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
public TransactionInterceptor transactionInterceptor(TransactionAttributeSource transactionAttributeSource) {
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2002-2024 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.annotation;
|
||||
|
||||
/**
|
||||
* An enum for global rollback-on behavior.
|
||||
*
|
||||
* <p>Note that the default behavior matches the traditional behavior in
|
||||
* EJB CMT and JTA, with the latter having rollback rules similar to Spring.
|
||||
* A global switch to trigger a rollback on any exception affects Spring's
|
||||
* {@link Transactional} as well as {@link jakarta.transaction.Transactional}
|
||||
* but leaves the non-rule-based {@link jakarta.ejb.TransactionAttribute} as-is.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 6.2
|
||||
* @see EnableTransactionManagement#rollbackOn()
|
||||
* @see org.springframework.transaction.interceptor.RuleBasedTransactionAttribute
|
||||
*/
|
||||
public enum RollbackOn {
|
||||
|
||||
/**
|
||||
* The default rollback-on behavior: rollback on
|
||||
* {@link RuntimeException RuntimeExceptions} as well as {@link Error Errors}.
|
||||
* @see org.springframework.transaction.interceptor.RollbackRuleAttribute#ROLLBACK_ON_RUNTIME_EXCEPTIONS
|
||||
*/
|
||||
RUNTIME_EXCEPTIONS,
|
||||
|
||||
/**
|
||||
* The alternative mode: rollback on all exceptions, including any checked
|
||||
* {@link Exception}.
|
||||
* @see org.springframework.transaction.interceptor.RollbackRuleAttribute#ROLLBACK_ON_ALL_EXCEPTIONS
|
||||
*/
|
||||
ALL_EXCEPTIONS
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -60,6 +60,8 @@ public interface TransactionAnnotationParser {
|
||||
* based on an annotation type understood by this parser.
|
||||
* <p>This essentially parses a known transaction annotation into Spring's metadata
|
||||
* attribute class. Returns {@code null} if the method/class is not transactional.
|
||||
* <p>The returned attribute will typically (but not necessarily) be of type
|
||||
* {@link org.springframework.transaction.interceptor.RuleBasedTransactionAttribute}.
|
||||
* @param element the annotated method or class
|
||||
* @return the configured transaction attribute, or {@code null} if none found
|
||||
* @see AnnotationTransactionAttributeSource#determineTransactionAttribute
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -65,6 +65,14 @@ public class RollbackRuleAttribute implements Serializable{
|
||||
public static final RollbackRuleAttribute ROLLBACK_ON_RUNTIME_EXCEPTIONS =
|
||||
new RollbackRuleAttribute(RuntimeException.class);
|
||||
|
||||
/**
|
||||
* The {@linkplain RollbackRuleAttribute rollback rule} for all
|
||||
* {@link Exception Exceptions}, including checked exceptions.
|
||||
* @since 6.2
|
||||
*/
|
||||
public static final RollbackRuleAttribute ROLLBACK_ON_ALL_EXCEPTIONS =
|
||||
new RollbackRuleAttribute(Exception.class);
|
||||
|
||||
|
||||
/**
|
||||
* Exception pattern: used when searching for matches in a thrown exception's
|
||||
|
||||
Reference in New Issue
Block a user