Skip to content

Commit 7d4c8a4

Browse files
committed
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
1 parent eb01cc0 commit 7d4c8a4

File tree

12 files changed

+293
-69
lines changed

12 files changed

+293
-69
lines changed

Diff for: framework-docs/modules/ROOT/pages/data-access/transaction/declarative/annotations.adoc

+22-3
Original file line numberDiff line numberDiff line change
@@ -436,9 +436,28 @@ properties of the `@Transactional` annotation:
436436
| Optional array of exception name patterns that must not cause rollback.
437437
|===
438438

439-
TIP: See xref:data-access/transaction/declarative/rolling-back.adoc#transaction-declarative-rollback-rules[Rollback rules] for further details
440-
on rollback rule semantics, patterns, and warnings regarding possible unintentional
441-
matches for pattern-based rollback rules.
439+
TIP: See xref:data-access/transaction/declarative/rolling-back.adoc#transaction-declarative-rollback-rules[Rollback rules]
440+
for further details on rollback rule semantics, patterns, and warnings
441+
regarding possible unintentional matches for pattern-based rollback rules.
442+
443+
[NOTE]
444+
====
445+
As of 6.2, you can globally change the default rollback behavior: e.g. through
446+
`@EnableTransactionManagement(rollbackOn=ALL_EXCEPTIONS)`, leading to a rollback
447+
for all exceptions raised within a transaction, including any checked exception.
448+
For further customizations, `AnnotationTransactionAttributeSource` provides an
449+
`addDefaultRollbackRule(RollbackRuleAttribute)` method for custom default rules.
450+
451+
Note that transaction-specific rollback rules override the default behavior but
452+
retain the chosen default for unspecified exceptions. This is the case for
453+
Spring's `@Transactional` as well as JTA's `jakarta.transaction.Transactional`.
454+
455+
Unless you rely on EJB-style business exceptions with commit behavior, it is
456+
advisable to switch to `ALL_EXCEPTIONS` for a consistent rollback even in case
457+
of a (potentially accidental) checked exception. Also, it is advisable to make
458+
that switch for Kotlin-based applications where there is no enforcement of
459+
checked exceptions at all.
460+
====
442461

443462
Currently, you cannot have explicit control over the name of a transaction, where 'name'
444463
means the transaction name that appears in a transaction monitor and in logging output.

Diff for: spring-aspects/src/main/java/org/springframework/transaction/aspectj/AspectJJtaTransactionManagementConfiguration.java

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@
2323
import org.springframework.transaction.annotation.EnableTransactionManagement;
2424
import org.springframework.transaction.annotation.TransactionManagementConfigurationSelector;
2525
import org.springframework.transaction.config.TransactionManagementConfigUtils;
26+
import org.springframework.transaction.interceptor.TransactionAttributeSource;
2627

