From bb75662a7e8616cebf9cbce6440d846c7e131be6 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 12 Mar 2010 20:44:41 +0000 Subject: [PATCH] @Transactional qualifiers work in unit tests as well (SPR-6892) --- .../TransactionalTestExecutionListener.java | 82 +++++++------- ...odLevelTransactionalSpringRunnerTests.java | 10 +- .../junit4/transactionalTests-context.xml | 12 ++- .../interceptor/TransactionAspectSupport.java | 41 +------ .../interceptor/TransactionAspectUtils.java | 102 ++++++++++++++++++ 5 files changed, 163 insertions(+), 84 deletions(-) create mode 100644 org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAspectUtils.java diff --git a/org.springframework.test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java b/org.springframework.test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java index c62c31e35b..c44aaf7c0a 100644 --- a/org.springframework.test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java +++ b/org.springframework.test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2010 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. @@ -29,6 +29,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.test.annotation.NotTransactional; import org.springframework.test.annotation.Rollback; @@ -40,10 +41,12 @@ import org.springframework.transaction.TransactionException; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource; import org.springframework.transaction.interceptor.DelegatingTransactionAttribute; +import org.springframework.transaction.interceptor.TransactionAspectUtils; import org.springframework.transaction.interceptor.TransactionAttribute; import org.springframework.transaction.interceptor.TransactionAttributeSource; import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; /** *

@@ -94,14 +97,13 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis protected final TransactionAttributeSource attributeSource = new AnnotationTransactionAttributeSource(); - private TransactionConfigurationAttributes configAttributes; + private TransactionConfigurationAttributes configurationAttributes; private volatile int transactionsStarted = 0; private final Map transactionContextCache = Collections.synchronizedMap(new IdentityHashMap()); - /** * If the test method of the supplied {@link TestContext test context} is * configured to run within a transaction, this method will run @@ -144,8 +146,18 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis logger.debug("Explicit transaction definition [" + transactionDefinition + "] found for test context [" + testContext + "]"); } - TransactionContext txContext = - new TransactionContext(getTransactionManager(testContext), transactionDefinition); + String qualifier = transactionAttribute.getQualifier(); + PlatformTransactionManager tm; + if (StringUtils.hasLength(qualifier)) { + // Use autowire-capable factory in order to support extended qualifier matching + // (only exposed on the internal BeanFactory, not on the ApplicationContext). + BeanFactory bf = testContext.getApplicationContext().getAutowireCapableBeanFactory(); + tm = TransactionAspectUtils.getTransactionManager(bf, qualifier); + } + else { + tm = getTransactionManager(testContext); + } + TransactionContext txContext = new TransactionContext(tm, transactionDefinition); runBeforeTransactionMethods(testContext); startNewTransaction(testContext, txContext); this.transactionContextCache.put(testMethod, txContext); @@ -291,18 +303,14 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis * @throws BeansException if an error occurs while retrieving the transaction manager */ protected final PlatformTransactionManager getTransactionManager(TestContext testContext) { - if (this.configAttributes == null) { - this.configAttributes = retrieveTransactionConfigurationAttributes(testContext.getTestClass()); - } - String transactionManagerName = this.configAttributes.getTransactionManagerName(); + String tmName = retrieveConfigurationAttributes(testContext).getTransactionManagerName(); try { - return (PlatformTransactionManager) testContext.getApplicationContext().getBean( - transactionManagerName, PlatformTransactionManager.class); + return testContext.getApplicationContext().getBean(tmName, PlatformTransactionManager.class); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Caught exception while retrieving transaction manager with bean name [" + - transactionManagerName + "] for test context [" + testContext + "]", ex); + tmName + "] for test context [" + testContext + "]", ex); } throw ex; } @@ -317,7 +325,7 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis * @throws Exception if an error occurs while determining the default rollback flag */ protected final boolean isDefaultRollback(TestContext testContext) throws Exception { - return retrieveTransactionConfigurationAttributes(testContext.getTestClass()).isDefaultRollback(); + return retrieveConfigurationAttributes(testContext).isDefaultRollback(); } /** @@ -439,7 +447,6 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis } /** - *

* Retrieves the {@link TransactionConfigurationAttributes} for the * specified {@link Class class} which may optionally declare or inherit a * {@link TransactionConfiguration @TransactionConfiguration}. If a @@ -450,33 +457,36 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis * the configuration attributes should be retrieved * @return a new TransactionConfigurationAttributes instance */ - private TransactionConfigurationAttributes retrieveTransactionConfigurationAttributes(Class clazz) { - Class annotationType = TransactionConfiguration.class; - TransactionConfiguration config = clazz.getAnnotation(annotationType); - if (logger.isDebugEnabled()) { - logger.debug("Retrieved @TransactionConfiguration [" + config + "] for test class [" + clazz + "]"); - } + private TransactionConfigurationAttributes retrieveConfigurationAttributes(TestContext testContext) { + if (this.configurationAttributes == null) { + Class clazz = testContext.getTestClass(); + Class annotationType = TransactionConfiguration.class; + TransactionConfiguration config = clazz.getAnnotation(annotationType); + if (logger.isDebugEnabled()) { + logger.debug("Retrieved @TransactionConfiguration [" + config + "] for test class [" + clazz + "]"); + } - String transactionManagerName; - boolean defaultRollback; - if (config != null) { - transactionManagerName = config.transactionManager(); - defaultRollback = config.defaultRollback(); - } - else { - transactionManagerName = (String) AnnotationUtils.getDefaultValue(annotationType, "transactionManager"); - defaultRollback = (Boolean) AnnotationUtils.getDefaultValue(annotationType, "defaultRollback"); - } + String transactionManagerName; + boolean defaultRollback; + if (config != null) { + transactionManagerName = config.transactionManager(); + defaultRollback = config.defaultRollback(); + } + else { + transactionManagerName = (String) AnnotationUtils.getDefaultValue(annotationType, "transactionManager"); + defaultRollback = (Boolean) AnnotationUtils.getDefaultValue(annotationType, "defaultRollback"); + } - TransactionConfigurationAttributes configAttributes = - new TransactionConfigurationAttributes(transactionManagerName, defaultRollback); - if (logger.isDebugEnabled()) { - logger.debug("Retrieved TransactionConfigurationAttributes [" + configAttributes + "] for class [" + clazz + "]"); + TransactionConfigurationAttributes configAttributes = + new TransactionConfigurationAttributes(transactionManagerName, defaultRollback); + if (logger.isDebugEnabled()) { + logger.debug("Retrieved TransactionConfigurationAttributes [" + configAttributes + "] for class [" + clazz + "]"); + } + this.configurationAttributes = configAttributes; } - return configAttributes; + return this.configurationAttributes; } - /** * Internal context holder for a specific test method. */ diff --git a/org.springframework.test/src/test/java/org/springframework/test/context/junit4/MethodLevelTransactionalSpringRunnerTests.java b/org.springframework.test/src/test/java/org/springframework/test/context/junit4/MethodLevelTransactionalSpringRunnerTests.java index 1140a48ade..21c8eb62a7 100644 --- a/org.springframework.test/src/test/java/org/springframework/test/context/junit4/MethodLevelTransactionalSpringRunnerTests.java +++ b/org.springframework.test/src/test/java/org/springframework/test/context/junit4/MethodLevelTransactionalSpringRunnerTests.java @@ -16,13 +16,11 @@ package org.springframework.test.context.junit4; -import static org.junit.Assert.assertEquals; -import static org.springframework.test.transaction.TransactionTestUtils.assertInTransaction; - import javax.annotation.Resource; import javax.sql.DataSource; import org.junit.AfterClass; +import static org.junit.Assert.*; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -34,6 +32,8 @@ import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; import org.springframework.test.context.support.DirtiesContextTestExecutionListener; import org.springframework.test.context.transaction.TransactionalTestExecutionListener; +import org.springframework.test.context.transaction.TransactionConfiguration; +import static org.springframework.test.transaction.TransactionTestUtils.*; import org.springframework.transaction.annotation.Transactional; /** @@ -85,7 +85,7 @@ public class MethodLevelTransactionalSpringRunnerTests extends AbstractTransacti } @Test - @Transactional + @Transactional("transactionManager2") public void modifyTestDataWithinTransaction() { assertInTransaction(true); assertEquals("Deleting bob", 1, deletePerson(simpleJdbcTemplate, BOB)); @@ -109,7 +109,7 @@ public class MethodLevelTransactionalSpringRunnerTests extends AbstractTransacti public static class DatabaseSetup { @Resource - public void setDataSource(DataSource dataSource) { + public void setDataSource2(DataSource dataSource) { simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource); createPersonTable(simpleJdbcTemplate); } diff --git a/org.springframework.test/src/test/java/org/springframework/test/context/junit4/transactionalTests-context.xml b/org.springframework.test/src/test/java/org/springframework/test/context/junit4/transactionalTests-context.xml index d84211548d..47767798ce 100644 --- a/org.springframework.test/src/test/java/org/springframework/test/context/junit4/transactionalTests-context.xml +++ b/org.springframework.test/src/test/java/org/springframework/test/context/junit4/transactionalTests-context.xml @@ -4,12 +4,16 @@ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> + p:driverClassName="org.hsqldb.jdbcDriver" p:url="jdbc:hsqldb:mem:transactional_tests" p:username="sa" p:password=""/> - + + p:dataSource-ref="dataSource" p:transactionSynchronizationName="SYNCHRONIZATION_NEVER"/> + + + diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAspectSupport.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAspectSupport.java index 02bd94b64d..d32b4a41a9 100644 --- a/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAspectSupport.java +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAspectSupport.java @@ -17,7 +17,6 @@ package org.springframework.transaction.interceptor; import java.lang.reflect.Method; -import java.util.Map; import java.util.Properties; import org.apache.commons.logging.Log; @@ -28,18 +27,13 @@ import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.ListableBeanFactory; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.support.AbstractBeanDefinition; -import org.springframework.beans.factory.support.AutowireCandidateQualifier; import org.springframework.core.NamedThreadLocal; import org.springframework.transaction.NoTransactionException; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.TransactionSystemException; +import org.springframework.transaction.interceptor.TransactionAspectUtils; import org.springframework.util.ClassUtils; -import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** @@ -249,38 +243,7 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init } String qualifier = txAttr.getQualifier(); if (StringUtils.hasLength(qualifier)) { - if (!(this.beanFactory instanceof ConfigurableListableBeanFactory)) { - throw new IllegalStateException("BeanFactory required to be a ConfigurableListableBeanFactory " + - "for resolution of qualifier '" + qualifier + "': " + this.beanFactory.getClass()); - } - ConfigurableListableBeanFactory bf = (ConfigurableListableBeanFactory) this.beanFactory; - Map tms = - BeanFactoryUtils.beansOfTypeIncludingAncestors(bf, PlatformTransactionManager.class); - PlatformTransactionManager chosen = null; - for (String beanName : tms.keySet()) { - if (bf.containsBeanDefinition(beanName)) { - BeanDefinition bd = bf.getBeanDefinition(beanName); - if (bd instanceof AbstractBeanDefinition) { - AbstractBeanDefinition abd = (AbstractBeanDefinition) bd; - AutowireCandidateQualifier candidate = abd.getQualifier(Qualifier.class.getName()); - if ((candidate != null && qualifier.equals(candidate.getAttribute(AutowireCandidateQualifier.VALUE_KEY))) || - qualifier.equals(beanName) || ObjectUtils.containsElement(bf.getAliases(beanName), qualifier)) { - if (chosen != null) { - throw new IllegalStateException("No unique PlatformTransactionManager bean found " + - "for qualifier '" + qualifier + "'"); - } - chosen = tms.get(beanName); - } - } - } - } - if (chosen != null) { - return chosen; - } - else { - throw new IllegalStateException( - "No matching PlatformTransactionManager bean found for qualifier '" + qualifier + "'"); - } + return TransactionAspectUtils.getTransactionManager(this.beanFactory, qualifier); } else if (this.transactionManagerBeanName != null) { return this.beanFactory.getBean(this.transactionManagerBeanName, PlatformTransactionManager.class); diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAspectUtils.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAspectUtils.java new file mode 100644 index 0000000000..de87a67f78 --- /dev/null +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAspectUtils.java @@ -0,0 +1,102 @@ +/* + * Copyright 2002-2010 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.transaction.interceptor; + +import java.util.Map; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.AutowireCandidateQualifier; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.util.ObjectUtils; + +/** + * Utility methods for obtaining a PlatformTransactionManager by + * {@link TransactionAttribute#getQualifier() qualifier value}. + * + * @author Juergen Hoeller + * @since 3.0.2 + */ +public abstract class TransactionAspectUtils { + + /** + * Obtain a PlatformTransactionManager from the given BeanFactory, + * matching the given qualifier. + * @param beanFactory the BeanFactory to get the PlatformTransactionManager bean from + * @param qualifier the qualifier for selecting between multiple PlatformTransactionManager matches + * @return the chosen PlatformTransactionManager (never null) + * @throws IllegalStateException if no matching PlatformTransactionManager bean found + */ + public static PlatformTransactionManager getTransactionManager(BeanFactory beanFactory, String qualifier) { + if (beanFactory instanceof ConfigurableListableBeanFactory) { + // Full qualifier matching supported. + return getTransactionManager((ConfigurableListableBeanFactory) beanFactory, qualifier); + } + else if (beanFactory.containsBean(qualifier)) { + // Fallback: PlatformTransactionManager at least found by bean name. + return beanFactory.getBean(qualifier, PlatformTransactionManager.class); + } + else { + throw new IllegalStateException("No matching PlatformTransactionManager bean found for bean name '" + + qualifier + "'! (Note: Qualifier matching not supported because given BeanFactory does not " + + "implement ConfigurableListableBeanFactory.)"); + } + } + + /** + * Obtain a PlatformTransactionManager from the given BeanFactory, + * matching the given qualifier. + * @param beanFactory the BeanFactory to get the PlatformTransactionManager bean from + * @param qualifier the qualifier for selecting between multiple PlatformTransactionManager matches + * @return the chosen PlatformTransactionManager (never null) + * @throws IllegalStateException if no matching PlatformTransactionManager bean found + */ + public static PlatformTransactionManager getTransactionManager(ConfigurableListableBeanFactory bf, String qualifier) { + Map tms = + BeanFactoryUtils.beansOfTypeIncludingAncestors(bf, PlatformTransactionManager.class); + PlatformTransactionManager chosen = null; + for (String beanName : tms.keySet()) { + if (bf.containsBeanDefinition(beanName)) { + BeanDefinition bd = bf.getBeanDefinition(beanName); + if (bd instanceof AbstractBeanDefinition) { + AbstractBeanDefinition abd = (AbstractBeanDefinition) bd; + AutowireCandidateQualifier candidate = abd.getQualifier(Qualifier.class.getName()); + if ((candidate != null && qualifier.equals(candidate.getAttribute(AutowireCandidateQualifier.VALUE_KEY))) || + qualifier.equals(beanName) || ObjectUtils.containsElement(bf.getAliases(beanName), qualifier)) { + if (chosen != null) { + throw new IllegalStateException("No unique PlatformTransactionManager bean found " + + "for qualifier '" + qualifier + "'"); + } + chosen = tms.get(beanName); + } + } + } + } + if (chosen != null) { + return chosen; + } + else { + throw new IllegalStateException("No matching PlatformTransactionManager bean found for qualifier '" + + qualifier + "' - neither qualifier match nor bean name match!"); + } + } + +}