Introduce programmatic tx mgmt in the TCF
Historically, Spring's JUnit 3.8 TestCase class hierarchy supported programmatic transaction management of "test-managed transactions" via the protected endTransaction() and startNewTransaction() methods in AbstractTransactionalSpringContextTests. The Spring TestContext Framework (TCF) was introduced in Spring 2.5 to supersede the legacy JUnit 3.8 support classes; however, prior to this commit the TCF has not provided support for programmatically starting or stopping the test-managed transaction. This commit introduces a TestTransaction class in the TCF that provides static utility methods for programmatically interacting with test-managed transactions. Specifically, the following features are supported by TestTransaction and its collaborators. - End the current test-managed transaction. - Start a new test-managed transaction, using the default rollback semantics configured via @TransactionConfiguration and @Rollback. - Flag the current test-managed transaction to be committed. - Flag the current test-managed transaction to be rolled back. Implementation Details: - TransactionContext is now a top-level, package private class. - The existing test transaction management logic has been extracted from TransactionalTestExecutionListener into TransactionContext. - The current TransactionContext is stored in a NamedInheritableThreadLocal that is managed by TransactionContextHolder. - TestTransaction defines the end-user API, interacting with the TransactionContextHolder behind the scenes. - TransactionalTestExecutionListener now delegates to TransactionContext completely for starting and ending transactions. Issue: SPR-5079
This commit is contained in:
parent
526b463474
commit
f667e43ca2
|
|
@ -0,0 +1,146 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2014 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
|
||||||
|
*
|
||||||
|
* http://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.test.context.transaction;
|
||||||
|
|
||||||
|
import org.springframework.test.context.TestExecutionListeners;
|
||||||
|
import org.springframework.transaction.TransactionStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code TestTransaction} provides a collection of static utility methods for
|
||||||
|
* programmatic interaction with <em>test-managed transactions</em>.
|
||||||
|
*
|
||||||
|
* <p>Test-managed transactions are transactions that are managed by the <em>Spring TestContext Framework</em>.
|
||||||
|
*
|
||||||
|
* <p>Support for {@code TestTransaction} is automatically available whenever
|
||||||
|
* the {@link TransactionalTestExecutionListener} is enabled. Note that the
|
||||||
|
* {@code TransactionalTestExecutionListener} is typically enabled by default,
|
||||||
|
* but it can also be manually enabled via the
|
||||||
|
* {@link TestExecutionListeners @TestExecutionListeners} annotation.
|
||||||
|
*
|
||||||
|
* @author Sam Brannen
|
||||||
|
* @since 4.1
|
||||||
|
* @see TransactionalTestExecutionListener
|
||||||
|
*/
|
||||||
|
public class TestTransaction {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether a test-managed transaction is currently <em>active</em>.
|
||||||
|
* @return {@code true} if a test-managed transaction is currently active
|
||||||
|
* @see #start()
|
||||||
|
* @see #end()
|
||||||
|
*/
|
||||||
|
public static boolean isActive() {
|
||||||
|
TransactionContext transactionContext = TransactionContextHolder.getCurrentTransactionContext();
|
||||||
|
if (transactionContext != null) {
|
||||||
|
TransactionStatus transactionStatus = transactionContext.getTransactionStatus();
|
||||||
|
return (transactionStatus != null) && (!transactionStatus.isCompleted());
|
||||||
|
}
|
||||||
|
|
||||||
|
// else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the current test-managed transaction has been
|
||||||
|
* {@linkplain #flagForRollback() flagged for rollback} or
|
||||||
|
* {@linkplain #flagForCommit() flagged for commit}.
|
||||||
|
* @return {@code true} if the current test-managed transaction is flagged
|
||||||
|
* to be rolled back; {@code false} if the current test-managed transaction
|
||||||
|
* is flagged to be committed
|
||||||
|
* @throws IllegalStateException if a transaction is not active for the
|
||||||
|
* current test
|
||||||
|
* @see #isActive()
|
||||||
|
* @see #flagForRollback()
|
||||||
|
* @see #flagForCommit()
|
||||||
|
*/
|
||||||
|
public static boolean isFlaggedForRollback() {
|
||||||
|
return requireCurrentTransactionContext().isFlaggedForRollback();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag the current test-managed transaction for <em>rollback</em>.
|
||||||
|
* <p>Invoking this method will <em>not</em> end the current transaction.
|
||||||
|
* Rather, the value of this flag will be used to determine whether or not
|
||||||
|
* the current test-managed transaction should be rolled back or committed
|
||||||
|
* once it is {@linkplain #end ended}.
|
||||||
|
* @throws IllegalStateException if a transaction is not active for the
|
||||||
|
* current test
|
||||||
|
* @see #isActive()
|
||||||
|
* @see #isFlaggedForRollback()
|
||||||
|
* @see #start()
|
||||||
|
* @see #end()
|
||||||
|
*/
|
||||||
|
public static void flagForRollback() {
|
||||||
|
setFlaggedForRollback(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag the current test-managed transaction for <em>commit</em>.
|
||||||
|
* <p>Invoking this method will <em>not</em> end the current transaction.
|
||||||
|
* Rather, the value of this flag will be used to determine whether or not
|
||||||
|
* the current test-managed transaction should be rolled back or committed
|
||||||
|
* once it is {@linkplain #end ended}.
|
||||||
|
* @throws IllegalStateException if a transaction is not active for the
|
||||||
|
* current test
|
||||||
|
* @see #isActive()
|
||||||
|
* @see #isFlaggedForRollback()
|
||||||
|
* @see #start()
|
||||||
|
* @see #end()
|
||||||
|
*/
|
||||||
|
public static void flagForCommit() {
|
||||||
|
setFlaggedForRollback(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a new test-managed transaction.
|
||||||
|
* <p>Only call this method if {@link #end} has been called or if no
|
||||||
|
* transaction has been previously started.
|
||||||
|
* @throws IllegalStateException if the transaction context could not be
|
||||||
|
* retrieved or if a transaction is already active for the current test
|
||||||
|
* @see #isActive()
|
||||||
|
* @see #end()
|
||||||
|
*/
|
||||||
|
public static void start() {
|
||||||
|
requireCurrentTransactionContext().startTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immediately force a <em>commit</em> or <em>rollback</em> of the current
|
||||||
|
* test-managed transaction, according to the {@linkplain #isFlaggedForRollback
|
||||||
|
* rollback flag}.
|
||||||
|
* @throws IllegalStateException if the transaction context could not be
|
||||||
|
* retrieved or if a transaction is not active for the current test
|
||||||
|
* @see #isActive()
|
||||||
|
* @see #start()
|
||||||
|
*/
|
||||||
|
public static void end() {
|
||||||
|
requireCurrentTransactionContext().endTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TransactionContext requireCurrentTransactionContext() {
|
||||||
|
TransactionContext txContext = TransactionContextHolder.getCurrentTransactionContext();
|
||||||
|
if (txContext == null) {
|
||||||
|
throw new IllegalStateException("TransactionContext is not active");
|
||||||
|
}
|
||||||
|
return txContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setFlaggedForRollback(boolean flag) {
|
||||||
|
requireCurrentTransactionContext().setFlaggedForRollback(flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2014 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
|
||||||
|
*
|
||||||
|
* http://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.test.context.transaction;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.springframework.test.context.TestContext;
|
||||||
|
import org.springframework.transaction.PlatformTransactionManager;
|
||||||
|
import org.springframework.transaction.TransactionDefinition;
|
||||||
|
import org.springframework.transaction.TransactionException;
|
||||||
|
import org.springframework.transaction.TransactionStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transaction context for a specific {@link TestContext}.
|
||||||
|
*
|
||||||
|
* @author Sam Brannen
|
||||||
|
* @author Juergen Hoeller
|
||||||
|
* @since 4.1
|
||||||
|
* @see org.springframework.transaction.annotation.Transactional
|
||||||
|
* @see org.springframework.test.context.transaction.TransactionalTestExecutionListener
|
||||||
|
*/
|
||||||
|
class TransactionContext {
|
||||||
|
|
||||||
|
private static final Log logger = LogFactory.getLog(TransactionContext.class);
|
||||||
|
|
||||||
|
private final TestContext testContext;
|
||||||
|
|
||||||
|
private final TransactionDefinition transactionDefinition;
|
||||||
|
|
||||||
|
private final PlatformTransactionManager transactionManager;
|
||||||
|
|
||||||
|
private final boolean defaultRollback;
|
||||||
|
|
||||||
|
private boolean flaggedForRollback;
|
||||||
|
|
||||||
|
private TransactionStatus transactionStatus;
|
||||||
|
|
||||||
|
private volatile int transactionsStarted = 0;
|
||||||
|
|
||||||
|
|
||||||
|
TransactionContext(TestContext testContext, PlatformTransactionManager transactionManager,
|
||||||
|
TransactionDefinition transactionDefinition, boolean defaultRollback) {
|
||||||
|
this.testContext = testContext;
|
||||||
|
this.transactionManager = transactionManager;
|
||||||
|
this.transactionDefinition = transactionDefinition;
|
||||||
|
this.defaultRollback = defaultRollback;
|
||||||
|
this.flaggedForRollback = defaultRollback;
|
||||||
|
}
|
||||||
|
|
||||||
|
TransactionStatus getTransactionStatus() {
|
||||||
|
return this.transactionStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Has the current transaction been flagged for rollback?
|
||||||
|
* <p>In other words, should we roll back or commit the current transaction
|
||||||
|
* upon completion of the current test?
|
||||||
|
*/
|
||||||
|
boolean isFlaggedForRollback() {
|
||||||
|
return this.flaggedForRollback;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setFlaggedForRollback(boolean flaggedForRollback) {
|
||||||
|
if (this.transactionStatus == null) {
|
||||||
|
throw new IllegalStateException(String.format(
|
||||||
|
"Failed to set rollback flag for test context %s: transaction does not exist.", this.testContext));
|
||||||
|
}
|
||||||
|
this.flaggedForRollback = flaggedForRollback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a new transaction for the configured {@linkplain #getTestContext test context}.
|
||||||
|
* <p>Only call this method if {@link #endTransaction} has been called or if no
|
||||||
|
* transaction has been previously started.
|
||||||
|
* @throws TransactionException if starting the transaction fails
|
||||||
|
*/
|
||||||
|
void startTransaction() {
|
||||||
|
if (this.transactionStatus != null) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Cannot start a new transaction without ending the existing transaction first.");
|
||||||
|
}
|
||||||
|
this.flaggedForRollback = this.defaultRollback;
|
||||||
|
this.transactionStatus = this.transactionManager.getTransaction(this.transactionDefinition);
|
||||||
|
++this.transactionsStarted;
|
||||||
|
if (logger.isInfoEnabled()) {
|
||||||
|
logger.info(String.format(
|
||||||
|
"Began transaction (%s) for test context %s; transaction manager [%s]; rollback [%s]",
|
||||||
|
this.transactionsStarted, this.testContext, this.transactionManager, flaggedForRollback));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immediately force a <em>commit</em> or <em>rollback</em> of the transaction
|
||||||
|
* for the configured {@linkplain #getTestContext test context}, according to
|
||||||
|
* the {@linkplain #isFlaggedForRollback rollback flag}.
|
||||||
|
*/
|
||||||
|
void endTransaction() {
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace(String.format(
|
||||||
|
"Ending transaction for test context %s; transaction status [%s]; rollback [%s]", this.testContext,
|
||||||
|
this.transactionStatus, flaggedForRollback));
|
||||||
|
}
|
||||||
|
if (this.transactionStatus == null) {
|
||||||
|
throw new IllegalStateException(String.format(
|
||||||
|
"Failed to end transaction for test context %s: transaction does not exist.", this.testContext));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (flaggedForRollback) {
|
||||||
|
this.transactionManager.rollback(this.transactionStatus);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.transactionManager.commit(this.transactionStatus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
this.transactionStatus = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logger.isInfoEnabled()) {
|
||||||
|
logger.info(String.format("%s transaction after test execution for test context %s.",
|
||||||
|
(flaggedForRollback ? "Rolled back" : "Committed"), this.testContext));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2014 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
|
||||||
|
*
|
||||||
|
* http://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.test.context.transaction;
|
||||||
|
|
||||||
|
import org.springframework.core.NamedInheritableThreadLocal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link InheritableThreadLocal}-based holder for the current {@link TransactionContext}.
|
||||||
|
*
|
||||||
|
* @author Sam Brannen
|
||||||
|
* @since 4.1
|
||||||
|
*/
|
||||||
|
class TransactionContextHolder {
|
||||||
|
|
||||||
|
private static final ThreadLocal<TransactionContext> currentTransactionContext = new NamedInheritableThreadLocal<TransactionContext>(
|
||||||
|
"Test Transaction Context");
|
||||||
|
|
||||||
|
|
||||||
|
static TransactionContext getCurrentTransactionContext() {
|
||||||
|
return currentTransactionContext.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setCurrentTransactionContext(TransactionContext transactionContext) {
|
||||||
|
currentTransactionContext.set(transactionContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
static TransactionContext removeCurrentTransactionContext() {
|
||||||
|
synchronized (currentTransactionContext) {
|
||||||
|
TransactionContext transactionContext = currentTransactionContext.get();
|
||||||
|
currentTransactionContext.remove();
|
||||||
|
return transactionContext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -22,8 +22,6 @@ import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
@ -38,7 +36,6 @@ import org.springframework.test.context.TestContext;
|
||||||
import org.springframework.test.context.support.AbstractTestExecutionListener;
|
import org.springframework.test.context.support.AbstractTestExecutionListener;
|
||||||
import org.springframework.transaction.PlatformTransactionManager;
|
import org.springframework.transaction.PlatformTransactionManager;
|
||||||
import org.springframework.transaction.TransactionDefinition;
|
import org.springframework.transaction.TransactionDefinition;
|
||||||
import org.springframework.transaction.TransactionException;
|
|
||||||
import org.springframework.transaction.TransactionStatus;
|
import org.springframework.transaction.TransactionStatus;
|
||||||
import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource;
|
import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource;
|
||||||
import org.springframework.transaction.annotation.TransactionManagementConfigurer;
|
import org.springframework.transaction.annotation.TransactionManagementConfigurer;
|
||||||
|
|
@ -53,7 +50,7 @@ import static org.springframework.core.annotation.AnnotationUtils.*;
|
||||||
/**
|
/**
|
||||||
* {@code TestExecutionListener} that provides support for executing tests
|
* {@code TestExecutionListener} that provides support for executing tests
|
||||||
* within transactions by honoring the
|
* within transactions by honoring the
|
||||||
* {@link org.springframework.transaction.annotation.Transactional @Transactional}
|
* {@link org.springframework.transaction.annotation.Transactional @Transactional}
|
||||||
* annotation. Expects a {@link PlatformTransactionManager} bean to be defined in the
|
* annotation. Expects a {@link PlatformTransactionManager} bean to be defined in the
|
||||||
* Spring {@link ApplicationContext} for the test.
|
* Spring {@link ApplicationContext} for the test.
|
||||||
*
|
*
|
||||||
|
|
@ -91,6 +88,7 @@ import static org.springframework.core.annotation.AnnotationUtils.*;
|
||||||
* @see org.springframework.test.annotation.Rollback
|
* @see org.springframework.test.annotation.Rollback
|
||||||
* @see BeforeTransaction
|
* @see BeforeTransaction
|
||||||
* @see AfterTransaction
|
* @see AfterTransaction
|
||||||
|
* @see TestTransaction
|
||||||
*/
|
*/
|
||||||
public class TransactionalTestExecutionListener extends AbstractTestExecutionListener {
|
public class TransactionalTestExecutionListener extends AbstractTestExecutionListener {
|
||||||
|
|
||||||
|
|
@ -104,18 +102,13 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
|
||||||
|
|
||||||
protected final TransactionAttributeSource attributeSource = new AnnotationTransactionAttributeSource();
|
protected final TransactionAttributeSource attributeSource = new AnnotationTransactionAttributeSource();
|
||||||
|
|
||||||
private final Map<Method, TransactionContext> transactionContextCache = new ConcurrentHashMap<Method, TransactionContext>(
|
|
||||||
8);
|
|
||||||
|
|
||||||
private TransactionConfigurationAttributes configurationAttributes;
|
private TransactionConfigurationAttributes configurationAttributes;
|
||||||
|
|
||||||
private volatile int transactionsStarted = 0;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the test method of the supplied {@link TestContext test context} is
|
* If the test method of the supplied {@linkplain TestContext test context}
|
||||||
* configured to run within a transaction, this method will run
|
* is configured to run within a transaction, this method will run
|
||||||
* {@link BeforeTransaction @BeforeTransaction methods} and start a new
|
* {@link BeforeTransaction @BeforeTransaction} methods and start a new
|
||||||
* transaction.
|
* transaction.
|
||||||
* <p>Note that if a {@code @BeforeTransaction} method fails, any remaining
|
* <p>Note that if a {@code @BeforeTransaction} method fails, any remaining
|
||||||
* {@code @BeforeTransaction} methods will not be invoked, and a transaction
|
* {@code @BeforeTransaction} methods will not be invoked, and a transaction
|
||||||
|
|
@ -129,9 +122,9 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
|
||||||
final Class<?> testClass = testContext.getTestClass();
|
final Class<?> testClass = testContext.getTestClass();
|
||||||
Assert.notNull(testMethod, "The test method of the supplied TestContext must not be null");
|
Assert.notNull(testMethod, "The test method of the supplied TestContext must not be null");
|
||||||
|
|
||||||
if (this.transactionContextCache.remove(testMethod) != null) {
|
TransactionContext txContext = TransactionContextHolder.removeCurrentTransactionContext();
|
||||||
throw new IllegalStateException("Cannot start new transaction without ending existing transaction: "
|
if (txContext != null) {
|
||||||
+ "Invoke endTransaction() before startNewTransaction().");
|
throw new IllegalStateException("Cannot start a new transaction without ending the existing transaction.");
|
||||||
}
|
}
|
||||||
|
|
||||||
PlatformTransactionManager tm = null;
|
PlatformTransactionManager tm = null;
|
||||||
|
|
@ -154,30 +147,34 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tm != null) {
|
if (tm != null) {
|
||||||
TransactionContext txContext = new TransactionContext(tm, transactionAttribute);
|
txContext = new TransactionContext(testContext, tm, transactionAttribute, isRollback(testContext));
|
||||||
runBeforeTransactionMethods(testContext);
|
runBeforeTransactionMethods(testContext);
|
||||||
startNewTransaction(testContext, txContext);
|
txContext.startTransaction();
|
||||||
this.transactionContextCache.put(testMethod, txContext);
|
TransactionContextHolder.setCurrentTransactionContext(txContext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If a transaction is currently active for the test method of the supplied
|
* If a transaction is currently active for the supplied
|
||||||
* {@link TestContext test context}, this method will end the transaction
|
* {@linkplain TestContext test context}, this method will end the transaction
|
||||||
* and run {@link AfterTransaction @AfterTransaction methods}.
|
* and run {@link AfterTransaction @AfterTransaction} methods.
|
||||||
* <p>{@code @AfterTransaction} methods are guaranteed to be
|
* <p>{@code @AfterTransaction} methods are guaranteed to be invoked even if
|
||||||
* invoked even if an error occurs while ending the transaction.
|
* an error occurs while ending the transaction.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void afterTestMethod(TestContext testContext) throws Exception {
|
public void afterTestMethod(TestContext testContext) throws Exception {
|
||||||
Method testMethod = testContext.getTestMethod();
|
Method testMethod = testContext.getTestMethod();
|
||||||
Assert.notNull(testMethod, "The test method of the supplied TestContext must not be null");
|
Assert.notNull(testMethod, "The test method of the supplied TestContext must not be null");
|
||||||
|
|
||||||
// If the transaction is still active...
|
TransactionContext txContext = TransactionContextHolder.removeCurrentTransactionContext();
|
||||||
TransactionContext txContext = this.transactionContextCache.remove(testMethod);
|
// If there was (or perhaps still is) a transaction...
|
||||||
if (txContext != null && !txContext.transactionStatus.isCompleted()) {
|
if (txContext != null) {
|
||||||
|
TransactionStatus transactionStatus = txContext.getTransactionStatus();
|
||||||
try {
|
try {
|
||||||
endTransaction(testContext, txContext);
|
// If the transaction is still active...
|
||||||
|
if ((transactionStatus != null) && !transactionStatus.isCompleted()) {
|
||||||
|
txContext.endTransaction();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
runAfterTransactionMethods(testContext);
|
runAfterTransactionMethods(testContext);
|
||||||
|
|
@ -186,7 +183,7 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run all {@link BeforeTransaction @BeforeTransaction methods} for the
|
* Run all {@link BeforeTransaction @BeforeTransaction} methods for the
|
||||||
* specified {@link TestContext test context}. If one of the methods fails,
|
* specified {@link TestContext test context}. If one of the methods fails,
|
||||||
* however, the caught exception will be rethrown in a wrapped
|
* however, the caught exception will be rethrown in a wrapped
|
||||||
* {@link RuntimeException}, and the remaining methods will <strong>not</strong>
|
* {@link RuntimeException}, and the remaining methods will <strong>not</strong>
|
||||||
|
|
@ -212,7 +209,7 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run all {@link AfterTransaction @AfterTransaction methods} for the
|
* Run all {@link AfterTransaction @AfterTransaction} methods for the
|
||||||
* specified {@link TestContext test context}. If one of the methods fails,
|
* specified {@link TestContext test context}. If one of the methods fails,
|
||||||
* the caught exception will be logged as an error, and the remaining
|
* the caught exception will be logged as an error, and the remaining
|
||||||
* methods will be given a chance to execute. After all methods have
|
* methods will be given a chance to execute. After all methods have
|
||||||
|
|
@ -252,45 +249,6 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Start a new transaction for the supplied {@link TestContext test context}.
|
|
||||||
* <p>Only call this method if {@link #endTransaction} has been called or if no
|
|
||||||
* transaction has been previously started.
|
|
||||||
* @param testContext the current test context
|
|
||||||
* @throws TransactionException if starting the transaction fails
|
|
||||||
* @throws Exception if an error occurs while retrieving the transaction manager
|
|
||||||
*/
|
|
||||||
private void startNewTransaction(TestContext testContext, TransactionContext txContext) throws Exception {
|
|
||||||
txContext.startTransaction();
|
|
||||||
++this.transactionsStarted;
|
|
||||||
if (logger.isInfoEnabled()) {
|
|
||||||
logger.info(String.format(
|
|
||||||
"Began transaction (%s) for test context %s; transaction manager [%s]; rollback [%s]",
|
|
||||||
this.transactionsStarted, testContext, txContext.transactionManager, isRollback(testContext)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Immediately force a <em>commit</em> or <em>rollback</em> of the
|
|
||||||
* transaction for the supplied {@link TestContext test context}, according
|
|
||||||
* to the commit and rollback flags.
|
|
||||||
* @param testContext the current test context
|
|
||||||
* @throws Exception if an error occurs while retrieving the transaction manager
|
|
||||||
*/
|
|
||||||
private void endTransaction(TestContext testContext, TransactionContext txContext) throws Exception {
|
|
||||||
boolean rollback = isRollback(testContext);
|
|
||||||
if (logger.isTraceEnabled()) {
|
|
||||||
logger.trace(String.format(
|
|
||||||
"Ending transaction for test context %s; transaction status [%s]; rollback [%s]", testContext,
|
|
||||||
txContext.transactionStatus, rollback));
|
|
||||||
}
|
|
||||||
txContext.endTransaction(rollback);
|
|
||||||
if (logger.isInfoEnabled()) {
|
|
||||||
logger.info((rollback ? "Rolled back" : "Committed")
|
|
||||||
+ " transaction after test execution for test context " + testContext);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the {@link PlatformTransactionManager transaction manager} to use
|
* Get the {@link PlatformTransactionManager transaction manager} to use
|
||||||
* for the supplied {@linkplain TestContext test context} and {@code qualifier}.
|
* for the supplied {@linkplain TestContext test context} and {@code qualifier}.
|
||||||
|
|
@ -478,7 +436,7 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
|
||||||
/**
|
/**
|
||||||
* Retrieves the {@link TransactionConfigurationAttributes} for the
|
* Retrieves the {@link TransactionConfigurationAttributes} for the
|
||||||
* specified {@link Class class} which may optionally declare or inherit
|
* specified {@link Class class} which may optionally declare or inherit
|
||||||
* {@link TransactionConfiguration @TransactionConfiguration}. If
|
* {@link TransactionConfiguration @TransactionConfiguration}. If
|
||||||
* {@code @TransactionConfiguration} is not present for the supplied
|
* {@code @TransactionConfiguration} is not present for the supplied
|
||||||
* class, the <em>default values</em> for attributes defined in
|
* class, the <em>default values</em> for attributes defined in
|
||||||
* {@code @TransactionConfiguration} will be used instead.
|
* {@code @TransactionConfiguration} will be used instead.
|
||||||
|
|
@ -520,37 +478,4 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
|
||||||
return this.configurationAttributes;
|
return this.configurationAttributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal context holder for a specific test method.
|
|
||||||
*/
|
|
||||||
private static class TransactionContext {
|
|
||||||
|
|
||||||
private final PlatformTransactionManager transactionManager;
|
|
||||||
|
|
||||||
private final TransactionDefinition transactionDefinition;
|
|
||||||
|
|
||||||
private TransactionStatus transactionStatus;
|
|
||||||
|
|
||||||
|
|
||||||
public TransactionContext(PlatformTransactionManager transactionManager,
|
|
||||||
TransactionDefinition transactionDefinition) {
|
|
||||||
this.transactionManager = transactionManager;
|
|
||||||
this.transactionDefinition = transactionDefinition;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void startTransaction() {
|
|
||||||
this.transactionStatus = this.transactionManager.getTransaction(this.transactionDefinition);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void endTransaction(boolean rollback) {
|
|
||||||
if (rollback) {
|
|
||||||
this.transactionManager.rollback(this.transactionStatus);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.transactionManager.commit(this.transactionStatus);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,9 @@ public class EmptyDatabaseConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public DataSource dataSource() {
|
public DataSource dataSource() {
|
||||||
return new EmbeddedDatabaseBuilder().build();
|
return new EmbeddedDatabaseBuilder()//
|
||||||
|
.setName("empty-sql-scripts-test-db")//
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ public class PopulatedSchemaDatabaseConfig {
|
||||||
@Bean
|
@Bean
|
||||||
public DataSource dataSource() {
|
public DataSource dataSource() {
|
||||||
return new EmbeddedDatabaseBuilder()//
|
return new EmbeddedDatabaseBuilder()//
|
||||||
|
.setName("populated-sql-scripts-test-db")//
|
||||||
.addScript("classpath:/org/springframework/test/context/jdbc/schema.sql") //
|
.addScript("classpath:/org/springframework/test/context/jdbc/schema.sql") //
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2013 the original author or authors.
|
* Copyright 2002-2014 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -19,6 +19,7 @@ package org.springframework.test.context.transaction;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
import org.springframework.test.annotation.Rollback;
|
import org.springframework.test.annotation.Rollback;
|
||||||
|
|
@ -70,6 +71,7 @@ public class TransactionalTestExecutionListenerTests {
|
||||||
when(testContext.getTestMethod()).thenReturn(clazz.getDeclaredMethod("transactionalTest"));
|
when(testContext.getTestMethod()).thenReturn(clazz.getDeclaredMethod("transactionalTest"));
|
||||||
|
|
||||||
assertFalse(instance.invoked);
|
assertFalse(instance.invoked);
|
||||||
|
TransactionContextHolder.removeCurrentTransactionContext();
|
||||||
listener.beforeTestMethod(testContext);
|
listener.beforeTestMethod(testContext);
|
||||||
assertEquals(invokedInTx, instance.invoked);
|
assertEquals(invokedInTx, instance.invoked);
|
||||||
}
|
}
|
||||||
|
|
@ -82,6 +84,7 @@ public class TransactionalTestExecutionListenerTests {
|
||||||
when(testContext.getTestMethod()).thenReturn(clazz.getDeclaredMethod("nonTransactionalTest"));
|
when(testContext.getTestMethod()).thenReturn(clazz.getDeclaredMethod("nonTransactionalTest"));
|
||||||
|
|
||||||
assertFalse(instance.invoked);
|
assertFalse(instance.invoked);
|
||||||
|
TransactionContextHolder.removeCurrentTransactionContext();
|
||||||
listener.beforeTestMethod(testContext);
|
listener.beforeTestMethod(testContext);
|
||||||
assertFalse(instance.invoked);
|
assertFalse(instance.invoked);
|
||||||
}
|
}
|
||||||
|
|
@ -100,6 +103,7 @@ public class TransactionalTestExecutionListenerTests {
|
||||||
when(tm.getTransaction(Mockito.any(TransactionDefinition.class))).thenReturn(new SimpleTransactionStatus());
|
when(tm.getTransaction(Mockito.any(TransactionDefinition.class))).thenReturn(new SimpleTransactionStatus());
|
||||||
|
|
||||||
assertFalse(instance.invoked);
|
assertFalse(instance.invoked);
|
||||||
|
TransactionContextHolder.removeCurrentTransactionContext();
|
||||||
listener.beforeTestMethod(testContext);
|
listener.beforeTestMethod(testContext);
|
||||||
listener.afterTestMethod(testContext);
|
listener.afterTestMethod(testContext);
|
||||||
assertTrue(instance.invoked);
|
assertTrue(instance.invoked);
|
||||||
|
|
@ -112,6 +116,7 @@ public class TransactionalTestExecutionListenerTests {
|
||||||
when(testContext.getTestMethod()).thenReturn(clazz.getDeclaredMethod("nonTransactionalTest"));
|
when(testContext.getTestMethod()).thenReturn(clazz.getDeclaredMethod("nonTransactionalTest"));
|
||||||
|
|
||||||
assertFalse(instance.invoked);
|
assertFalse(instance.invoked);
|
||||||
|
TransactionContextHolder.removeCurrentTransactionContext();
|
||||||
listener.beforeTestMethod(testContext);
|
listener.beforeTestMethod(testContext);
|
||||||
listener.afterTestMethod(testContext);
|
listener.afterTestMethod(testContext);
|
||||||
assertFalse(instance.invoked);
|
assertFalse(instance.invoked);
|
||||||
|
|
@ -133,6 +138,11 @@ public class TransactionalTestExecutionListenerTests {
|
||||||
assertEquals(rollback, listener.isRollback(testContext));
|
assertEquals(rollback, listener.isRollback(testContext));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void cleanUpThreadLocalStateForSubsequentTestClassesInSuite() {
|
||||||
|
TransactionContextHolder.removeCurrentTransactionContext();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void beforeTestMethodWithTransactionalDeclaredOnClassLocally() throws Exception {
|
public void beforeTestMethodWithTransactionalDeclaredOnClassLocally() throws Exception {
|
||||||
assertBeforeTestMethodWithTransactionalTestMethod(TransactionalDeclaredOnClassLocallyTestCase.class);
|
assertBeforeTestMethodWithTransactionalTestMethod(TransactionalDeclaredOnClassLocallyTestCase.class);
|
||||||
|
|
@ -192,14 +202,12 @@ public class TransactionalTestExecutionListenerTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void retrieveConfigurationAttributesWithMissingTransactionConfiguration() throws Exception {
|
public void retrieveConfigurationAttributesWithMissingTransactionConfiguration() throws Exception {
|
||||||
assertTransactionConfigurationAttributes(MissingTransactionConfigurationTestCase.class, "",
|
assertTransactionConfigurationAttributes(MissingTransactionConfigurationTestCase.class, "", true);
|
||||||
true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void retrieveConfigurationAttributesWithEmptyTransactionConfiguration() throws Exception {
|
public void retrieveConfigurationAttributesWithEmptyTransactionConfiguration() throws Exception {
|
||||||
assertTransactionConfigurationAttributes(EmptyTransactionConfigurationTestCase.class, "",
|
assertTransactionConfigurationAttributes(EmptyTransactionConfigurationTestCase.class, "", true);
|
||||||
true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,284 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2014 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
|
||||||
|
*
|
||||||
|
* http://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.test.context.transaction.programmatic;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.TestName;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
|
||||||
|
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
|
||||||
|
import org.springframework.test.annotation.Rollback;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
|
||||||
|
import org.springframework.test.context.transaction.AfterTransaction;
|
||||||
|
import org.springframework.test.context.transaction.BeforeTransaction;
|
||||||
|
import org.springframework.test.context.transaction.TestTransaction;
|
||||||
|
import org.springframework.transaction.PlatformTransactionManager;
|
||||||
|
import org.springframework.transaction.annotation.Propagation;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
import static org.springframework.test.transaction.TransactionTestUtils.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Integration tests that verify support for programmatic transaction management
|
||||||
|
* within the <em>Spring TestContext Framework</em>.
|
||||||
|
*
|
||||||
|
* @author Sam Brannen
|
||||||
|
* @since 4.1
|
||||||
|
*/
|
||||||
|
@ContextConfiguration
|
||||||
|
public class ProgrammaticTxMgmtTests extends AbstractTransactionalJUnit4SpringContextTests {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public TestName testName = new TestName();
|
||||||
|
|
||||||
|
|
||||||
|
@BeforeTransaction
|
||||||
|
public void beforeTransaction() {
|
||||||
|
deleteFromTables("user");
|
||||||
|
executeSqlScript("classpath:/org/springframework/test/context/jdbc/data.sql", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterTransaction
|
||||||
|
public void afterTransaction() {
|
||||||
|
String method = testName.getMethodName();
|
||||||
|
switch (method) {
|
||||||
|
case "commitTxAndStartNewTx": {
|
||||||
|
assertUsers("Dogbert");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "commitTxButDoNotStartNewTx": {
|
||||||
|
assertUsers("Dogbert");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "rollbackTxAndStartNewTx": {
|
||||||
|
assertUsers("Dilbert");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "rollbackTxButDoNotStartNewTx": {
|
||||||
|
assertUsers("Dilbert");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "rollbackTxAndStartNewTxWithDefaultCommitSemantics": {
|
||||||
|
assertUsers("Dilbert", "Dogbert");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "startTxWithExistingTransaction": {
|
||||||
|
assertUsers("Dilbert");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
fail("missing 'after transaction' assertion for test method: " + method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Transactional(propagation = Propagation.NOT_SUPPORTED)
|
||||||
|
public void isActiveWithNonExistentTransactionContext() {
|
||||||
|
assertFalse(TestTransaction.isActive());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalStateException.class)
|
||||||
|
@Transactional(propagation = Propagation.NOT_SUPPORTED)
|
||||||
|
public void flagForRollbackWithNonExistentTransactionContext() {
|
||||||
|
TestTransaction.flagForRollback();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalStateException.class)
|
||||||
|
@Transactional(propagation = Propagation.NOT_SUPPORTED)
|
||||||
|
public void flagForCommitWithNonExistentTransactionContext() {
|
||||||
|
TestTransaction.flagForCommit();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalStateException.class)
|
||||||
|
@Transactional(propagation = Propagation.NOT_SUPPORTED)
|
||||||
|
public void isFlaggedForRollbackWithNonExistentTransactionContext() {
|
||||||
|
TestTransaction.isFlaggedForRollback();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalStateException.class)
|
||||||
|
@Transactional(propagation = Propagation.NOT_SUPPORTED)
|
||||||
|
public void startTxWithNonExistentTransactionContext() {
|
||||||
|
TestTransaction.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalStateException.class)
|
||||||
|
public void startTxWithExistingTransaction() {
|
||||||
|
TestTransaction.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalStateException.class)
|
||||||
|
@Transactional(propagation = Propagation.NOT_SUPPORTED)
|
||||||
|
public void endTxWithNonExistentTransactionContext() {
|
||||||
|
TestTransaction.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void commitTxAndStartNewTx() {
|
||||||
|
assertInTransaction(true);
|
||||||
|
assertTrue(TestTransaction.isActive());
|
||||||
|
assertUsers("Dilbert");
|
||||||
|
deleteFromTables("user");
|
||||||
|
assertUsers();
|
||||||
|
|
||||||
|
// Commit
|
||||||
|
TestTransaction.flagForCommit();
|
||||||
|
assertFalse(TestTransaction.isFlaggedForRollback());
|
||||||
|
TestTransaction.end();
|
||||||
|
assertInTransaction(false);
|
||||||
|
assertFalse(TestTransaction.isActive());
|
||||||
|
assertUsers();
|
||||||
|
|
||||||
|
executeSqlScript("classpath:/org/springframework/test/context/jdbc/data-add-dogbert.sql", false);
|
||||||
|
assertUsers("Dogbert");
|
||||||
|
|
||||||
|
TestTransaction.start();
|
||||||
|
assertInTransaction(true);
|
||||||
|
assertTrue(TestTransaction.isActive());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void commitTxButDoNotStartNewTx() {
|
||||||
|
assertInTransaction(true);
|
||||||
|
assertTrue(TestTransaction.isActive());
|
||||||
|
assertUsers("Dilbert");
|
||||||
|
deleteFromTables("user");
|
||||||
|
assertUsers();
|
||||||
|
|
||||||
|
// Commit
|
||||||
|
TestTransaction.flagForCommit();
|
||||||
|
assertFalse(TestTransaction.isFlaggedForRollback());
|
||||||
|
TestTransaction.end();
|
||||||
|
assertFalse(TestTransaction.isActive());
|
||||||
|
assertInTransaction(false);
|
||||||
|
assertUsers();
|
||||||
|
|
||||||
|
executeSqlScript("classpath:/org/springframework/test/context/jdbc/data-add-dogbert.sql", false);
|
||||||
|
assertUsers("Dogbert");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void rollbackTxAndStartNewTx() {
|
||||||
|
assertInTransaction(true);
|
||||||
|
assertTrue(TestTransaction.isActive());
|
||||||
|
assertUsers("Dilbert");
|
||||||
|
deleteFromTables("user");
|
||||||
|
assertUsers();
|
||||||
|
|
||||||
|
// Rollback (automatically)
|
||||||
|
assertTrue(TestTransaction.isFlaggedForRollback());
|
||||||
|
TestTransaction.end();
|
||||||
|
assertFalse(TestTransaction.isActive());
|
||||||
|
assertInTransaction(false);
|
||||||
|
assertUsers("Dilbert");
|
||||||
|
|
||||||
|
// Start new transaction with default rollback semantics
|
||||||
|
TestTransaction.start();
|
||||||
|
assertInTransaction(true);
|
||||||
|
assertTrue(TestTransaction.isFlaggedForRollback());
|
||||||
|
assertTrue(TestTransaction.isActive());
|
||||||
|
|
||||||
|
executeSqlScript("classpath:/org/springframework/test/context/jdbc/data-add-dogbert.sql", false);
|
||||||
|
assertUsers("Dilbert", "Dogbert");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void rollbackTxButDoNotStartNewTx() {
|
||||||
|
assertInTransaction(true);
|
||||||
|
assertTrue(TestTransaction.isActive());
|
||||||
|
assertUsers("Dilbert");
|
||||||
|
deleteFromTables("user");
|
||||||
|
assertUsers();
|
||||||
|
|
||||||
|
// Rollback (automatically)
|
||||||
|
assertTrue(TestTransaction.isFlaggedForRollback());
|
||||||
|
TestTransaction.end();
|
||||||
|
assertFalse(TestTransaction.isActive());
|
||||||
|
assertInTransaction(false);
|
||||||
|
assertUsers("Dilbert");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Rollback(false)
|
||||||
|
public void rollbackTxAndStartNewTxWithDefaultCommitSemantics() {
|
||||||
|
assertInTransaction(true);
|
||||||
|
assertTrue(TestTransaction.isActive());
|
||||||
|
assertUsers("Dilbert");
|
||||||
|
deleteFromTables("user");
|
||||||
|
assertUsers();
|
||||||
|
|
||||||
|
// Rollback
|
||||||
|
TestTransaction.flagForRollback();
|
||||||
|
assertTrue(TestTransaction.isFlaggedForRollback());
|
||||||
|
TestTransaction.end();
|
||||||
|
assertFalse(TestTransaction.isActive());
|
||||||
|
assertInTransaction(false);
|
||||||
|
assertUsers("Dilbert");
|
||||||
|
|
||||||
|
// Start new transaction with default commit semantics
|
||||||
|
TestTransaction.start();
|
||||||
|
assertInTransaction(true);
|
||||||
|
assertFalse(TestTransaction.isFlaggedForRollback());
|
||||||
|
assertTrue(TestTransaction.isActive());
|
||||||
|
|
||||||
|
executeSqlScript("classpath:/org/springframework/test/context/jdbc/data-add-dogbert.sql", false);
|
||||||
|
assertUsers("Dilbert", "Dogbert");
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
private void assertUsers(String... users) {
|
||||||
|
List<Map<String, Object>> results = jdbcTemplate.queryForList("select name from user");
|
||||||
|
List<String> names = new ArrayList<String>();
|
||||||
|
for (Map<String, Object> map : results) {
|
||||||
|
names.add((String) map.get("name"));
|
||||||
|
}
|
||||||
|
assertEquals(Arrays.asList(users), names);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
static class Config {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PlatformTransactionManager transactionManager() {
|
||||||
|
return new DataSourceTransactionManager(dataSource());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public DataSource dataSource() {
|
||||||
|
return new EmbeddedDatabaseBuilder()//
|
||||||
|
.setName("programmatic-tx-mgmt-test-db")//
|
||||||
|
.addScript("classpath:/org/springframework/test/context/jdbc/schema.sql") //
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue