Support @Rollback on classes & deprecate @TxConfig

Since Spring Framework 2.5, @Rollback has been supported on test
methods, with class-level rollback settings configured via
@TransactionConfiguration; however, allowing @Rollback to be declared
on test classes with method-level declarations overriding class-level
declarations would prove more intuitive than having to declare both
@TransactionConfiguration and @Rollback. Furthermore, the
transactionManager flag in @TransactionConfiguration was made
superfluous many years ago with the introduction of support for a
qualifier in @Transactional.

This commit enables @Rollback to be declared at the class level for
default rollback semantics within test class hierarchies and deprecates
@TransactionConfiguration in favor of @Rollback and @Transactional
qualifiers.

Issue: SPR-13276, SPR-13277
This commit is contained in:
Sam Brannen
2015-07-25 18:22:26 +02:00
parent efd7f9bf72
commit 3f8b51283e
22 changed files with 713 additions and 196 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2015 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.
@@ -24,25 +24,36 @@ import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
/**
* Test annotation used to indicate whether or not the transaction for the
* annotated test method should be <em>rolled back</em> after the test method
* has completed. If {@code true}, the transaction will be rolled back;
* otherwise, the transaction will be committed.
* Test annotation used to indicate whether a <em>test-managed transaction</em>
* should be <em>rolled back</em> after the test method has completed.
*
* <p>Consult the class-level Javadoc for
* {@link org.springframework.test.context.transaction.TransactionalTestExecutionListener}
* for an explanation of <em>test-managed transactions</em>.
*
* <p>When declared as a class-level annotation, {@code @Rollback} defines
* the default rollback semantics for all test methods within the test class
* hierarchy. When declared as a method-level annotation, {@code @Rollback}
* defines rollback semantics for the specific test method, potentially
* overriding class-level default rollback semantics.
*
* <p>As of Spring Framework 4.0, this annotation may be used as a
* <em>meta-annotation</em> to create custom <em>composed annotations</em>.
*
* @author Sam Brannen
* @since 2.5
* @see org.springframework.test.context.transaction.TransactionalTestExecutionListener
*/
@Documented
@Retention(RUNTIME)
@Target({ METHOD, ANNOTATION_TYPE })
@Target({ TYPE, METHOD, ANNOTATION_TYPE })
public @interface Rollback {
/**
* Whether or not the transaction for the annotated method should be rolled
* back after the method has completed.
* Whether the <em>test-managed transaction</em> should be rolled back
* after the test method has completed.
* <p>If {@code true}, the transaction will be rolled back; otherwise,
* the transaction will be committed.
*/
boolean value() default true;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 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.
@@ -36,8 +36,8 @@ import org.springframework.util.StringUtils;
/**
* Utility methods for working with transactions and data access related beans
* within the <em>Spring TestContext Framework</em>. Mainly for internal use
* within the framework.
* within the <em>Spring TestContext Framework</em>.
* <p>Mainly for internal use within the framework.
*
* @author Sam Brannen
* @author Juergen Hoeller
@@ -146,6 +146,8 @@ public abstract class TestContextTransactionUtils {
* @return the transaction manager to use, or {@code null} if not found
* @throws BeansException if an error occurs while retrieving an explicitly
* named transaction manager
* @throws IllegalStateException if more than one TransactionManagementConfigurer
* exists in the ApplicationContext
*/
public static PlatformTransactionManager retrieveTransactionManager(TestContext testContext, String name) {
Assert.notNull(testContext, "TestContext must not be null");

View File

@@ -39,7 +39,10 @@ import java.lang.annotation.Target;
* @see org.springframework.test.context.jdbc.SqlConfig
* @see org.springframework.test.context.jdbc.SqlConfig#transactionManager
* @see org.springframework.test.context.ContextConfiguration
* @deprecated As of Spring Framework 4.2, use {@code @Rollback} at the class
* level and the {@code transactionManager} qualifier in {@code @Transactional}.
*/
@Deprecated
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@@ -48,7 +51,7 @@ public @interface TransactionConfiguration {
/**
* The bean name of the {@link org.springframework.transaction.PlatformTransactionManager
* PlatformTransactionManager} that should be used to drive transactions.
* PlatformTransactionManager} that should be used to drive <em>test-managed transactions</em>.
*
* <p>The name is only used if there is more than one bean of type
* {@code PlatformTransactionManager} in the test's {@code ApplicationContext}.
@@ -76,7 +79,7 @@ public @interface TransactionConfiguration {
String transactionManager() default "";
/**
* Should transactions be rolled back by default?
* Whether <em>test-managed transactions</em> should be rolled back by default.
*/
boolean defaultRollback() default true;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2015 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.
@@ -36,14 +36,27 @@ public class TransactionConfigurationAttributes {
/**
* Construct a new TransactionConfigurationAttributes instance from the
* supplied arguments.
* Construct a new {@code TransactionConfigurationAttributes} instance
* using an empty string for the bean name of the
* {@link PlatformTransactionManager} and {@code true} for the default
* rollback flag.
* @see #TransactionConfigurationAttributes(String, boolean)
*/
public TransactionConfigurationAttributes() {
this("", true);
}
/**
* Construct a new {@code TransactionConfigurationAttributes} instance
* from the supplied arguments.
* @param transactionManagerName the bean name of the
* {@link PlatformTransactionManager} that is to be used to drive transactions
* @param defaultRollback whether or not transactions should be rolled back by default
* {@link PlatformTransactionManager} that is to be used to drive
* <em>test-managed transactions</em>
* @param defaultRollback whether or not <em>test-managed transactions</em>
* should be rolled back by default
*/
public TransactionConfigurationAttributes(String transactionManagerName, boolean defaultRollback) {
Assert.notNull(transactionManagerName, "transactionManagerName can not be null");
Assert.notNull(transactionManagerName, "transactionManagerName must not be null");
this.transactionManagerName = transactionManagerName;
this.defaultRollback = defaultRollback;
}
@@ -51,14 +64,14 @@ public class TransactionConfigurationAttributes {
/**
* Get the bean name of the {@link PlatformTransactionManager} that is to
* be used to drive transactions.
* be used to drive <em>test-managed transactions</em>.
*/
public final String getTransactionManagerName() {
return this.transactionManagerName;
}
/**
* Whether or not transactions should be rolled back by default.
* Whether <em>test-managed transactions</em> should be rolled back by default.
* @return the <em>default rollback</em> flag
*/
public final boolean isDefaultRollback() {

View File

@@ -30,7 +30,6 @@ import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.AbstractTestExecutionListener;
@@ -84,9 +83,8 @@ import static org.springframework.core.annotation.AnnotationUtils.*;
* <h3>Declarative Rollback and Commit Behavior</h3>
* <p>By default, test transactions will be automatically <em>rolled back</em>
* after completion of the test; however, transactional commit and rollback
* behavior can be configured declaratively via the class-level
* {@link TransactionConfiguration @TransactionConfiguration} and method-level
* {@link Rollback @Rollback} annotations.
* behavior can be configured declaratively via the {@link Rollback @Rollback}
* annotation at the class level and at the method level.
*
* <h3>Programmatic Transaction Management</h3>
* <p>As of Spring Framework 4.1, it is possible to interact with test-managed
@@ -107,13 +105,11 @@ import static org.springframework.core.annotation.AnnotationUtils.*;
* {@link PlatformTransactionManager} bean to be defined in the Spring
* {@code ApplicationContext} for the test. In case there are multiple
* instances of {@code PlatformTransactionManager} within the test's
* {@code ApplicationContext}, {@code @TransactionConfiguration} supports
* configuring the bean name of the {@code PlatformTransactionManager} that
* should be used to drive transactions. Alternatively, a <em>qualifier</em>
* may be declared via
* {@link org.springframework.transaction.annotation.Transactional#value @Transactional("myQualifier")}, or
* {@link org.springframework.transaction.annotation.TransactionManagementConfigurer TransactionManagementConfigurer}
* can be implemented by an
* {@code ApplicationContext}, a <em>qualifier</em> may be declared via
* {@link org.springframework.transaction.annotation.Transactional @Transactional}
* (e.g., {@code @Transactional("myTxMgr")} or {@code @Transactional(transactionManger = "myTxMgr")},
* or {@link org.springframework.transaction.annotation.TransactionManagementConfigurer
* TransactionManagementConfigurer} can be implemented by an
* {@link org.springframework.context.annotation.Configuration @Configuration}
* class. See {@link TestContextTransactionUtils#retrieveTransactionManager}
* for details on the algorithm used to look up a transaction manager in
@@ -122,7 +118,6 @@ import static org.springframework.core.annotation.AnnotationUtils.*;
* @author Sam Brannen
* @author Juergen Hoeller
* @since 2.5
* @see TransactionConfiguration
* @see org.springframework.transaction.annotation.TransactionManagementConfigurer
* @see org.springframework.transaction.annotation.Transactional
* @see org.springframework.test.annotation.Rollback
@@ -134,8 +129,7 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
private static final Log logger = LogFactory.getLog(TransactionalTestExecutionListener.class);
private static final TransactionConfiguration defaultTransactionConfiguration =
AnnotationUtils.synthesizeAnnotation(TransactionConfiguration.class);
private static final TransactionConfigurationAttributes defaultTxConfigAttributes = new TransactionConfigurationAttributes();
protected final TransactionAttributeSource attributeSource = new AnnotationTransactionAttributeSource();
@@ -229,8 +223,8 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
/**
* Run all {@link BeforeTransaction @BeforeTransaction} methods for the
* specified {@link TestContext test context}. If one of the methods fails,
* however, the caught exception will be rethrown in a wrapped
* specified {@linkplain TestContext test context}. If one of the methods
* fails, however, the caught exception will be rethrown in a wrapped
* {@link RuntimeException}, and the remaining methods will <strong>not</strong>
* be given a chance to execute.
* @param testContext the current test context
@@ -255,8 +249,8 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
/**
* Run all {@link AfterTransaction @AfterTransaction} methods for the
* specified {@link TestContext test context}. If one of the methods fails,
* the caught exception will be logged as an error, and the remaining
* specified {@linkplain TestContext test context}. If one of the methods
* fails, the caught exception will be logged as an error, and the remaining
* methods will be given a chance to execute. After all methods have
* executed, the first caught exception, if any, will be rethrown.
* @param testContext the current test context
@@ -295,7 +289,7 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
}
/**
* Get the {@link PlatformTransactionManager transaction manager} to use
* Get the {@linkplain PlatformTransactionManager transaction manager} to use
* for the supplied {@linkplain TestContext test context} and {@code qualifier}.
* <p>Delegates to {@link #getTransactionManager(TestContext)} if the
* supplied {@code qualifier} is {@code null} or empty.
@@ -334,8 +328,8 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
}
/**
* Get the {@link PlatformTransactionManager transaction manager} to use
* for the supplied {@link TestContext test context}.
* Get the {@linkplain PlatformTransactionManager transaction manager}
* to use for the supplied {@linkplain TestContext test context}.
* <p>The default implementation simply delegates to
* {@link TestContextTransactionUtils#retrieveTransactionManager}.
* @param testContext the test context for which the transaction manager
@@ -343,6 +337,8 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
* @return the transaction manager to use, or {@code null} if not found
* @throws BeansException if an error occurs while retrieving an explicitly
* named transaction manager
* @throws IllegalStateException if more than one TransactionManagementConfigurer
* exists in the ApplicationContext
* @see #getTransactionManager(TestContext, String)
*/
protected PlatformTransactionManager getTransactionManager(TestContext testContext) {
@@ -352,21 +348,45 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
/**
* Determine whether or not to rollback transactions by default for the
* supplied {@link TestContext test context}.
* supplied {@linkplain TestContext test context}.
* <p>Supports {@link Rollback @Rollback} or
* {@link TransactionConfiguration @TransactionConfiguration} at the
* class-level.
* @param testContext the test context for which the default rollback flag
* should be retrieved
* @return the <em>default rollback</em> flag for the supplied test context
* @throws Exception if an error occurs while determining the default rollback flag
*/
protected final boolean isDefaultRollback(TestContext testContext) throws Exception {
return retrieveConfigurationAttributes(testContext).isDefaultRollback();
Class<?> testClass = testContext.getTestClass();
Rollback rollback = findAnnotation(testClass, Rollback.class);
boolean rollbackPresent = (rollback != null);
TransactionConfigurationAttributes txConfigAttributes = retrieveConfigurationAttributes(testContext);
if (rollbackPresent && txConfigAttributes != defaultTxConfigAttributes) {
throw new IllegalStateException(String.format("Test class [%s] is annotated with both @Rollback "
+ "and @TransactionConfiguration, but only one is permitted.", testClass.getName()));
}
if (rollbackPresent) {
boolean defaultRollback = rollback.value();
if (logger.isDebugEnabled()) {
logger.debug(String.format("Retrieved default @Rollback(%s) for test class [%s].", defaultRollback,
testClass.getName()));
}
return defaultRollback;
}
// else
return txConfigAttributes.isDefaultRollback();
}
/**
* Determine whether or not to rollback transactions for the supplied
* {@link TestContext test context} by taking into consideration the
* {@link #isDefaultRollback(TestContext) default rollback} flag and a
* possible method-level override via the {@link Rollback} annotation.
* {@linkplain TestContext test context} by taking into consideration the
* {@linkplain #isDefaultRollback(TestContext) default rollback} flag and a
* possible method-level override via the {@link Rollback @Rollback}
* annotation.
* @param testContext the test context for which the rollback flag
* should be retrieved
* @return the <em>rollback</em> flag for the supplied test context
@@ -458,8 +478,8 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
}
/**
* Determine if the supplied {@link Method current method} is <em>shadowed</em>
* by a {@link Method previous method}.
* Determine if the supplied {@linkplain Method current method} is
* <em>shadowed</em> by a {@linkplain Method previous method}.
* <p>Note: This code has been borrowed from
* {@link org.junit.internal.runners.TestClass#isShadowed(Method, Method)}.
* @param current the current method
@@ -482,17 +502,20 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
}
/**
* Retrieves the {@link TransactionConfigurationAttributes} for the
* specified {@link Class class} which may optionally declare or inherit
* {@link TransactionConfiguration @TransactionConfiguration}. If
* {@code @TransactionConfiguration} is not present for the supplied
* class, the <em>default values</em> for attributes defined in
* {@code @TransactionConfiguration} will be used instead.
* Retrieve the {@link TransactionConfigurationAttributes} for the
* supplied {@link TestContext} whose {@linkplain Class test class}
* may optionally declare or inherit
* {@link TransactionConfiguration @TransactionConfiguration}.
* <p>If {@code @TransactionConfiguration} is not present for the
* supplied {@code TestContext}, a default instance of
* {@code TransactionConfigurationAttributes} will be used instead.
* @param testContext the test context for which the configuration
* attributes should be retrieved
* @return the TransactionConfigurationAttributes instance for this listener,
* potentially cached
* @see TransactionConfigurationAttributes#TransactionConfigurationAttributes()
*/
@SuppressWarnings("deprecation")
TransactionConfigurationAttributes retrieveConfigurationAttributes(TestContext testContext) {
if (this.configurationAttributes == null) {
Class<?> clazz = testContext.getTestClass();
@@ -501,18 +524,15 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
TransactionConfiguration.class);
if (logger.isDebugEnabled()) {
logger.debug(String.format("Retrieved @TransactionConfiguration [%s] for test class [%s].",
txConfig, clazz));
txConfig, clazz.getName()));
}
if (txConfig == null) {
txConfig = defaultTransactionConfiguration;
}
TransactionConfigurationAttributes configAttributes = (txConfig == null ? defaultTxConfigAttributes
: new TransactionConfigurationAttributes(txConfig.transactionManager(), txConfig.defaultRollback()));
TransactionConfigurationAttributes configAttributes = new TransactionConfigurationAttributes(
txConfig.transactionManager(), txConfig.defaultRollback());
if (logger.isDebugEnabled()) {
logger.debug(String.format("Using TransactionConfigurationAttributes %s for class [%s].",
configAttributes, clazz));
logger.debug(String.format("Using TransactionConfigurationAttributes %s for test class [%s].",
configAttributes, clazz.getName()));
}
this.configurationAttributes = configAttributes;
}