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:
@@ -436,9 +436,28 @@ properties of the `@Transactional` annotation:
|
||||
| Optional array of exception name patterns that must not cause rollback.
|
||||
|===
|
||||
|
||||
TIP: See xref:data-access/transaction/declarative/rolling-back.adoc#transaction-declarative-rollback-rules[Rollback rules] for further details
|
||||
on rollback rule semantics, patterns, and warnings regarding possible unintentional
|
||||
matches for pattern-based rollback rules.
|
||||
TIP: See xref:data-access/transaction/declarative/rolling-back.adoc#transaction-declarative-rollback-rules[Rollback rules]
|
||||
for further details on rollback rule semantics, patterns, and warnings
|
||||
regarding possible unintentional matches for pattern-based rollback rules.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
As of 6.2, you can globally change the default rollback behavior: e.g. through
|
||||
`@EnableTransactionManagement(rollbackOn=ALL_EXCEPTIONS)`, leading to a rollback
|
||||
for all exceptions raised within a transaction, including any checked exception.
|
||||
For further customizations, `AnnotationTransactionAttributeSource` provides an
|
||||
`addDefaultRollbackRule(RollbackRuleAttribute)` method for custom default rules.
|
||||
|
||||
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 `@Transactional` as well as JTA's `jakarta.transaction.Transactional`.
|
||||
|
||||
Unless you rely on EJB-style business exceptions with commit behavior, it is
|
||||
advisable to switch to `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.
|
||||
====
|
||||
|
||||
Currently, you cannot have explicit control over the name of a transaction, where 'name'
|
||||
means the transaction name that appears in a transaction monitor and in logging output.
|
||||
|
||||
@@ -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.
|
||||
@@ -23,6 +23,7 @@ import org.springframework.context.annotation.Role;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
import org.springframework.transaction.annotation.TransactionManagementConfigurationSelector;
|
||||
import org.springframework.transaction.config.TransactionManagementConfigUtils;
|
||||
import org.springframework.transaction.interceptor.TransactionAttributeSource;
|
||||
|
||||
/**
|
||||
* {@code @Configuration} class that registers the Spring infrastructure beans necessary
|
||||
@@ -35,14 +36,15 @@ import org.springframework.transaction.config.TransactionManagementConfigUtils;
|
||||
* @see EnableTransactionManagement
|
||||
* @see TransactionManagementConfigurationSelector
|
||||
*/
|
||||
@Configuration
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
public class AspectJJtaTransactionManagementConfiguration extends AspectJTransactionManagementConfiguration {
|
||||
|
||||
@Bean(name = TransactionManagementConfigUtils.JTA_TRANSACTION_ASPECT_BEAN_NAME)
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
public JtaAnnotationTransactionAspect jtaTransactionAspect() {
|
||||
public JtaAnnotationTransactionAspect jtaTransactionAspect(TransactionAttributeSource transactionAttributeSource) {
|
||||
JtaAnnotationTransactionAspect txAspect = JtaAnnotationTransactionAspect.aspectOf();
|
||||
txAspect.setTransactionAttributeSource(transactionAttributeSource);
|
||||
if (this.txManager != null) {
|
||||
txAspect.setTransactionManager(this.txManager);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -24,6 +24,7 @@ import org.springframework.transaction.annotation.AbstractTransactionManagementC
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
import org.springframework.transaction.annotation.TransactionManagementConfigurationSelector;
|
||||
import org.springframework.transaction.config.TransactionManagementConfigUtils;
|
||||
import org.springframework.transaction.interceptor.TransactionAttributeSource;
|
||||
|
||||
/**
|
||||
* {@code @Configuration} class that registers the Spring infrastructure beans necessary
|
||||
@@ -37,14 +38,15 @@ import org.springframework.transaction.config.TransactionManagementConfigUtils;
|
||||
* @see TransactionManagementConfigurationSelector
|
||||
* @see AspectJJtaTransactionManagementConfiguration
|
||||
*/
|
||||
@Configuration
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
public class AspectJTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {
|
||||
|
||||
@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ASPECT_BEAN_NAME)
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
public AnnotationTransactionAspect transactionAspect() {
|
||||
public AnnotationTransactionAspect transactionAspect(TransactionAttributeSource transactionAttributeSource) {
|
||||
AnnotationTransactionAspect txAspect = AnnotationTransactionAspect.aspectOf();
|
||||
txAspect.setTransactionAttributeSource(transactionAttributeSource);
|
||||
if (this.txManager != null) {
|
||||
txAspect.setTransactionManager(this.txManager);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -19,10 +19,8 @@ package org.springframework.cache.annotation;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.cache.interceptor.AbstractFallbackCacheOperationSource;
|
||||
@@ -47,17 +45,17 @@ import org.springframework.util.Assert;
|
||||
@SuppressWarnings("serial")
|
||||
public class AnnotationCacheOperationSource extends AbstractFallbackCacheOperationSource implements Serializable {
|
||||
|
||||
private final boolean publicMethodsOnly;
|
||||
|
||||
private final Set<CacheAnnotationParser> annotationParsers;
|
||||
|
||||
private boolean publicMethodsOnly = true;
|
||||
|
||||
|
||||
/**
|
||||
* Create a default AnnotationCacheOperationSource, supporting public methods
|
||||
* that carry the {@code Cacheable} and {@code CacheEvict} annotations.
|
||||
*/
|
||||
public AnnotationCacheOperationSource() {
|
||||
this(true);
|
||||
this.annotationParsers = Collections.singleton(new SpringCacheAnnotationParser());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,10 +64,11 @@ public class AnnotationCacheOperationSource extends AbstractFallbackCacheOperati
|
||||
* @param publicMethodsOnly whether to support only annotated public methods
|
||||
* typically for use with proxy-based AOP), or protected/private methods as well
|
||||
* (typically used with AspectJ class weaving)
|
||||
* @see #setPublicMethodsOnly
|
||||
*/
|
||||
public AnnotationCacheOperationSource(boolean publicMethodsOnly) {
|
||||
this();
|
||||
this.publicMethodsOnly = publicMethodsOnly;
|
||||
this.annotationParsers = Collections.singleton(new SpringCacheAnnotationParser());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,7 +76,6 @@ public class AnnotationCacheOperationSource extends AbstractFallbackCacheOperati
|
||||
* @param annotationParser the CacheAnnotationParser to use
|
||||
*/
|
||||
public AnnotationCacheOperationSource(CacheAnnotationParser annotationParser) {
|
||||
this.publicMethodsOnly = true;
|
||||
Assert.notNull(annotationParser, "CacheAnnotationParser must not be null");
|
||||
this.annotationParsers = Collections.singleton(annotationParser);
|
||||
}
|
||||
@@ -87,9 +85,8 @@ public class AnnotationCacheOperationSource extends AbstractFallbackCacheOperati
|
||||
* @param annotationParsers the CacheAnnotationParser to use
|
||||
*/
|
||||
public AnnotationCacheOperationSource(CacheAnnotationParser... annotationParsers) {
|
||||
this.publicMethodsOnly = true;
|
||||
Assert.notEmpty(annotationParsers, "At least one CacheAnnotationParser needs to be specified");
|
||||
this.annotationParsers = new LinkedHashSet<>(Arrays.asList(annotationParsers));
|
||||
this.annotationParsers = Set.of(annotationParsers);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,12 +94,21 @@ public class AnnotationCacheOperationSource extends AbstractFallbackCacheOperati
|
||||
* @param annotationParsers the CacheAnnotationParser to use
|
||||
*/
|
||||
public AnnotationCacheOperationSource(Set<CacheAnnotationParser> annotationParsers) {
|
||||
this.publicMethodsOnly = true;
|
||||
Assert.notEmpty(annotationParsers, "At least one CacheAnnotationParser needs to be specified");
|
||||
this.annotationParsers = annotationParsers;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set whether cacheable methods are expected to be public.
|
||||
* <p>The default is {@code true}.
|
||||
* @since 6.2
|
||||
*/
|
||||
public void setPublicMethodsOnly(boolean publicMethodsOnly) {
|
||||
this.publicMethodsOnly = publicMethodsOnly;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isCandidateClass(Class<?> targetClass) {
|
||||
for (CacheAnnotationParser parser : this.annotationParsers) {
|
||||
@@ -156,6 +162,7 @@ public class AnnotationCacheOperationSource extends AbstractFallbackCacheOperati
|
||||
|
||||
/**
|
||||
* By default, only public methods can be made cacheable.
|
||||
* @see #setPublicMethodsOnly
|
||||
*/
|
||||
@Override
|
||||
protected boolean allowPublicMethodsOnly() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -46,6 +46,7 @@ import org.springframework.transaction.testfixture.CallCountingTransactionManage
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatException;
|
||||
import static org.springframework.transaction.annotation.RollbackOn.ALL_EXCEPTIONS;
|
||||
|
||||
/**
|
||||
* Tests demonstrating use of @EnableTransactionManagement @Configuration classes.
|
||||
@@ -226,8 +227,8 @@ class EnableTransactionManagementTests {
|
||||
// should throw CNFE when trying to load AnnotationTransactionAspect.
|
||||
// Do you actually have org.springframework.aspects on the classpath?
|
||||
assertThatException()
|
||||
.isThrownBy(() -> new AnnotationConfigApplicationContext(EnableAspectjTxConfig.class, TxManagerConfig.class))
|
||||
.withMessageContaining("AspectJJtaTransactionManagementConfiguration");
|
||||
.isThrownBy(() -> new AnnotationConfigApplicationContext(EnableAspectjTxConfig.class, TxManagerConfig.class))
|
||||
.withMessageContaining("AspectJJtaTransactionManagementConfiguration");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -288,8 +289,8 @@ class EnableTransactionManagementTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void gh24502AppliesTransactionOnlyOnAnnotatedInterface() {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Gh24502ConfigA.class);
|
||||
void gh24502AppliesTransactionFromAnnotatedInterface() {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Gh24502Config.class);
|
||||
Object bean = ctx.getBean("testBean");
|
||||
CallCountingTransactionManager txManager = ctx.getBean(CallCountingTransactionManager.class);
|
||||
|
||||
@@ -302,6 +303,36 @@ class EnableTransactionManagementTests {
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void gh23473AppliesToRuntimeExceptionOnly() {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Gh23473ConfigA.class);
|
||||
TestServiceWithRollback bean = ctx.getBean("testBean", TestServiceWithRollback.class);
|
||||
CallCountingTransactionManager txManager = ctx.getBean(CallCountingTransactionManager.class);
|
||||
|
||||
assertThatException().isThrownBy(bean::methodOne);
|
||||
assertThatException().isThrownBy(bean::methodTwo);
|
||||
assertThat(txManager.begun).isEqualTo(2);
|
||||
assertThat(txManager.commits).isEqualTo(2);
|
||||
assertThat(txManager.rollbacks).isEqualTo(0);
|
||||
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void gh23473AppliesRollbackOnAnyException() {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Gh23473ConfigB.class);
|
||||
TestServiceWithRollback bean = ctx.getBean("testBean", TestServiceWithRollback.class);
|
||||
CallCountingTransactionManager txManager = ctx.getBean(CallCountingTransactionManager.class);
|
||||
|
||||
assertThatException().isThrownBy(bean::methodOne);
|
||||
assertThatException().isThrownBy(bean::methodTwo);
|
||||
assertThat(txManager.begun).isEqualTo(2);
|
||||
assertThat(txManager.commits).isEqualTo(0);
|
||||
assertThat(txManager.rollbacks).isEqualTo(2);
|
||||
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
|
||||
@Service
|
||||
public static class TransactionalTestBean {
|
||||
@@ -590,7 +621,7 @@ class EnableTransactionManagementTests {
|
||||
|
||||
@Configuration
|
||||
@EnableTransactionManagement
|
||||
static class Gh24502ConfigA {
|
||||
static class Gh24502Config {
|
||||
|
||||
@Bean
|
||||
public MixedTransactionalTestService testBean() {
|
||||
@@ -603,4 +634,50 @@ class EnableTransactionManagementTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class TestServiceWithRollback {
|
||||
|
||||
@Transactional
|
||||
public void methodOne() throws Exception {
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void methodTwo() throws Exception {
|
||||
throw new Exception();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
@EnableTransactionManagement
|
||||
static class Gh23473ConfigA {
|
||||
|
||||
@Bean
|
||||
public TestServiceWithRollback testBean() {
|
||||
return new TestServiceWithRollback();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PlatformTransactionManager txManager() {
|
||||
return new CallCountingTransactionManager();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
@EnableTransactionManagement(rollbackOn = ALL_EXCEPTIONS)
|
||||
static class Gh23473ConfigB {
|
||||
|
||||
@Bean
|
||||
public TestServiceWithRollback testBean() {
|
||||
return new TestServiceWithRollback();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PlatformTransactionManager txManager() {
|
||||
return new CallCountingTransactionManager();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user