Skip to content

Commit

Permalink
Support TransactionManagementConfigurer in the TCF
Browse files Browse the repository at this point in the history
Currently the Spring TestContext Framework looks up a
PlatformTransactionManager bean named "transactionManager". The exact
name of the bean can be overridden via @TransactionConfiguration or
@transactional; however, the bean will always be looked up 'by name'.

The TransactionManagementConfigurer interface that was introduced in
Spring 3.1 provides a programmatic approach to specifying the
PlatformTransactionManager bean to be used for annotation-driven
transaction management, and that bean is not required to be named
"transactionManager". However, as of Spring 3.1.2, using the
TransactionManagementConfigurer on a @configuration class has no effect
on how the TestContext framework looks up the transaction manager.
Consequently, if an explicit name or qualifier has not been specified,
the bean must be named "transactionManager" in order for a transactional
integration test to work.

This commit addresses this issue by refactoring the
TransactionalTestExecutionListener so that it looks up and delegates to
a single TransactionManagementConfigurer as part of the algorithm for
determining the transaction manager.

Issue: SPR-9604
  • Loading branch information
sbrannen committed Jul 27, 2012
1 parent f21fe33 commit 2b7a629
Show file tree
Hide file tree
Showing 11 changed files with 155 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,19 @@

/**
* The bean name of the {@link org.springframework.transaction.PlatformTransactionManager
* PlatformTransactionManager} that is to be used to drive transactions.
* PlatformTransactionManager} that should be used to drive transactions.
*
* <p>This attribute is not required and only needs to be specified explicitly
* if there are multiple beans of type {@code PlatformTransactionManager} in
* the test's {@code ApplicationContext} and the bean name of the desired
* {@code PlatformTransactionManager} is not "transactionManager".
* <p>This attribute is not required and only needs to be declared if there
* are multiple beans of type {@code PlatformTransactionManager} in the test's
* {@code ApplicationContext} <em>and</em> if one of the following is true.
* <ul>
* <li>the bean name of the desired {@code PlatformTransactionManager} is not
* "transactionManager"</li>
* <li>{@link org.springframework.transaction.annotation.TransactionManagementConfigurer
* TransactionManagementConfigurer} was not implemented to specify which
* {@code PlatformTransactionManager} bean should be used for annotation-driven
* transaction management
* </ul>
*
* <p><b>NOTE:</b> The XML {@code <tx:annotation-driven>} element also refers
* to a bean named "transactionManager" by default. If you are using both
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource;
import org.springframework.transaction.annotation.TransactionManagementConfigurer;
import org.springframework.transaction.interceptor.DelegatingTransactionAttribute;
import org.springframework.transaction.interceptor.TransactionAttribute;
import org.springframework.transaction.interceptor.TransactionAttributeSource;
Expand All @@ -68,11 +69,15 @@
*
* <p>Transactional commit and rollback behavior can be configured via the
* class-level {@link TransactionConfiguration @TransactionConfiguration} and
* method-level {@link Rollback @Rollback} annotations. 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 is
* to be used to drive transactions.
* method-level {@link Rollback @Rollback} annotations.
*
* <p>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,
* {@link TransactionManagementConfigurer} can be implemented in an
* {@link org.springframework.context.annotation.Configuration @Configuration}
* class.
*
* <p>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
Expand Down Expand Up @@ -349,13 +354,25 @@ protected final PlatformTransactionManager getTransactionManager(TestContext tes
return bf.getBean(tmName, PlatformTransactionManager.class);
}

// look up single bean by type
if (bf instanceof ListableBeanFactory) {
ListableBeanFactory lbf = (ListableBeanFactory) bf;
Map<String, PlatformTransactionManager> beansOfType = BeanFactoryUtils.beansOfTypeIncludingAncestors(

// look up single bean by type
Map<String, PlatformTransactionManager> txMgrs = BeanFactoryUtils.beansOfTypeIncludingAncestors(
lbf, PlatformTransactionManager.class);
if (beansOfType.size() == 1) {
return beansOfType.values().iterator().next();
if (txMgrs.size() == 1) {
return txMgrs.values().iterator().next();
}

// look up single TransactionManagementConfigurer
Map<String, TransactionManagementConfigurer> configurers = BeanFactoryUtils.beansOfTypeIncludingAncestors(
lbf, TransactionManagementConfigurer.class);
if (configurers.size() > 1) {
throw new IllegalStateException(
"Only one TransactionManagementConfigurer may exist in the ApplicationContext");
}
if (configurers.size() == 1) {
return configurers.values().iterator().next().annotationDrivenTransactionManager();
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright 2002-2012 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.junit4.spr9604;

import static org.junit.Assert.*;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.AfterTransaction;
import org.springframework.test.context.transaction.BeforeTransaction;
import org.springframework.test.transaction.CallCountingTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.TransactionManagementConfigurer;
import org.springframework.transaction.annotation.Transactional;

/**
* Integration tests that verify the behavior requested in
* <a href="https://jira.springsource.org/browse/SPR-9604">SPR-9604</a>.
*
* @author Sam Brannen
* @since 3.2
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@Transactional
public class LookUpTxMgrViaTransactionManagementConfigurerTests {

private static final CallCountingTransactionManager txManager1 = new CallCountingTransactionManager();
private static final CallCountingTransactionManager txManager2 = new CallCountingTransactionManager();


@Configuration
static class Config implements TransactionManagementConfigurer {

public PlatformTransactionManager annotationDrivenTransactionManager() {
return txManager1();
}

@Bean
public PlatformTransactionManager txManager1() {
return txManager1;
}

@Bean
public PlatformTransactionManager txManager2() {
return txManager2;
}
}


@BeforeTransaction
public void beforeTransaction() {
txManager1.clear();
txManager2.clear();
}

@Test
public void transactionalTest() {
assertEquals(1, txManager1.begun);
assertEquals(1, txManager1.inflight);
assertEquals(0, txManager1.commits);
assertEquals(0, txManager1.rollbacks);

assertEquals(0, txManager2.begun);
assertEquals(0, txManager2.inflight);
assertEquals(0, txManager2.commits);
assertEquals(0, txManager2.rollbacks);
}

@AfterTransaction
public void afterTransaction() {
assertEquals(1, txManager1.begun);
assertEquals(0, txManager1.inflight);
assertEquals(0, txManager1.commits);
assertEquals(1, txManager1.rollbacks);

assertEquals(0, txManager2.begun);
assertEquals(0, txManager2.inflight);
assertEquals(0, txManager2.commits);
assertEquals(0, txManager2.rollbacks);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.AfterTransaction;
import org.springframework.test.context.transaction.BeforeTransaction;
import org.springframework.test.transaction.CallCountingTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

Expand All @@ -42,7 +40,6 @@ public class LookUpNonexistentTxMgrTests {

private static final CallCountingTransactionManager txManager = new CallCountingTransactionManager();


@Configuration
static class Config {

Expand All @@ -52,26 +49,11 @@ public PlatformTransactionManager transactionManager() {
}
}


@BeforeTransaction
public void beforeTransaction() {
txManager.clear();
}

@Test
public void lookUpNothing() {
public void nonTransactionalTest() {
assertEquals(0, txManager.begun);
assertEquals(0, txManager.inflight);
assertEquals(0, txManager.commits);
assertEquals(0, txManager.rollbacks);
}

@AfterTransaction
public void afterTransaction() {
assertEquals(0, txManager.begun);
assertEquals(0, txManager.inflight);
assertEquals(0, txManager.commits);
assertEquals(0, txManager.rollbacks);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public void beforeTransaction() {
}

@Test
public void lookUpByTypeAndDefaultName() {
public void transactionalTest() {
assertEquals(1, txManager1.begun);
assertEquals(1, txManager1.inflight);
assertEquals(0, txManager1.commits);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public void beforeTransaction() {
}

@Test
public void lookUpByTypeAndName() {
public void transactionalTest() {
assertEquals(1, txManager1.begun);
assertEquals(1, txManager1.inflight);
assertEquals(0, txManager1.commits);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public void beforeTransaction() {
}

@Test
public void lookUpByTypeAndQualifier() {
public void transactionalTest() {
assertEquals(1, txManager1.begun);
assertEquals(1, txManager1.inflight);
assertEquals(0, txManager1.commits);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public void beforeTransaction() {

@Transactional("txManager1")
@Test
public void lookUpByTypeAndQualifier() {
public void transactionalTest() {
assertEquals(1, txManager1.begun);
assertEquals(1, txManager1.inflight);
assertEquals(0, txManager1.commits);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public void beforeTransaction() {
}

@Test
public void lookUpByType() {
public void transactionalTest() {
assertEquals(1, txManager.begun);
assertEquals(1, txManager.inflight);
assertEquals(0, txManager.commits);
Expand Down
1 change: 1 addition & 0 deletions src/dist/changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Changes in version 3.2 M2 (2012-08-xx)
* support content negotiation options in MVC namespace and MVC Java config
* support named dispatchers in MockServletContext (SPR-9587)
* support single, unqualified tx manager in the TestContext framework (SPR-9645)
* support TransactionManagementConfigurer in the TestContext framework (SPR-9604)


Changes in version 3.2 M1 (2012-05-28)
Expand Down
15 changes: 9 additions & 6 deletions src/reference/docbook/testing.xml
Original file line number Diff line number Diff line change
Expand Up @@ -630,9 +630,9 @@ public class CustomTestExecutionListenerTests {

<para>Defines class-level metadata for configuring transactional
tests. Specifically, the bean name of the
<interfacename>PlatformTransactionManager</interfacename> that is
to be used to drive transactions can be explicitly specified if
there are multiple beans of type
<interfacename>PlatformTransactionManager</interfacename> that
should be used to drive transactions can be explicitly specified
if there are multiple beans of type
<interfacename>PlatformTransactionManager</interfacename> in the
test's <interfacename>ApplicationContext</interfacename> and the
bean name of the desired
Expand All @@ -657,9 +657,12 @@ public class CustomConfiguredTransactionalTests {
configuration, you can avoid using
<interfacename>@TransactionConfiguration</interfacename>
altogether. In other words, if you have only one transaction
manger (or your transaction manager bean is named
"transactionManager") and if you want transactions to roll back
automatically, there is no need to annotate your test class with
manger — or if you have multiple transaction mangers but the
transaction manager for tests is named "transactionManager" or
specified via a
<interfacename>TransactionManagementConfigurer</interfacename> —
and if you want transactions to roll back automatically, then
there is no need to annotate your test class with
<interfacename>@TransactionConfiguration</interfacename>.</para>
</note>
</listitem>
Expand Down

0 comments on commit 2b7a629

Please sign in to comment.