Skip to content

Commit fa3130d

Browse files
committed
Document that TX rollback rules may result in unintentional matches
Closes gh-28125
1 parent b3e5f86 commit fa3130d

File tree

4 files changed

+209
-87
lines changed

4 files changed

+209
-87
lines changed

spring-tx/src/main/java/org/springframework/transaction/annotation/Transactional.java

Lines changed: 72 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2022 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.
@@ -37,17 +37,57 @@
3737
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html#transaction">Transaction Management</a>
3838
* section of the reference manual.
3939
*
40-
* <p>This annotation type is generally directly comparable to Spring's
40+
* <p>This annotation is generally directly comparable to Spring's
4141
* {@link org.springframework.transaction.interceptor.RuleBasedTransactionAttribute}
4242
* class, and in fact {@link AnnotationTransactionAttributeSource} will directly
43-
* convert the data to the latter class, so that Spring's transaction support code
44-
* does not have to know about annotations. If no custom rollback rules apply,
45-
* the transaction will roll back on {@link RuntimeException} and {@link Error}
46-
* but not on checked exceptions.
43+
* convert this annotation's attributes to properties in {@code RuleBasedTransactionAttribute},
44+
* so that Spring's transaction support code does not have to know about annotations.
4745
*
48-
* <p>For specific information about the semantics of this annotation's attributes,
49-
* consult the {@link org.springframework.transaction.TransactionDefinition} and
50-
* {@link org.springframework.transaction.interceptor.TransactionAttribute} javadocs.
46+
* <h3>Attribute Semantics</h3>
47+
*
48+
* <p>If no custom rollback rules are configured in this annotation, the transaction
49+
* will roll back on {@link RuntimeException} and {@link Error} but not on checked
50+
* exceptions.
51+
*
52+
* <p>Rollback rules determine if a transaction should be rolled back when a given
53+
* exception is thrown, and the rules are based on patterns. A pattern can be a
54+
* fully qualified class name or a substring of a fully qualified class name for
55+
* an exception type (which must be a subclass of {@code Throwable}), with no
56+
* wildcard support at present. For example, a value of
57+
* {@code "javax.servlet.ServletException"} or {@code "ServletException"} will
58+
* match {@code javax.servlet.ServletException} and its subclasses.
59+
*
60+
* <p>Rollback rules may be configured via {@link #rollbackFor}/{@link #noRollbackFor}
61+
* and {@link #rollbackForClassName}/{@link #noRollbackForClassName}, which allow
62+
* patterns to be specified as {@link Class} references or {@linkplain String
63+
* strings}, respectively. When an exception type is specified as a class reference
64+
* its fully qualified name will be used as the pattern. Consequently,
65+
* {@code @Transactional(rollbackFor = example.CustomException.class)} is equivalent
66+
* to {@code @Transactional(rollbackForClassName = "example.CustomException")}.
67+
*
68+
* <p><strong>WARNING:</strong> You must carefully consider how specific the pattern
69+
* is and whether to include package information (which isn't mandatory). For example,
70+
* {@code "Exception"} will match nearly anything and will probably hide other
71+
* rules. {@code "java.lang.Exception"} would be correct if {@code "Exception"}
72+
* were meant to define a rule for all checked exceptions. With more unique
73+
* exception names such as {@code "BaseBusinessException"} there is likely no
74+
* need to use the fully qualified class name for the exception pattern. Furthermore,
75+
* rollback rules may result in unintentional matches for similarly named exceptions
76+
* and nested classes. This is due to the fact that a thrown exception is considered
77+
* to be a match for a given rollback rule if the name of thrown exception contains
78+
* the exception pattern configured for the rollback rule. For example, given a
79+
* rule configured to match on {@code com.example.CustomException}, that rule
80+
* would match against an exception named
81+
* {@code com.example.CustomExceptionV2} (an exception in the same package as
82+
* {@code CustomException} but with an additional suffix) or an exception named
83+
* {@code com.example.CustomException$AnotherException}
84+
* (an exception declared as a nested class in {@code CustomException}).
85+
*
86+
* <p>For specific information about the semantics of other attributes in this
87+
* annotation, consult the {@link org.springframework.transaction.TransactionDefinition}
88+
* and {@link org.springframework.transaction.interceptor.TransactionAttribute} javadocs.
89+
*
90+
* <h3>Transaction Management</h3>
5191
*
5292
* <p>This annotation commonly works with thread-bound transactions managed by a
5393
* {@link org.springframework.transaction.PlatformTransactionManager}, exposing a
@@ -167,37 +207,33 @@
167207
boolean readOnly() default false;
168208

169209
/**
170-
* Defines zero (0) or more exception {@link Class classes}, which must be
210+
* Defines zero (0) or more exception {@linkplain Class classes}, which must be
171211
* subclasses of {@link Throwable}, indicating which exception types must cause
172212
* a transaction rollback.
173-
* <p>By default, a transaction will be rolling back on {@link RuntimeException}
213+
* <p>By default, a transaction will be rolled back on {@link RuntimeException}
174214
* and {@link Error} but not on checked exceptions (business exceptions). See
175215
* {@link org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)}
176216
* for a detailed explanation.
177217
* <p>This is the preferred way to construct a rollback rule (in contrast to
178-
* {@link #rollbackForClassName}), matching the exception class and its subclasses.
179-
* <p>Similar to {@link org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(Class clazz)}.
218+
* {@link #rollbackForClassName}), matching the exception type, its subclasses,
219+
* and its nested classes. See the {@linkplain Transactional class-level javadocs}
220+
* for further details on rollback rule semantics and warnings regarding possible
221+
* unintentional matches.
180222
* @see #rollbackForClassName
223+
* @see org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(Class)
181224
* @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)
182225
*/
183226
Class<? extends Throwable>[] rollbackFor() default {};
184227

185228
/**
186-
* Defines zero (0) or more exception names (for exceptions which must be a
229+
* Defines zero (0) or more exception name patterns (for exceptions which must be a
187230
* subclass of {@link Throwable}), indicating which exception types must cause
188231
* a transaction rollback.
189-
* <p>This can be a substring of a fully qualified class name, with no wildcard
190-
* support at present. For example, a value of {@code "ServletException"} would
191-
* match {@code javax.servlet.ServletException} and its subclasses.
192-
* <p><b>NB:</b> Consider carefully how specific the pattern is and whether
193-
* to include package information (which isn't mandatory). For example,
194-
* {@code "Exception"} will match nearly anything and will probably hide other
195-
* rules. {@code "java.lang.Exception"} would be correct if {@code "Exception"}
196-
* were meant to define a rule for all checked exceptions. With more unusual
197-
* {@link Exception} names such as {@code "BaseBusinessException"} there is no
198-
* need to use a FQN.
199-
* <p>Similar to {@link org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(String exceptionName)}.
232+
* <p>See the {@linkplain Transactional class-level javadocs} for further details
233+
* on rollback rule semantics, patterns, and warnings regarding possible
234+
* unintentional matches.
200235
* @see #rollbackFor
236+
* @see org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(String)
201237
* @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)
202238
*/
203239
String[] rollbackForClassName() default {};
@@ -206,23 +242,26 @@
206242
* Defines zero (0) or more exception {@link Class Classes}, which must be
207243
* subclasses of {@link Throwable}, indicating which exception types must
208244
* <b>not</b> cause a transaction rollback.
209-
* <p>This is the preferred way to construct a rollback rule (in contrast
210-
* to {@link #noRollbackForClassName}), matching the exception class and
211-
* its subclasses.
212-
* <p>Similar to {@link org.springframework.transaction.interceptor.NoRollbackRuleAttribute#NoRollbackRuleAttribute(Class clazz)}.
245+
* <p>This is the preferred way to construct a rollback rule (in contrast to
246+
* {@link #noRollbackForClassName}), matching the exception type, its subclasses,
247+
* and its nested classes. See the {@linkplain Transactional class-level javadocs}
248+
* for further details on rollback rule semantics and warnings regarding possible
249+
* unintentional matches.
213250
* @see #noRollbackForClassName
251+
* @see org.springframework.transaction.interceptor.NoRollbackRuleAttribute#NoRollbackRuleAttribute(Class)
214252
* @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)
215253
*/
216254
Class<? extends Throwable>[] noRollbackFor() default {};
217255

218256
/**
219-
* Defines zero (0) or more exception names (for exceptions which must be a
257+
* Defines zero (0) or more exception name patterns (for exceptions which must be a
220258
* subclass of {@link Throwable}) indicating which exception types must <b>not</b>
221259
* cause a transaction rollback.
222-
* <p>See the description of {@link #rollbackForClassName} for further
223-
* information on how the specified names are treated.
224-
* <p>Similar to {@link org.springframework.transaction.interceptor.NoRollbackRuleAttribute#NoRollbackRuleAttribute(String exceptionName)}.
260+
* <p>See the {@linkplain Transactional class-level javadocs} for further details
261+
* on rollback rule semantics, patterns, and warnings regarding possible
262+
* unintentional matches.
225263
* @see #noRollbackFor
264+
* @see org.springframework.transaction.interceptor.NoRollbackRuleAttribute#NoRollbackRuleAttribute(String)
226265
* @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)
227266
*/
228267
String[] noRollbackForClassName() default {};

spring-tx/src/main/java/org/springframework/transaction/interceptor/NoRollbackRuleAttribute.java

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2022 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.
@@ -21,29 +21,36 @@
2121
* to the {@code RollbackRuleAttribute} superclass.
2222
*
2323
* @author Rod Johnson
24+
* @author Sam Brannen
2425
* @since 09.04.2003
2526
*/
2627
@SuppressWarnings("serial")
2728
public class NoRollbackRuleAttribute extends RollbackRuleAttribute {
2829

2930
/**
3031
* Create a new instance of the {@code NoRollbackRuleAttribute} class
31-
* for the supplied {@link Throwable} class.
32-
* @param clazz the {@code Throwable} class
32+
* for the given {@code exceptionType}.
33+
* @param exceptionType exception type; must be {@link Throwable} or a subclass
34+
* of {@code Throwable}
35+
* @throws IllegalArgumentException if the supplied {@code exceptionType} is
36+
* not a {@code Throwable} type or is {@code null}
3337
* @see RollbackRuleAttribute#RollbackRuleAttribute(Class)
3438
*/
35-
public NoRollbackRuleAttribute(Class<?> clazz) {
36-
super(clazz);
39+
public NoRollbackRuleAttribute(Class<?> exceptionType) {
40+
super(exceptionType);
3741
}
3842

3943
/**
4044
* Create a new instance of the {@code NoRollbackRuleAttribute} class
41-
* for the supplied {@code exceptionName}.
42-
* @param exceptionName the exception name pattern
45+
* for the supplied {@code exceptionPattern}.
46+
* @param exceptionPattern the exception name pattern; can also be a fully
47+
* package-qualified class name
48+
* @throws IllegalArgumentException if the supplied {@code exceptionPattern}
49+
* is {@code null} or empty
4350
* @see RollbackRuleAttribute#RollbackRuleAttribute(String)
4451
*/
45-
public NoRollbackRuleAttribute(String exceptionName) {
46-
super(exceptionName);
52+
public NoRollbackRuleAttribute(String exceptionPattern) {
53+
super(exceptionPattern);
4754
}
4855

4956
@Override

spring-tx/src/main/java/org/springframework/transaction/interceptor/RollbackRuleAttribute.java

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,22 @@
2727
* <p>Multiple such rules can be applied to determine whether a transaction
2828
* should commit or rollback after an exception has been thrown.
2929
*
30+
* <p>Each rule is based on an exception pattern which can be a fully qualified
31+
* class name or a substring of a fully qualified class name for an exception
32+
* type (which must be a subclass of {@code Throwable}), with no wildcard support
33+
* at present. For example, a value of {@code "javax.servlet.ServletException"}
34+
* or {@code "ServletException"} would match {@code javax.servlet.ServletException}
35+
* and its subclasses.
36+
*
37+
* <p>An exception pattern can be specified as a {@link Class} reference or a
38+
* {@link String} in {@link #RollbackRuleAttribute(Class)} and
39+
* {@link #RollbackRuleAttribute(String)}, respectively. When an exception type
40+
* is specified as a class reference its fully qualified name will be used as the
41+
* pattern. See the javadocs for
42+
* {@link org.springframework.transaction.annotation.Transactional @Transactional}
43+
* for further details on rollback rule semantics, patterns, and warnings regarding
44+
* possible unintentional matches.
45+
*
3046
* @author Rod Johnson
3147
* @author Sam Brannen
3248
* @since 09.04.2003
@@ -56,6 +72,10 @@ public class RollbackRuleAttribute implements Serializable{
5672
* for the given {@code exceptionType}.
5773
* <p>This is the preferred way to construct a rollback rule that matches
5874
* the supplied exception type, its subclasses, and its nested classes.
75+
* <p>See the javadocs for
76+
* {@link org.springframework.transaction.annotation.Transactional @Transactional}
77+
* for further details on rollback rule semantics, patterns, and warnings regarding
78+
* possible unintentional matches.
5979
* @param exceptionType exception type; must be {@link Throwable} or a subclass
6080
* of {@code Throwable}
6181
* @throws IllegalArgumentException if the supplied {@code exceptionType} is
@@ -73,16 +93,10 @@ public RollbackRuleAttribute(Class<?> exceptionType) {
7393
/**
7494
* Create a new instance of the {@code RollbackRuleAttribute} class
7595
* for the given {@code exceptionPattern}.
76-
* <p>This can be a substring, with no wildcard support at present. A value
77-
* of "ServletException" would match
78-
* {@code javax.servlet.ServletException} and subclasses, for example.
79-
* <p><b>NB:</b> Consider carefully how specific the pattern is, and
80-
* whether to include package information (which is not mandatory). For
81-
* example, "Exception" will match nearly anything, and will probably hide
82-
* other rules. "java.lang.Exception" would be correct if "Exception" was
83-
* meant to define a rule for all checked exceptions. With more unique
84-
* exception names such as "BaseBusinessException" there's no need to use a
85-
* fully package-qualified name.
96+
* <p>See the javadocs for
97+
* {@link org.springframework.transaction.annotation.Transactional @Transactional}
98+
* for further details on rollback rule semantics, patterns, and warnings regarding
99+
* possible unintentional matches.
86100
* @param exceptionPattern the exception name pattern; can also be a fully
87101
* package-qualified class name
88102
* @throws IllegalArgumentException if the supplied {@code exceptionPattern}
@@ -106,7 +120,7 @@ public String getExceptionName() {
106120
* Return the depth of the superclass matching, with the following semantics.
107121
* <ul>
108122
* <li>{@code -1} means this rule does not match the supplied {@code exception}.</li>
109-
* <li>{@code 0} means this rule matches the supplied {@code exception} exactly.</li>
123+
* <li>{@code 0} means this rule matches the supplied {@code exception} directly.</li>
110124
* <li>Any other positive value means this rule matches the supplied {@code exception}
111125
* within the superclass hierarchy, where the value is the number of levels in the
112126
* class hierarchy between the supplied {@code exception} and the exception against
@@ -115,22 +129,25 @@ public String getExceptionName() {
115129
* <p>When comparing roll back rules that match against a given exception, a rule
116130
* with a lower matching depth wins. For example, a direct match ({@code depth == 0})
117131
* wins over a match in the superclass hierarchy ({@code depth > 0}).
132+
* <p>A match against a nested exception type or similarly named exception type
133+
* will return a depth signifying a match at the corresponding level in the
134+
* class hierarchy as if there had been a direct match.
118135
*/
119136
public int getDepth(Throwable exception) {
120137
return getDepth(exception.getClass(), 0);
121138
}
122139

123140

124-
private int getDepth(Class<?> exceptionClass, int depth) {
125-
if (exceptionClass.getName().contains(this.exceptionPattern)) {
141+
private int getDepth(Class<?> exceptionType, int depth) {
142+
if (exceptionType.getName().contains(this.exceptionPattern)) {
126143
// Found it!
127144
return depth;
128145
}
129146
// If we've gone as far as we can go and haven't found it...
130-
if (exceptionClass == Throwable.class) {
147+
if (exceptionType == Throwable.class) {
131148
return -1;
132149
}
133-
return getDepth(exceptionClass.getSuperclass(), depth + 1);
150+
return getDepth(exceptionType.getSuperclass(), depth + 1);
134151
}
135152

136153

0 commit comments

Comments
 (0)