Skip to content

Commit a0cc800

Browse files
committed
Concurrency and exception message refinements for test transactions
1 parent ff818d5 commit a0cc800

File tree

9 files changed

+194
-230
lines changed

9 files changed

+194
-230
lines changed

spring-test/src/main/java/org/springframework/test/context/transaction/AfterTransaction.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2018 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.
@@ -28,15 +28,15 @@
2828
* configured to run within a transaction via Spring's {@code @Transactional}
2929
* annotation.
3030
*
31-
* <p>As of Spring Framework 4.3, {@code @AfterTransaction} may be declared on
32-
* Java 8 based interface default methods.
33-
*
3431
* <p>{@code @AfterTransaction} methods declared in superclasses or as interface
3532
* default methods will be executed after those of the current test class.
3633
*
3734
* <p>As of Spring Framework 4.0, this annotation may be used as a
3835
* <em>meta-annotation</em> to create custom <em>composed annotations</em>.
3936
*
37+
* <p>As of Spring Framework 4.3, {@code @AfterTransaction} may also be
38+
* declared on Java 8 based interface default methods.
39+
*
4040
* @author Sam Brannen
4141
* @since 2.5
4242
* @see org.springframework.transaction.annotation.Transactional

spring-test/src/main/java/org/springframework/test/context/transaction/BeforeTransaction.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2018 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.
@@ -28,15 +28,15 @@
2828
* configured to run within a transaction via Spring's {@code @Transactional}
2929
* annotation.
3030
*
31-
* <p>As of Spring Framework 4.3, {@code @BeforeTransaction} may be declared on
32-
* Java 8 based interface default methods.
33-
*
3431
* <p>{@code @BeforeTransaction} methods declared in superclasses or as interface
3532
* default methods will be executed before those of the current test class.
3633
*
3734
* <p>As of Spring Framework 4.0, this annotation may be used as a
3835
* <em>meta-annotation</em> to create custom <em>composed annotations</em>.
3936
*
37+
* <p>As of Spring Framework 4.3, {@code @BeforeTransaction} may also be
38+
* declared on Java 8 based interface default methods.
39+
*
4040
* @author Sam Brannen
4141
* @since 2.5
4242
* @see org.springframework.transaction.annotation.Transactional

spring-test/src/main/java/org/springframework/test/context/transaction/TestContextTransactionUtils.java

Lines changed: 29 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2018 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.
@@ -17,7 +17,6 @@
1717
package org.springframework.test.context.transaction;
1818

1919
import java.util.Map;
20-
2120
import javax.sql.DataSource;
2221

