|
|
@ -1,5 +1,5 @@ |
|
|
|
/* |
|
|
|
/* |
|
|
|
* Copyright 2002-2010 the original author or authors. |
|
|
|
* Copyright 2002-2011 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. |
|
|
@ -27,7 +27,6 @@ import java.util.Map; |
|
|
|
|
|
|
|
|
|
|
|
import org.apache.commons.logging.Log; |
|
|
|
import org.apache.commons.logging.Log; |
|
|
|
import org.apache.commons.logging.LogFactory; |
|
|
|
import org.apache.commons.logging.LogFactory; |
|
|
|
|
|
|
|
|
|
|
|
import org.springframework.beans.BeansException; |
|
|
|
import org.springframework.beans.BeansException; |
|
|
|
import org.springframework.beans.factory.BeanFactory; |
|
|
|
import org.springframework.beans.factory.BeanFactory; |
|
|
|
import org.springframework.core.annotation.AnnotationUtils; |
|
|
|
import org.springframework.core.annotation.AnnotationUtils; |
|
|
@ -50,19 +49,19 @@ import org.springframework.util.StringUtils; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* <p> |
|
|
|
* <p> |
|
|
|
* <code>TestExecutionListener</code> which provides support for executing |
|
|
|
* <code>TestExecutionListener</code> that provides support for executing |
|
|
|
* tests within transactions by using |
|
|
|
* tests within transactions by using the |
|
|
|
* {@link org.springframework.transaction.annotation.Transactional @Transactional} |
|
|
|
* {@link org.springframework.transaction.annotation.Transactional @Transactional} |
|
|
|
* and {@link NotTransactional @NotTransactional} annotations. |
|
|
|
* and {@link NotTransactional @NotTransactional} annotations. |
|
|
|
* </p> |
|
|
|
* </p> |
|
|
|
* <p> |
|
|
|
* <p> |
|
|
|
* Changes to the database during a test run with @Transactional will be |
|
|
|
* Changes to the database during a test that is run with @Transactional will be |
|
|
|
* run within a transaction that will, by default, be automatically |
|
|
|
* run within a transaction that will, by default, be automatically |
|
|
|
* <em>rolled back</em> after completion of the test; whereas, changes to the |
|
|
|
* <em>rolled back</em> after completion of the test; whereas, changes to the |
|
|
|
* database during a test run with @NotTransactional will <strong>not</strong> |
|
|
|
* database during a test that is run with @NotTransactional will <strong>not</strong> |
|
|
|
* be run within a transaction. Similarly, test methods that are not annotated |
|
|
|
* be run within a transaction. Test methods that are not annotated with either |
|
|
|
* with either @Transactional (at the class or method level) or |
|
|
|
* @Transactional (at the class or method level) or @NotTransactional |
|
|
|
* @NotTransactional will not be run within a transaction. |
|
|
|
* will not be run within a transaction. |
|
|
|
* </p> |
|
|
|
* </p> |
|
|
|
* <p> |
|
|
|
* <p> |
|
|
|
* Transactional commit and rollback behavior can be configured via the |
|
|
|
* Transactional commit and rollback behavior can be configured via the |
|
|
@ -73,7 +72,7 @@ import org.springframework.util.StringUtils; |
|
|
|
* is to be used to drive transactions. |
|
|
|
* is to be used to drive transactions. |
|
|
|
* </p> |
|
|
|
* </p> |
|
|
|
* <p> |
|
|
|
* <p> |
|
|
|
* When executing transactional tests, it is sometimes useful to be able execute |
|
|
|
* When executing transactional tests, it is sometimes useful to be able to execute |
|
|
|
* certain <em>set up</em> or <em>tear down</em> code outside of a |
|
|
|
* certain <em>set up</em> or <em>tear down</em> code outside of a |
|
|
|
* transaction. <code>TransactionalTestExecutionListener</code> provides such |
|
|
|
* transaction. <code>TransactionalTestExecutionListener</code> provides such |
|
|
|
* support for methods annotated with |
|
|
|
* support for methods annotated with |
|
|
@ -102,8 +101,8 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis |
|
|
|
|
|
|
|
|
|
|
|
private volatile int transactionsStarted = 0; |
|
|
|
private volatile int transactionsStarted = 0; |
|
|
|
|
|
|
|
|
|
|
|
private final Map<Method, TransactionContext> transactionContextCache = |
|
|
|
private final Map<Method, TransactionContext> transactionContextCache = Collections.synchronizedMap(new IdentityHashMap<Method, TransactionContext>()); |
|
|
|
Collections.synchronizedMap(new IdentityHashMap<Method, TransactionContext>()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* If the test method of the supplied {@link TestContext test context} is |
|
|
|
* If the test method of the supplied {@link TestContext test context} is |
|
|
@ -123,19 +122,20 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis |
|
|
|
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) { |
|
|
|
if (this.transactionContextCache.remove(testMethod) != null) { |
|
|
|
throw new IllegalStateException("Cannot start new transaction without ending existing transaction: " + |
|
|
|
throw new IllegalStateException("Cannot start new transaction without ending existing transaction: " |
|
|
|
"Invoke endTransaction() before startNewTransaction()."); |
|
|
|
+ "Invoke endTransaction() before startNewTransaction()."); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (testMethod.isAnnotationPresent(NotTransactional.class)) { |
|
|
|
if (testMethod.isAnnotationPresent(NotTransactional.class)) { |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
TransactionAttribute transactionAttribute = |
|
|
|
TransactionAttribute transactionAttribute = this.attributeSource.getTransactionAttribute(testMethod, |
|
|
|
this.attributeSource.getTransactionAttribute(testMethod, testContext.getTestClass()); |
|
|
|
testContext.getTestClass()); |
|
|
|
TransactionDefinition transactionDefinition = null; |
|
|
|
TransactionDefinition transactionDefinition = null; |
|
|
|
if (transactionAttribute != null) { |
|
|
|
if (transactionAttribute != null) { |
|
|
|
transactionDefinition = new DelegatingTransactionAttribute(transactionAttribute) { |
|
|
|
transactionDefinition = new DelegatingTransactionAttribute(transactionAttribute) { |
|
|
|
|
|
|
|
|
|
|
|
public String getName() { |
|
|
|
public String getName() { |
|
|
|
return testMethod.getName(); |
|
|
|
return testMethod.getName(); |
|
|
|
} |
|
|
|
} |
|
|
@ -144,14 +144,15 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis |
|
|
|
|
|
|
|
|
|
|
|
if (transactionDefinition != null) { |
|
|
|
if (transactionDefinition != null) { |
|
|
|
if (logger.isDebugEnabled()) { |
|
|
|
if (logger.isDebugEnabled()) { |
|
|
|
logger.debug("Explicit transaction definition [" + transactionDefinition + |
|
|
|
logger.debug("Explicit transaction definition [" + transactionDefinition + "] found for test context [" |
|
|
|
"] found for test context [" + testContext + "]"); |
|
|
|
+ testContext + "]"); |
|
|
|
} |
|
|
|
} |
|
|
|
String qualifier = transactionAttribute.getQualifier(); |
|
|
|
String qualifier = transactionAttribute.getQualifier(); |
|
|
|
PlatformTransactionManager tm; |
|
|
|
PlatformTransactionManager tm; |
|
|
|
if (StringUtils.hasLength(qualifier)) { |
|
|
|
if (StringUtils.hasLength(qualifier)) { |
|
|
|
// Use autowire-capable factory in order to support extended qualifier matching
|
|
|
|
// Use autowire-capable factory in order to support extended
|
|
|
|
// (only exposed on the internal BeanFactory, not on the ApplicationContext).
|
|
|
|
// qualifier matching (only exposed on the internal BeanFactory,
|
|
|
|
|
|
|
|
// not on the ApplicationContext).
|
|
|
|
BeanFactory bf = testContext.getApplicationContext().getAutowireCapableBeanFactory(); |
|
|
|
BeanFactory bf = testContext.getApplicationContext().getAutowireCapableBeanFactory(); |
|
|
|
tm = TransactionAspectUtils.getTransactionManager(bf, qualifier); |
|
|
|
tm = TransactionAspectUtils.getTransactionManager(bf, qualifier); |
|
|
|
} |
|
|
|
} |
|
|
@ -231,8 +232,8 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis |
|
|
|
for (Method method : methods) { |
|
|
|
for (Method method : methods) { |
|
|
|
try { |
|
|
|
try { |
|
|
|
if (logger.isDebugEnabled()) { |
|
|
|
if (logger.isDebugEnabled()) { |
|
|
|
logger.debug("Executing @AfterTransaction method [" + method + "] for test context [" + |
|
|
|
logger.debug("Executing @AfterTransaction method [" + method + "] for test context [" + testContext |
|
|
|
testContext + "]"); |
|
|
|
+ "]"); |
|
|
|
} |
|
|
|
} |
|
|
|
method.invoke(testContext.getTestInstance()); |
|
|
|
method.invoke(testContext.getTestInstance()); |
|
|
|
} |
|
|
|
} |
|
|
@ -241,15 +242,15 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis |
|
|
|
if (afterTransactionException == null) { |
|
|
|
if (afterTransactionException == null) { |
|
|
|
afterTransactionException = targetException; |
|
|
|
afterTransactionException = targetException; |
|
|
|
} |
|
|
|
} |
|
|
|
logger.error("Exception encountered while executing @AfterTransaction method [" + method + |
|
|
|
logger.error("Exception encountered while executing @AfterTransaction method [" + method |
|
|
|
"] for test context [" + testContext + "]", targetException); |
|
|
|
+ "] for test context [" + testContext + "]", targetException); |
|
|
|
} |
|
|
|
} |
|
|
|
catch (Exception ex) { |
|
|
|
catch (Exception ex) { |
|
|
|
if (afterTransactionException == null) { |
|
|
|
if (afterTransactionException == null) { |
|
|
|
afterTransactionException = ex; |
|
|
|
afterTransactionException = ex; |
|
|
|
} |
|
|
|
} |
|
|
|
logger.error("Exception encountered while executing @AfterTransaction method [" + method + |
|
|
|
logger.error("Exception encountered while executing @AfterTransaction method [" + method |
|
|
|
"] for test context [" + testContext + "]", ex); |
|
|
|
+ "] for test context [" + testContext + "]", ex); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -270,8 +271,8 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis |
|
|
|
txContext.startTransaction(); |
|
|
|
txContext.startTransaction(); |
|
|
|
++this.transactionsStarted; |
|
|
|
++this.transactionsStarted; |
|
|
|
if (logger.isInfoEnabled()) { |
|
|
|
if (logger.isInfoEnabled()) { |
|
|
|
logger.info("Began transaction (" + this.transactionsStarted + "): transaction manager [" + |
|
|
|
logger.info("Began transaction (" + this.transactionsStarted + "): transaction manager [" |
|
|
|
txContext.transactionManager + "]; rollback [" + isRollback(testContext) + "]"); |
|
|
|
+ txContext.transactionManager + "]; rollback [" + isRollback(testContext) + "]"); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -285,13 +286,13 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis |
|
|
|
private void endTransaction(TestContext testContext, TransactionContext txContext) throws Exception { |
|
|
|
private void endTransaction(TestContext testContext, TransactionContext txContext) throws Exception { |
|
|
|
boolean rollback = isRollback(testContext); |
|
|
|
boolean rollback = isRollback(testContext); |
|
|
|
if (logger.isTraceEnabled()) { |
|
|
|
if (logger.isTraceEnabled()) { |
|
|
|
logger.trace("Ending transaction for test context [" + testContext + "]; transaction manager [" + |
|
|
|
logger.trace("Ending transaction for test context [" + testContext + "]; transaction manager [" |
|
|
|
txContext.transactionStatus + "]; rollback [" + rollback + "]"); |
|
|
|
+ txContext.transactionStatus + "]; rollback [" + rollback + "]"); |
|
|
|
} |
|
|
|
} |
|
|
|
txContext.endTransaction(rollback); |
|
|
|
txContext.endTransaction(rollback); |
|
|
|
if (logger.isInfoEnabled()) { |
|
|
|
if (logger.isInfoEnabled()) { |
|
|
|
logger.info((rollback ? "Rolled back" : "Committed") + |
|
|
|
logger.info((rollback ? "Rolled back" : "Committed") |
|
|
|
" transaction after test execution for test context [" + testContext + "]"); |
|
|
|
+ " transaction after test execution for test context [" + testContext + "]"); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -310,8 +311,8 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis |
|
|
|
} |
|
|
|
} |
|
|
|
catch (BeansException ex) { |
|
|
|
catch (BeansException ex) { |
|
|
|
if (logger.isWarnEnabled()) { |
|
|
|
if (logger.isWarnEnabled()) { |
|
|
|
logger.warn("Caught exception while retrieving transaction manager with bean name [" + |
|
|
|
logger.warn("Caught exception while retrieving transaction manager with bean name [" + tmName |
|
|
|
tmName + "] for test context [" + testContext + "]", ex); |
|
|
|
+ "] for test context [" + testContext + "]", ex); |
|
|
|
} |
|
|
|
} |
|
|
|
throw ex; |
|
|
|
throw ex; |
|
|
|
} |
|
|
|
} |
|
|
@ -345,15 +346,15 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis |
|
|
|
if (rollbackAnnotation != null) { |
|
|
|
if (rollbackAnnotation != null) { |
|
|
|
boolean rollbackOverride = rollbackAnnotation.value(); |
|
|
|
boolean rollbackOverride = rollbackAnnotation.value(); |
|
|
|
if (logger.isDebugEnabled()) { |
|
|
|
if (logger.isDebugEnabled()) { |
|
|
|
logger.debug("Method-level @Rollback(" + rollbackOverride + ") overrides default rollback [" + |
|
|
|
logger.debug("Method-level @Rollback(" + rollbackOverride + ") overrides default rollback [" + rollback |
|
|
|
rollback + "] for test context [" + testContext + "]"); |
|
|
|
+ "] for test context [" + testContext + "]"); |
|
|
|
} |
|
|
|
} |
|
|
|
rollback = rollbackOverride; |
|
|
|
rollback = rollbackOverride; |
|
|
|
} |
|
|
|
} |
|
|
|
else { |
|
|
|
else { |
|
|
|
if (logger.isDebugEnabled()) { |
|
|
|
if (logger.isDebugEnabled()) { |
|
|
|
logger.debug("No method-level @Rollback override: using default rollback [" + |
|
|
|
logger.debug("No method-level @Rollback override: using default rollback [" + rollback |
|
|
|
rollback + "] for test context [" + testContext + "]"); |
|
|
|
+ "] for test context [" + testContext + "]"); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
return rollback; |
|
|
|
return rollback; |
|
|
@ -478,16 +479,18 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis |
|
|
|
defaultRollback = (Boolean) AnnotationUtils.getDefaultValue(annotationType, "defaultRollback"); |
|
|
|
defaultRollback = (Boolean) AnnotationUtils.getDefaultValue(annotationType, "defaultRollback"); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
TransactionConfigurationAttributes configAttributes = |
|
|
|
TransactionConfigurationAttributes configAttributes = new TransactionConfigurationAttributes( |
|
|
|
new TransactionConfigurationAttributes(transactionManagerName, defaultRollback); |
|
|
|
transactionManagerName, defaultRollback); |
|
|
|
if (logger.isDebugEnabled()) { |
|
|
|
if (logger.isDebugEnabled()) { |
|
|
|
logger.debug("Retrieved TransactionConfigurationAttributes [" + configAttributes + "] for class [" + clazz + "]"); |
|
|
|
logger.debug("Retrieved TransactionConfigurationAttributes [" + configAttributes + "] for class [" |
|
|
|
|
|
|
|
+ clazz + "]"); |
|
|
|
} |
|
|
|
} |
|
|
|
this.configurationAttributes = configAttributes; |
|
|
|
this.configurationAttributes = configAttributes; |
|
|
|
} |
|
|
|
} |
|
|
|
return this.configurationAttributes; |
|
|
|
return this.configurationAttributes; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Internal context holder for a specific test method. |
|
|
|
* Internal context holder for a specific test method. |
|
|
|
*/ |
|
|
|
*/ |
|
|
@ -499,7 +502,9 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis |
|
|
|
|
|
|
|
|
|
|
|
private TransactionStatus transactionStatus; |
|
|
|
private TransactionStatus transactionStatus; |
|
|
|
|
|
|
|
|
|
|
|
public TransactionContext(PlatformTransactionManager transactionManager, TransactionDefinition transactionDefinition) { |
|
|
|
|
|
|
|
|
|
|
|
public TransactionContext(PlatformTransactionManager transactionManager, |
|
|
|
|
|
|
|
TransactionDefinition transactionDefinition) { |
|
|
|
this.transactionManager = transactionManager; |
|
|
|
this.transactionManager = transactionManager; |
|
|
|
this.transactionDefinition = transactionDefinition; |
|
|
|
this.transactionDefinition = transactionDefinition; |
|
|
|
} |
|
|
|
} |
|
|
|