Skip to content

Commit b3e5f86

Browse files
committed
Polish rollback rule support
1 parent 340f41a commit b3e5f86

File tree

5 files changed

+188
-143
lines changed

5 files changed

+188
-143
lines changed
Lines changed: 44 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 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.
@@ -22,21 +22,21 @@
2222
import org.springframework.util.Assert;
2323

2424
/**
25-
* Rule determining whether or not a given exception (and any subclasses)
26-
* should cause a rollback.
25+
* Rule determining whether or not a given exception should cause a rollback.
2726
*
2827
* <p>Multiple such rules can be applied to determine whether a transaction
2928
* should commit or rollback after an exception has been thrown.
3029
*
3130
* @author Rod Johnson
31+
* @author Sam Brannen
3232
* @since 09.04.2003
3333
* @see NoRollbackRuleAttribute
3434
*/
3535
@SuppressWarnings("serial")
3636
public class RollbackRuleAttribute implements Serializable{
3737

3838
/**
39-
* The {@link RollbackRuleAttribute rollback rule} for
39+
* The {@linkplain RollbackRuleAttribute rollback rule} for
4040
* {@link RuntimeException RuntimeExceptions}.
4141
*/
4242
public static final RollbackRuleAttribute ROLLBACK_ON_RUNTIME_EXCEPTIONS =
@@ -48,71 +48,81 @@ public class RollbackRuleAttribute implements Serializable{
4848
* This way does multiple string comparisons, but how often do we decide
4949
* whether to roll back a transaction following an exception?
5050
*/
51-
private final String exceptionName;
51+
private final String exceptionPattern;
5252

5353

5454
/**
55-
* Create a new instance of the {@code RollbackRuleAttribute} class.
55+
* Create a new instance of the {@code RollbackRuleAttribute} class
56+
* for the given {@code exceptionType}.
5657
* <p>This is the preferred way to construct a rollback rule that matches
57-
* the supplied {@link Exception} class, its subclasses, and its nested classes.
58-
* @param clazz throwable class; must be {@link Throwable} or a subclass
58+
* the supplied exception type, its subclasses, and its nested classes.
59+
* @param exceptionType exception type; must be {@link Throwable} or a subclass
5960
* of {@code Throwable}
60-
* @throws IllegalArgumentException if the supplied {@code clazz} is
61+
* @throws IllegalArgumentException if the supplied {@code exceptionType} is
6162
* not a {@code Throwable} type or is {@code null}
6263
*/
63-
public RollbackRuleAttribute(Class<?> clazz) {
64-
Assert.notNull(clazz, "'clazz' cannot be null");
65-
if (!Throwable.class.isAssignableFrom(clazz)) {
64+
public RollbackRuleAttribute(Class<?> exceptionType) {
65+
Assert.notNull(exceptionType, "'exceptionType' cannot be null");
66+
if (!Throwable.class.isAssignableFrom(exceptionType)) {
6667
throw new IllegalArgumentException(
67-
"Cannot construct rollback rule from [" + clazz.getName() + "]: it's not a Throwable");
68+
"Cannot construct rollback rule from [" + exceptionType.getName() + "]: it's not a Throwable");
6869
}
69-
this.exceptionName = clazz.getName();
70+
this.exceptionPattern = exceptionType.getName();
7071
}
7172

7273
/**
7374
* Create a new instance of the {@code RollbackRuleAttribute} class
74-
* for the given {@code exceptionName}.
75+
* for the given {@code exceptionPattern}.
7576
* <p>This can be a substring, with no wildcard support at present. A value
7677
* of "ServletException" would match
7778
* {@code javax.servlet.ServletException} and subclasses, for example.
7879
* <p><b>NB:</b> Consider carefully how specific the pattern is, and
7980
* whether to include package information (which is not mandatory). For
8081
* example, "Exception" will match nearly anything, and will probably hide
8182
* other rules. "java.lang.Exception" would be correct if "Exception" was
82-
* meant to define a rule for all checked exceptions. With more unusual
83+
* meant to define a rule for all checked exceptions. With more unique
8384
* exception names such as "BaseBusinessException" there's no need to use a
8485
* fully package-qualified name.
85-
* @param exceptionName the exception name pattern; can also be a fully
86+
* @param exceptionPattern the exception name pattern; can also be a fully
8687
* package-qualified class name
87-
* @throws IllegalArgumentException if the supplied
88-
* {@code exceptionName} is {@code null} or empty
88+
* @throws IllegalArgumentException if the supplied {@code exceptionPattern}
89+
* is {@code null} or empty
8990
*/
90-
public RollbackRuleAttribute(String exceptionName) {
91-
Assert.hasText(exceptionName, "'exceptionName' cannot be null or empty");
92-
this.exceptionName = exceptionName;
91+
public RollbackRuleAttribute(String exceptionPattern) {
92+
Assert.hasText(exceptionPattern, "'exceptionPattern' cannot be null or empty");
93+
this.exceptionPattern = exceptionPattern;
9394
}
9495

9596

9697
/**
97-
* Return the pattern for the exception name.
98+
* Get the configured exception name pattern that this rule uses for matching.
99+
* @see #getDepth(Throwable)
98100
*/
99101
public String getExceptionName() {
100-
return this.exceptionName;
102+
return this.exceptionPattern;
101103
}
102104

103105
/**
104-
* Return the depth of the superclass matching.
105-
* <p>{@code 0} means {@code ex} matches exactly. Returns
106-
* {@code -1} if there is no match. Otherwise, returns depth with the
107-
* lowest depth winning.
106+
* Return the depth of the superclass matching, with the following semantics.
107+
* <ul>
108+
* <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>
110+
* <li>Any other positive value means this rule matches the supplied {@code exception}
111+
* within the superclass hierarchy, where the value is the number of levels in the
112+
* class hierarchy between the supplied {@code exception} and the exception against
113+
* which this rule matches directly.</li>
114+
* </ul>
115+
* <p>When comparing roll back rules that match against a given exception, a rule
116+
* with a lower matching depth wins. For example, a direct match ({@code depth == 0})
117+
* wins over a match in the superclass hierarchy ({@code depth > 0}).
108118
*/
109-
public int getDepth(Throwable ex) {
110-
return getDepth(ex.getClass(), 0);
119+
public int getDepth(Throwable exception) {
120+
return getDepth(exception.getClass(), 0);
111121
}
112122

113123

114124
private int getDepth(Class<?> exceptionClass, int depth) {
115-
if (exceptionClass.getName().contains(this.exceptionName)) {
125+
if (exceptionClass.getName().contains(this.exceptionPattern)) {
116126
// Found it!
117127
return depth;
118128
}
@@ -133,17 +143,17 @@ public boolean equals(@Nullable Object other) {
133143
return false;
134144
}
135145
RollbackRuleAttribute rhs = (RollbackRuleAttribute) other;
136-
return this.exceptionName.equals(rhs.exceptionName);
146+
return this.exceptionPattern.equals(rhs.exceptionPattern);
137147
}
138148

139149
@Override
140150
public int hashCode() {
141-
return this.exceptionName.hashCode();
151+
return this.exceptionPattern.hashCode();
142152
}
143153

144154
@Override
145155
public String toString() {
146-
return "RollbackRuleAttribute with pattern [" + this.exceptionName + "]";
156+
return "RollbackRuleAttribute with pattern [" + this.exceptionPattern + "]";
147157
}
148158

149159
}

spring-tx/src/test/java/org/springframework/transaction/interceptor/MyRuntimeException.java

Lines changed: 7 additions & 1 deletion
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.
@@ -25,7 +25,13 @@
2525
*/
2626
@SuppressWarnings("serial")
2727
class MyRuntimeException extends NestedRuntimeException {
28+
29+
public MyRuntimeException() {
30+
super("");
31+
}
32+
2833
public MyRuntimeException(String msg) {
2934
super(msg);
3035
}
36+
3137
}

spring-tx/src/test/java/org/springframework/transaction/interceptor/RollbackRuleAttributeTests.java

Lines changed: 90 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.io.IOException;
2020

21+
import org.junit.jupiter.api.Nested;
2122
import org.junit.jupiter.api.Test;
2223

2324
import org.springframework.beans.FatalBeanException;
@@ -36,65 +37,105 @@
3637
*/
3738
class RollbackRuleAttributeTests {
3839

39-
@Test
40-
void constructorArgumentMustBeThrowableClassWithNonThrowableType() {
41-
assertThatIllegalArgumentException().isThrownBy(() -> new RollbackRuleAttribute(Object.class));
42-
}
40+
@Nested
41+
class ExceptionPatternTests {
4342

44-
@Test
45-
void constructorArgumentMustBeThrowableClassWithNullThrowableType() {
46-
assertThatIllegalArgumentException().isThrownBy(() -> new RollbackRuleAttribute((Class<?>) null));
47-
}
43+
@Test
44+
void constructorPreconditions() {
45+
assertThatIllegalArgumentException().isThrownBy(() -> new RollbackRuleAttribute((String) null));
46+
}
4847

49-
@Test
50-
void constructorArgumentMustBeStringWithNull() {
51-
assertThatIllegalArgumentException().isThrownBy(() -> new RollbackRuleAttribute((String) null));
52-
}
48+
@Test
49+
void notFound() {
50+
RollbackRuleAttribute rr = new RollbackRuleAttribute(IOException.class.getName());
51+
assertThat(rr.getDepth(new MyRuntimeException())).isEqualTo(-1);
52+
}
5353

54-
@Test
55-
void notFound() {
56-
RollbackRuleAttribute rr = new RollbackRuleAttribute(IOException.class);
57-
assertThat(rr.getDepth(new MyRuntimeException(""))).isEqualTo(-1);
58-
}
54+
@Test
55+
void alwaysFoundForThrowable() {
56+
RollbackRuleAttribute rr = new RollbackRuleAttribute(Throwable.class.getName());
57+
assertThat(rr.getDepth(new MyRuntimeException())).isGreaterThan(0);
58+
assertThat(rr.getDepth(new IOException())).isGreaterThan(0);
59+
assertThat(rr.getDepth(new FatalBeanException(null, null))).isGreaterThan(0);
60+
assertThat(rr.getDepth(new RuntimeException())).isGreaterThan(0);
61+
}
5962

60-
@Test
61-
void foundImmediatelyWithString() {
62-
RollbackRuleAttribute rr = new RollbackRuleAttribute(Exception.class.getName());
63-
assertThat(rr.getDepth(new Exception())).isEqualTo(0);
64-
}
63+
@Test
64+
void foundImmediatelyWhenDirectMatch() {
65+
RollbackRuleAttribute rr = new RollbackRuleAttribute(Exception.class.getName());
66+
assertThat(rr.getDepth(new Exception())).isEqualTo(0);
67+
}
6568

66-
@Test
67-
void foundImmediatelyWithClass() {
68-
RollbackRuleAttribute rr = new RollbackRuleAttribute(Exception.class);
69-
assertThat(rr.getDepth(new Exception())).isEqualTo(0);
70-
}
69+
@Test
70+
void foundImmediatelyWhenExceptionThrownIsNestedTypeOfRegisteredException() {
71+
RollbackRuleAttribute rr = new RollbackRuleAttribute(EnclosingException.class.getName());
72+
assertThat(rr.getDepth(new EnclosingException.NestedException())).isEqualTo(0);
73+
}
7174

72-
@Test
73-
void foundInSuperclassHierarchy() {
74-
RollbackRuleAttribute rr = new RollbackRuleAttribute(Exception.class);
75-
// Exception -> RuntimeException -> NestedRuntimeException -> MyRuntimeException
76-
assertThat(rr.getDepth(new MyRuntimeException(""))).isEqualTo(3);
77-
}
75+
@Test
76+
void foundImmediatelyWhenNameOfExceptionThrownStartsWithNameOfRegisteredException() {
77+
RollbackRuleAttribute rr = new RollbackRuleAttribute(MyException.class.getName());
78+
assertThat(rr.getDepth(new MyException2())).isEqualTo(0);
79+
}
7880

79-
@Test
80-
void alwaysFoundForThrowable() {
81-
RollbackRuleAttribute rr = new RollbackRuleAttribute(Throwable.class);
82-
assertThat(rr.getDepth(new MyRuntimeException(""))).isGreaterThan(0);
83-
assertThat(rr.getDepth(new IOException())).isGreaterThan(0);
84-
assertThat(rr.getDepth(new FatalBeanException(null, null))).isGreaterThan(0);
85-
assertThat(rr.getDepth(new RuntimeException())).isGreaterThan(0);
86-
}
81+
@Test
82+
void foundInSuperclassHierarchy() {
83+
RollbackRuleAttribute rr = new RollbackRuleAttribute(Exception.class.getName());
84+
// Exception -> RuntimeException -> NestedRuntimeException -> MyRuntimeException
85+
assertThat(rr.getDepth(new MyRuntimeException())).isEqualTo(3);
86+
}
8787

88-
@Test
89-
void foundNestedExceptionInEnclosingException() {
90-
RollbackRuleAttribute rr = new RollbackRuleAttribute(EnclosingException.class);
91-
assertThat(rr.getDepth(new EnclosingException.NestedException())).isEqualTo(0);
9288
}
9389

94-
@Test
95-
void foundWhenNameOfExceptionThrownStartsWithTheNameOfTheRegisteredExceptionType() {
96-
RollbackRuleAttribute rr = new RollbackRuleAttribute(MyException.class);
97-
assertThat(rr.getDepth(new MyException2())).isEqualTo(0);
90+
@Nested
91+
class ExceptionTypeTests {
92+
93+
@Test
94+
void constructorPreconditions() {
95+
assertThatIllegalArgumentException().isThrownBy(() -> new RollbackRuleAttribute(Object.class));
96+
assertThatIllegalArgumentException().isThrownBy(() -> new RollbackRuleAttribute((Class<?>) null));
97+
}
98+
99+
@Test
100+
void notFound() {
101+
RollbackRuleAttribute rr = new RollbackRuleAttribute(IOException.class);
102+
assertThat(rr.getDepth(new MyRuntimeException())).isEqualTo(-1);
103+
}
104+
105+
@Test
106+
void alwaysFoundForThrowable() {
107+
RollbackRuleAttribute rr = new RollbackRuleAttribute(Throwable.class);
108+
assertThat(rr.getDepth(new MyRuntimeException())).isGreaterThan(0);
109+
assertThat(rr.getDepth(new IOException())).isGreaterThan(0);
110+
assertThat(rr.getDepth(new FatalBeanException(null, null))).isGreaterThan(0);
111+
assertThat(rr.getDepth(new RuntimeException())).isGreaterThan(0);
112+
}
113+
114+
@Test
115+
void foundImmediatelyWhenDirectMatch() {
116+
RollbackRuleAttribute rr = new RollbackRuleAttribute(Exception.class);
117+
assertThat(rr.getDepth(new Exception())).isEqualTo(0);
118+
}
119+
120+
@Test
121+
void foundImmediatelyWhenExceptionThrownIsNestedTypeOfRegisteredException() {
122+
RollbackRuleAttribute rr = new RollbackRuleAttribute(EnclosingException.class);
123+
assertThat(rr.getDepth(new EnclosingException.NestedException())).isEqualTo(0);
124+
}
125+
126+
@Test
127+
void foundImmediatelyWhenNameOfExceptionThrownStartsWithNameOfRegisteredException() {
128+
RollbackRuleAttribute rr = new RollbackRuleAttribute(MyException.class);
129+
assertThat(rr.getDepth(new MyException2())).isEqualTo(0);
130+
}
131+
132+
@Test
133+
void foundInSuperclassHierarchy() {
134+
RollbackRuleAttribute rr = new RollbackRuleAttribute(Exception.class);
135+
// Exception -> RuntimeException -> NestedRuntimeException -> MyRuntimeException
136+
assertThat(rr.getDepth(new MyRuntimeException())).isEqualTo(3);
137+
}
138+
98139
}
99140

100141

0 commit comments

Comments
 (0)