2322
import org.apache.commons.logging.Log;
@@ -40,6 +39,7 @@
4039
/**
4140
* Utility methods for working with transactions and data access related beans
4241
* within the <em>Spring TestContext Framework</em>.
42+
*
4343
* <p>Mainly for internal use within the framework.
4444
*
4545
* @author Sam Brannen
@@ -48,8 +48,6 @@
4848
*/
4949
public abstract class TestContextTransactionUtils {
5050

51-
private static final Log logger = LogFactory.getLog(TestContextTransactionUtils.class);
52-
5351
/**
5452
* Default bean name for a {@link DataSource}: {@code "dataSource"}.
5553
*/
@@ -62,9 +60,8 @@ public abstract class TestContextTransactionUtils {
6260
public static final String DEFAULT_TRANSACTION_MANAGER_NAME = "transactionManager";
6361

6462

65-
private TestContextTransactionUtils() {
66-
/* prevent instantiation */
67-
}
63+
private static final Log logger = LogFactory.getLog(TestContextTransactionUtils.class);
64+
6865

6966
/**
7067
* Retrieve the {@link DataSource} to use for the supplied {@linkplain TestContext
@@ -82,8 +79,8 @@ private TestContextTransactionUtils() {
8279
* {@linkplain #DEFAULT_DATA_SOURCE_NAME default data source name}.
8380
* @param testContext the test context for which the {@code DataSource}
8481
* should be retrieved; never {@code null}
85-
* @param name the name of the {@code DataSource} to retrieve; may be {@code null}
86-
* or <em>empty</em>
82+
* @param name the name of the {@code DataSource} to retrieve
83+
* (may be {@code null} or <em>empty</em>)
8784
* @return the {@code DataSource} to use, or {@code null} if not found
8885
* @throws BeansException if an error occurs while retrieving an explicitly
8986
* named {@code DataSource}
@@ -94,24 +91,24 @@ public static DataSource retrieveDataSource(TestContext testContext, @Nullable S
9491
BeanFactory bf = testContext.getApplicationContext().getAutowireCapableBeanFactory();
9592

9693
try {
97-
// look up by type and explicit name
94+
// Look up by type and explicit name
9895
if (StringUtils.hasText(name)) {
9996
return bf.getBean(name, DataSource.class);
10097
}
10198
}
10299
catch (BeansException ex) {
103-
logger.error(
104-
String.format("Failed to retrieve DataSource named '%s' for test context %s", name, testContext), ex);
100+
logger.error(String.format("Failed to retrieve DataSource named '%s' for test context %s",
101+
name, testContext), ex);
105102
throw ex;
106103
}
107104

108105
try {
109106
if (bf instanceof ListableBeanFactory) {
110107
ListableBeanFactory lbf = (ListableBeanFactory) bf;
111108

112-
// look up single bean by type
113-
Map<String, DataSource> dataSources = BeanFactoryUtils.beansOfTypeIncludingAncestors(lbf,
114-
DataSource.class);
109+
// Look up single bean by type
110+
Map<String, DataSource> dataSources =
111+
BeanFactoryUtils.beansOfTypeIncludingAncestors(lbf, DataSource.class);
115112
if (dataSources.size() == 1) {
116113
return dataSources.values().iterator().next();
117114
}
@@ -153,8 +150,8 @@ public static DataSource retrieveDataSource(TestContext testContext, @Nullable S
153150
* name}.
154151
* @param testContext the test context for which the transaction manager
155152
* should be retrieved; never {@code null}
156-
* @param name the name of the transaction manager to retrieve; may be
157-
* {@code null} or <em>empty</em>
153+
* @param name the name of the transaction manager to retrieve
154+
* (may be {@code null} or <em>empty</em>)
158155
* @return the transaction manager to use, or {@code null} if not found
159156
* @throws BeansException if an error occurs while retrieving an explicitly
160157
* named transaction manager
@@ -167,39 +164,39 @@ public static PlatformTransactionManager retrieveTransactionManager(TestContext
167164
BeanFactory bf = testContext.getApplicationContext().getAutowireCapableBeanFactory();
168165

169166
try {
170-
// look up by type and explicit name
167+
// Look up by type and explicit name
171168
if (StringUtils.hasText(name)) {
172169
return bf.getBean(name, PlatformTransactionManager.class);
173170
}
174171
}
175172
catch (BeansException ex) {
176-
logger.error(String.format("Failed to retrieve transaction manager named '%s' for test context %s", name,
177-
testContext), ex);
173+
logger.error(String.format("Failed to retrieve transaction manager named '%s' for test context %s",
174+
name, testContext), ex);
178175
throw ex;
179176
}
180177

181178
try {
182179
if (bf instanceof ListableBeanFactory) {
183180
ListableBeanFactory lbf = (ListableBeanFactory) bf;
184181

185-
// look up single bean by type
186-
Map<String, PlatformTransactionManager> txMgrs = BeanFactoryUtils.beansOfTypeIncludingAncestors(lbf,
187-
PlatformTransactionManager.class);
182+
// Look up single bean by type
183+
Map<String, PlatformTransactionManager> txMgrs =
184+
BeanFactoryUtils.beansOfTypeIncludingAncestors(lbf, PlatformTransactionManager.class);
188185
if (txMgrs.size() == 1) {
189186
return txMgrs.values().iterator().next();
190187
}
191188

192189
try {
193-
// look up single bean by type, with support for 'primary' beans
190+
// Look up single bean by type, with support for 'primary' beans
194191
return bf.getBean(PlatformTransactionManager.class);
195192
}
196193
catch (BeansException ex) {
197194
logBeansException(testContext, ex, PlatformTransactionManager.class);
198195
}
199196

200-
// look up single TransactionManagementConfigurer
201-
Map<String, TransactionManagementConfigurer> configurers = BeanFactoryUtils.beansOfTypeIncludingAncestors(
202-
lbf, TransactionManagementConfigurer.class);
197+
// Look up single TransactionManagementConfigurer
198+
Map<String, TransactionManagementConfigurer> configurers =
199+
BeanFactoryUtils.beansOfTypeIncludingAncestors(lbf, TransactionManagementConfigurer.class);
203200
Assert.state(configurers.size() <= 1,
204201
"Only one TransactionManagementConfigurer may exist in the ApplicationContext");
205202
if (configurers.size() == 1) {
@@ -227,13 +224,13 @@ private static void logBeansException(TestContext testContext, BeansException ex
227224
* Create a delegating {@link TransactionAttribute} for the supplied target
228225
* {@link TransactionAttribute} and {@link TestContext}, using the names of
229226
* the test class and test method to build the name of the transaction.
230-
*
231-
* @param testContext the {@code TestContext} upon which to base the name; never {@code null}
232-
* @param targetAttribute the {@code TransactionAttribute} to delegate to; never {@code null}
227+
* @param testContext the {@code TestContext} upon which to base the name
228+
* @param targetAttribute the {@code TransactionAttribute} to delegate to
233229
* @return the delegating {@code TransactionAttribute}
234230
*/
235-
public static TransactionAttribute createDelegatingTransactionAttribute(TestContext testContext,
236-
TransactionAttribute targetAttribute) {
231+
public static TransactionAttribute createDelegatingTransactionAttribute(
232+
TestContext testContext, TransactionAttribute targetAttribute) {
233+
237234
Assert.notNull(testContext, "TestContext must not be null");
238235
Assert.notNull(targetAttribute, "Target TransactionAttribute must not be null");
239236
return new TestContextTransactionAttribute(targetAttribute, testContext);

spring-test/src/main/java/org/springframework/test/context/transaction/TestTransaction.java

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2018 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.
@@ -50,10 +50,8 @@ public static boolean isActive() {
5050
TransactionContext transactionContext = TransactionContextHolder.getCurrentTransactionContext();
5151
if (transactionContext != null) {
5252
TransactionStatus transactionStatus = transactionContext.getTransactionStatus();
53-
return (transactionStatus != null) && (!transactionStatus.isCompleted());
53+
return (transactionStatus != null && !transactionStatus.isCompleted());
5454
}
55-
56-
// else
5755
return false;
5856
}
5957

@@ -80,8 +78,7 @@ public static boolean isFlaggedForRollback() {
8078
* Rather, the value of this flag will be used to determine whether or not
8179
* the current test-managed transaction should be rolled back or committed
8280
* once it is {@linkplain #end ended}.
83-
* @throws IllegalStateException if a transaction is not active for the
84-
* current test
81+
* @throws IllegalStateException if no transaction is active for the current test
8582
* @see #isActive()
8683
* @see #isFlaggedForRollback()
8784
* @see #start()
@@ -97,8 +94,7 @@ public static void flagForRollback() {
9794
* Rather, the value of this flag will be used to determine whether or not
9895
* the current test-managed transaction should be rolled back or committed
9996
* once it is {@linkplain #end ended}.
100-
* @throws IllegalStateException if a transaction is not active for the
101-
* current test
97+
* @throws IllegalStateException if no transaction is active for the current test
10298
* @see #isActive()
10399
* @see #isFlaggedForRollback()
104100
* @see #start()
@@ -122,9 +118,9 @@ public static void start() {
122118
}
123119

124120
/**
125-
* Immediately force a <em>commit</em> or <em>rollback</em> of the current
126-
* test-managed transaction, according to the {@linkplain #isFlaggedForRollback
127-
* rollback flag}.
121+
* Immediately force a <em>commit</em> or <em>rollback</em> of the
122+
* current test-managed transaction, according to the
123+
* {@linkplain #isFlaggedForRollback rollback flag}.
128124
* @throws IllegalStateException if the transaction context could not be
129125
* retrieved or if a transaction is not active for the current test
130126
* @see #isActive()
@@ -134,6 +130,7 @@ public static void end() {
134130
requireCurrentTransactionContext().endTransaction();
135131
}
136132

133+
137134
private static TransactionContext requireCurrentTransactionContext() {
138135
TransactionContext txContext = TransactionContextHolder.getCurrentTransactionContext();
139136
Assert.state(txContext != null, "TransactionContext is not active");
@@ -144,4 +141,4 @@ private static void setFlaggedForRollback(boolean flag) {
144141
requireCurrentTransactionContext().setFlaggedForRollback(flag);
145142
}
146143

147-
}
144+
}

spring-test/src/main/java/org/springframework/test/context/transaction/TransactionContext.java

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 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.
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.test.context.transaction;
1818

19+
import java.util.concurrent.atomic.AtomicInteger;
20+
1921
import org.apache.commons.logging.Log;
2022
import org.apache.commons.logging.LogFactory;
2123

@@ -53,7 +55,7 @@ class TransactionContext {
5355
@Nullable
5456
private TransactionStatus transactionStatus;
5557

56-
private volatile int transactionsStarted = 0;
58+
private final AtomicInteger transactionsStarted = new AtomicInteger(0);
5759

5860

5961
TransactionContext(TestContext testContext, PlatformTransactionManager transactionManager,
@@ -82,8 +84,8 @@ boolean isFlaggedForRollback() {
8284
}
8385

8486
void setFlaggedForRollback(boolean flaggedForRollback) {
85-
Assert.state(this.transactionStatus != null, () -> String.format(
86-
"Failed to set rollback flag for test context %s: transaction does not exist.", this.testContext));
87+
Assert.state(this.transactionStatus != null, () ->
88+
"Failed to set rollback flag - transaction does not exist: " + this.testContext);
8789
this.flaggedForRollback = flaggedForRollback;
8890
}
8991

@@ -95,16 +97,16 @@ void setFlaggedForRollback(boolean flaggedForRollback) {
9597
*/
9698
void startTransaction() {
9799
Assert.state(this.transactionStatus == null,
98-
"Cannot start a new transaction without ending the existing transaction first.");
100+
"Cannot start a new transaction without ending the existing transaction first");
99101

100102
this.flaggedForRollback = this.defaultRollback;
101103
this.transactionStatus = this.transactionManager.getTransaction(this.transactionDefinition);
102-
++this.transactionsStarted;
104+
int transactionsStarted = this.transactionsStarted.incrementAndGet();
103105

104106
if (logger.isInfoEnabled()) {
105107
logger.info(String.format(
106108
"Began transaction (%s) for test context %s; transaction manager [%s]; rollback [%s]",
107-
this.transactionsStarted, this.testContext, this.transactionManager, flaggedForRollback));
109+
transactionsStarted, this.testContext, this.transactionManager, flaggedForRollback));
108110
}
109111
}
110112

@@ -118,8 +120,8 @@ void endTransaction() {
118120
"Ending transaction for test context %s; transaction status [%s]; rollback [%s]",
119121
this.testContext, this.transactionStatus, this.flaggedForRollback));
120122
}
121-
Assert.state(this.transactionStatus != null, () -> String.format(
122-
"Failed to end transaction for test context %s: transaction does not exist.", this.testContext));
123+
Assert.state(this.transactionStatus != null,
124+
() -> "Failed to end transaction - transaction does not exist: " + this.testContext);
123125

124126
try {
125127
if (this.flaggedForRollback) {
@@ -134,8 +136,8 @@ void endTransaction() {
134136
}
135137

136138
if (logger.isInfoEnabled()) {
137-
logger.info(String.format("%s transaction for test context %s.",
138-
(this.flaggedForRollback ? "Rolled back" : "Committed"), this.testContext));
139+
logger.info((this.flaggedForRollback ? "Rolled back" : "Committed") +
140+
" transaction for test: " + this.testContext);
139141
}
140142
}
141143

spring-test/src/main/java/org/springframework/test/context/transaction/TransactionContextHolder.java

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 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.
@@ -31,7 +31,7 @@ class TransactionContextHolder {
3131
new NamedInheritableThreadLocal<>("Test Transaction Context");
3232

3333

34-
static void setCurrentTransactionContext(@Nullable TransactionContext transactionContext) {
34+
static void setCurrentTransactionContext(TransactionContext transactionContext) {
3535
currentTransactionContext.set(transactionContext);
3636
}
3737

@@ -42,11 +42,9 @@ static TransactionContext getCurrentTransactionContext() {
4242

4343
@Nullable
4444
static TransactionContext removeCurrentTransactionContext() {
45-
synchronized (currentTransactionContext) {
46-
TransactionContext transactionContext = currentTransactionContext.get();
47-
currentTransactionContext.remove();
48-
return transactionContext;
49-
}
45+
TransactionContext transactionContext = currentTransactionContext.get();
46+
currentTransactionContext.remove();
47+
return transactionContext;
5048
}
5149

5250
}

0 commit comments

Comments
 (0)