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:
Juergen Hoeller
2024-03-05 18:08:08 +01:00
parent eb01cc0d9d
commit 7d4c8a403e
12 changed files with 295 additions and 71 deletions

View File

@@ -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.

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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() {

View File

@@ -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() {

View File

@@ -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() {

View File

@@ -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;
}

View File

@@ -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) {

View File

@@ -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
}

View File

@@ -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

View File

@@ -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

View File

@@ -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();
}
}
}