2728
/**
2829
* {@code @Configuration} class that registers the Spring infrastructure beans necessary
@@ -35,14 +36,15 @@
3536
* @see EnableTransactionManagement
3637
* @see TransactionManagementConfigurationSelector
3738
*/
38-
@Configuration
39+
@Configuration(proxyBeanMethods = false)
3940
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
4041
public class AspectJJtaTransactionManagementConfiguration extends AspectJTransactionManagementConfiguration {
4142

4243
@Bean(name = TransactionManagementConfigUtils.JTA_TRANSACTION_ASPECT_BEAN_NAME)
4344
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
44-
public JtaAnnotationTransactionAspect jtaTransactionAspect() {
45+
public JtaAnnotationTransactionAspect jtaTransactionAspect(TransactionAttributeSource transactionAttributeSource) {
4546
JtaAnnotationTransactionAspect txAspect = JtaAnnotationTransactionAspect.aspectOf();
47+
txAspect.setTransactionAttributeSource(transactionAttributeSource);
4648
if (this.txManager != null) {
4749
txAspect.setTransactionManager(this.txManager);
4850
}

Diff for: spring-aspects/src/main/java/org/springframework/transaction/aspectj/AspectJTransactionManagementConfiguration.java

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@
2424
import org.springframework.transaction.annotation.EnableTransactionManagement;
2525
import org.springframework.transaction.annotation.TransactionManagementConfigurationSelector;
2626
import org.springframework.transaction.config.TransactionManagementConfigUtils;
27+
import org.springframework.transaction.interceptor.TransactionAttributeSource;
2728

2829
/**
2930
* {@code @Configuration} class that registers the Spring infrastructure beans necessary
@@ -37,14 +38,15 @@
3738
* @see TransactionManagementConfigurationSelector
3839
* @see AspectJJtaTransactionManagementConfiguration
3940
*/
40-
@Configuration
41+
@Configuration(proxyBeanMethods = false)
4142
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
4243
public class AspectJTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {
4344

4445
@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ASPECT_BEAN_NAME)
4546
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
46-
public AnnotationTransactionAspect transactionAspect() {
47+
public AnnotationTransactionAspect transactionAspect(TransactionAttributeSource transactionAttributeSource) {
4748
AnnotationTransactionAspect txAspect = AnnotationTransactionAspect.aspectOf();
49+
txAspect.setTransactionAttributeSource(transactionAttributeSource);
4850
if (this.txManager != null) {
4951
txAspect.setTransactionManager(this.txManager);
5052
}

Diff for: spring-context/src/main/java/org/springframework/cache/annotation/AnnotationCacheOperationSource.java

+18-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,10 +19,8 @@
1919
import java.io.Serializable;
2020
import java.lang.reflect.Method;
2121
import java.util.ArrayList;
22-
import java.util.Arrays;
2322
import java.util.Collection;
2423
import java.util.Collections;
25-
import java.util.LinkedHashSet;
2624
import java.util.Set;
2725

2826
import org.springframework.cache.interceptor.AbstractFallbackCacheOperationSource;
@@ -47,17 +45,17 @@
4745
@SuppressWarnings("serial")
4846
public class AnnotationCacheOperationSource extends AbstractFallbackCacheOperationSource implements Serializable {
4947

50-
private final boolean publicMethodsOnly;
51-
5248
private final Set<CacheAnnotationParser> annotationParsers;
5349

50+
private boolean publicMethodsOnly = true;
51+
5452

5553
/**
5654
* Create a default AnnotationCacheOperationSource, supporting public methods
5755
* that carry the {@code Cacheable} and {@code CacheEvict} annotations.
5856
*/
5957
public AnnotationCacheOperationSource() {
60-
this(true);
58+
this.annotationParsers = Collections.singleton(new SpringCacheAnnotationParser());
6159
}
6260

6361
/**
@@ -66,18 +64,18 @@ public AnnotationCacheOperationSource() {
6664
* @param publicMethodsOnly whether to support only annotated public methods
6765
* typically for use with proxy-based AOP), or protected/private methods as well
6866
* (typically used with AspectJ class weaving)
67+
* @see #setPublicMethodsOnly
6968
*/
7069
public AnnotationCacheOperationSource(boolean publicMethodsOnly) {
70+
this();
7171
this.publicMethodsOnly = publicMethodsOnly;
72-
this.annotationParsers = Collections.singleton(new SpringCacheAnnotationParser());
7372
}
7473

7574
/**
7675
* Create a custom AnnotationCacheOperationSource.
7776
* @param annotationParser the CacheAnnotationParser to use
7877
*/
7978
public AnnotationCacheOperationSource(CacheAnnotationParser annotationParser) {
80-
this.publicMethodsOnly = true;
8179
Assert.notNull(annotationParser, "CacheAnnotationParser must not be null");
8280
this.annotationParsers = Collections.singleton(annotationParser);
8381
}
@@ -87,22 +85,30 @@ public AnnotationCacheOperationSource(CacheAnnotationParser annotationParser) {
8785
* @param annotationParsers the CacheAnnotationParser to use
8886
*/
8987
public AnnotationCacheOperationSource(CacheAnnotationParser... annotationParsers) {
90-
this.publicMethodsOnly = true;
9188
Assert.notEmpty(annotationParsers, "At least one CacheAnnotationParser needs to be specified");
92-
this.annotationParsers = new LinkedHashSet<>(Arrays.asList(annotationParsers));
89+
this.annotationParsers = Set.of(annotationParsers);
9390
}
9491

9592
/**
9693
* Create a custom AnnotationCacheOperationSource.
9794
* @param annotationParsers the CacheAnnotationParser to use
9895
*/
9996
public AnnotationCacheOperationSource(Set<CacheAnnotationParser> annotationParsers) {
100-
this.publicMethodsOnly = true;
10197
Assert.notEmpty(annotationParsers, "At least one CacheAnnotationParser needs to be specified");
10298
this.annotationParsers = annotationParsers;
10399
}
104100

105101

102+
/**
103+
* Set whether cacheable methods are expected to be public.
104+
* <p>The default is {@code true}.
105+
* @since 6.2
106+
*/
107+
public void setPublicMethodsOnly(boolean publicMethodsOnly) {
108+
this.publicMethodsOnly = publicMethodsOnly;
109+
}
110+
111+
106112
@Override
107113
public boolean isCandidateClass(Class<?> targetClass) {
108114
for (CacheAnnotationParser parser : this.annotationParsers) {
@@ -156,6 +162,7 @@ protected Collection<CacheOperation> determineCacheOperations(CacheOperationProv
156162

157163
/**
158164
* By default, only public methods can be made cacheable.
165+
* @see #setPublicMethodsOnly
159166
*/
160167
@Override
161168
protected boolean allowPublicMethodsOnly() {

Diff for: spring-tx/src/main/java/org/springframework/transaction/annotation/AbstractTransactionManagementConfiguration.java

+16-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,6 +30,8 @@
3030
import org.springframework.transaction.TransactionManager;
3131
import org.springframework.transaction.config.TransactionManagementConfigUtils;
3232
import org.springframework.transaction.event.TransactionalEventListenerFactory;
33+
import org.springframework.transaction.interceptor.RollbackRuleAttribute;
34+
import org.springframework.transaction.interceptor.TransactionAttributeSource;
3335
import org.springframework.util.CollectionUtils;
3436

3537
/**
@@ -38,6 +40,7 @@
3840
*
3941
* @author Chris Beams
4042
* @author Stephane Nicoll
43+
* @author Juergen Hoeller
4144
* @since 3.1
4245
* @see EnableTransactionManagement
4346
*/
@@ -77,6 +80,18 @@ void setConfigurers(Collection<TransactionManagementConfigurer> configurers) {
7780
}
7881

7982

83+
@Bean
84+
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
85+
public TransactionAttributeSource transactionAttributeSource() {
86+
// Accept protected @Transactional methods on CGLIB proxies, as of 6.0
87+
AnnotationTransactionAttributeSource tas = new AnnotationTransactionAttributeSource(false);
88+
// Apply default rollback rule, as of 6.2
89+
if (this.enableTx != null && this.enableTx.getEnum("rollbackOn") == RollbackOn.ALL_EXCEPTIONS) {
90+
tas.addDefaultRollbackRule(RollbackRuleAttribute.ROLLBACK_ON_ALL_EXCEPTIONS);
91+
}
92+
return tas;
93+
}
94+
8095
@Bean(name = TransactionManagementConfigUtils.TRANSACTIONAL_EVENT_LISTENER_FACTORY_BEAN_NAME)
8196
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
8297
public static TransactionalEventListenerFactory transactionalEventListenerFactory() {

0 commit comments

Comments
 (0)