diff --git a/spring-test/src/main/java/org/springframework/test/context/jdbc/Sql.java b/spring-test/src/main/java/org/springframework/test/context/jdbc/Sql.java
index bc9af8a977..3777aef4e9 100644
--- a/spring-test/src/main/java/org/springframework/test/context/jdbc/Sql.java
+++ b/spring-test/src/main/java/org/springframework/test/context/jdbc/Sql.java
@@ -22,9 +22,6 @@ import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
-import org.springframework.jdbc.datasource.init.ScriptUtils;
-import org.springframework.util.ResourceUtils;
-
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
@@ -39,8 +36,8 @@ import static java.lang.annotation.RetentionPolicy.*;
*
*
The configuration options provided by this annotation and
* {@link SqlConfig @SqlConfig} are equivalent to those supported by
- * {@link ScriptUtils} and
- * {@link org.springframework.jdbc.datasource.init.ResourceDatabasePopulator}
+ * {@link org.springframework.jdbc.datasource.init.ScriptUtils ScriptUtils} and
+ * {@link org.springframework.jdbc.datasource.init.ResourceDatabasePopulator ResourceDatabasePopulator}
* but are a superset of those provided by the {@code }
* XML namespace element. Consult the javadocs of individual attributes in this
* annotation and {@link SqlConfig @SqlConfig} for details.
@@ -110,9 +107,9 @@ public @interface Sql {
* absolute classpath resource, for example:
* {@code "/org/example/schema.sql"}. A path which references a
* URL (e.g., a path prefixed with
- * {@link ResourceUtils#CLASSPATH_URL_PREFIX classpath:},
- * {@link ResourceUtils#FILE_URL_PREFIX file:}, {@code http:}, etc.) will be
- * loaded using the specified resource protocol.
+ * {@link org.springframework.util.ResourceUtils#CLASSPATH_URL_PREFIX classpath:},
+ * {@link org.springframework.util.ResourceUtils#FILE_URL_PREFIX file:},
+ * {@code http:}, etc.) will be loaded using the specified resource protocol.
*
Default Script Detection
* If no SQL scripts are specified, an attempt will be made to detect a
* default script depending on where this annotation is declared.
diff --git a/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlConfig.java b/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlConfig.java
index e9ea581310..86f1531a66 100644
--- a/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlConfig.java
+++ b/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlConfig.java
@@ -21,8 +21,6 @@ import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
-import org.springframework.jdbc.datasource.init.ScriptUtils;
-
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
@@ -46,7 +44,7 @@ import static java.lang.annotation.RetentionPolicy.*;
* attribute. Thus, in order to support overrides of inherited global
* configuration, {@code @SqlConfig} attributes have an explicit
* {@code default} value of either {@code ""} for Strings or {@code DEFAULT} for
- * Enums. This approach allows local declarations {@code @SqlConfig} to
+ * Enums. This approach allows local declarations of {@code @SqlConfig} to
* selectively override individual attributes from global declarations of
* {@code @SqlConfig} by providing a value other than {@code ""} or {@code DEFAULT}.
*
@@ -91,22 +89,50 @@ public @interface SqlConfig {
DEFAULT,
/**
- * Indicates that the transaction mode to use when executing SQL scripts
- * should be inferred based on whether or not a Spring-managed
- * transaction is currently present.
- *
SQL scripts will be executed within the current transaction if present;
- * otherwise, scripts will be executed in a new transaction that will be
- * immediately committed.
- *
The current transaction will typically be managed by the
- * {@link org.springframework.test.context.transaction.TransactionalTestExecutionListener
- * TransactionalTestExecutionListener}.
+ * Indicates that the transaction mode to use when executing SQL
+ * scripts should be inferred using the rules listed below.
+ * In the context of these rules, the term "available"
+ * means that the bean for the data source or transaction manager
+ * is either explicitly specified via a corresponding annotation
+ * attribute in {@code @SqlConfig} or discoverable via conventions. See
+ * {@link org.springframework.test.context.transaction.TestContextTransactionUtils TestContextTransactionUtils}
+ * for details on the conventions used to discover such beans in
+ * the {@code ApplicationContext}.
+ *
+ *
Inference Rules
+ *
+ * - If neither a transaction manager nor a data source is
+ * available, an exception will be thrown.
+ *
- If a transaction manager is not available but a data source
+ * is available, SQL scripts will be executed directly against the
+ * data source without a transaction.
+ *
- If a transaction manager is available:
+ *
+ * - If a data source is not available, an attempt will be made
+ * to retrieve it from the transaction manager by using reflection
+ * to invoke a public method named {@code getDataSource()} on the
+ * transaction manager. If the attempt fails, an exception will be
+ * thrown.
+ *
- Using the resolved transaction manager and data source, SQL
+ * scripts will be executed within an existing transaction if
+ * present; otherwise, scripts will be executed in a new transaction
+ * that will be immediately committed. An existing
+ * transaction will typically be managed by the
+ * {@link org.springframework.test.context.transaction.TransactionalTestExecutionListener TransactionalTestExecutionListener}.
+ *
+ *
* @see #ISOLATED
+ * @see org.springframework.test.context.transaction.TestContextTransactionUtils#retrieveDataSource
+ * @see org.springframework.test.context.transaction.TestContextTransactionUtils#retrieveTransactionManager
*/
INFERRED,
/**
* Indicates that SQL scripts should always be executed in a new,
* isolated transaction that will be immediately committed.
+ * In contrast to {@link #INFERRED}, this mode requires the
+ * presence of a transaction manager and a data
+ * source.
*/
ISOLATED
}
@@ -164,18 +190,24 @@ public @interface SqlConfig {
/**
- * The bean name of the {@link javax.sql.DataSource} against which the scripts
- * should be executed.
- *
The name is only used if there is more than one bean of type
- * {@code DataSource} in the test's {@code ApplicationContext}. If there is
- * only one such bean, it is not necessary to specify a bean name.
+ * The bean name of the {@link javax.sql.DataSource} against which the
+ * scripts should be executed.
+ *
The name is only required if there is more than one bean of type
+ * {@code DataSource} in the test's {@code ApplicationContext}. If there
+ * is only one such bean, it is not necessary to specify a bean name.
*
Defaults to an empty string, requiring that one of the following is
* true:
*
+ * - An explicit bean name is defined in a global declaration of
+ * {@code @SqlConfig}.
+ *
- The data source can be retrieved from the transaction manager
+ * by using reflection to invoke a public method named
+ * {@code getDataSource()} on the transaction manager.
*
- There is only one bean of type {@code DataSource} in the test's
* {@code ApplicationContext}.
* - The {@code DataSource} to use is named {@code "dataSource"}.
*
+ * @see org.springframework.test.context.transaction.TestContextTransactionUtils#retrieveDataSource
*/
String dataSource() default "";
@@ -188,6 +220,8 @@ public @interface SqlConfig {
* Defaults to an empty string, requiring that one of the following is
* true:
*
+ * - An explicit bean name is defined in a global declaration of
+ * {@code @SqlConfig}.
*
- There is only one bean of type {@code PlatformTransactionManager} in
* the test's {@code ApplicationContext}.
* - {@link org.springframework.transaction.annotation.TransactionManagementConfigurer
@@ -197,6 +231,7 @@ public @interface SqlConfig {
*
- The {@code PlatformTransactionManager} to use is named
* {@code "transactionManager"}.
*
+ * @see org.springframework.test.context.transaction.TestContextTransactionUtils#retrieveTransactionManager
*/
String transactionManager() default "";
@@ -223,16 +258,19 @@ public @interface SqlConfig {
* SQL scripts.
* Implicitly defaults to {@code ";"} if not specified and falls back to
* {@code "\n"} as a last resort.
- *
May be set to {@link ScriptUtils#EOF_STATEMENT_SEPARATOR} to signal
- * that each script contains a single statement without a separator.
- * @see ScriptUtils#DEFAULT_STATEMENT_SEPARATOR
+ *
May be set to
+ * {@link org.springframework.jdbc.datasource.init.ScriptUtils#EOF_STATEMENT_SEPARATOR}
+ * to signal that each script contains a single statement without a
+ * separator.
+ * @see org.springframework.jdbc.datasource.init.ScriptUtils#DEFAULT_STATEMENT_SEPARATOR
+ * @see org.springframework.jdbc.datasource.init.ScriptUtils#EOF_STATEMENT_SEPARATOR
*/
String separator() default "";
/**
* The prefix that identifies single-line comments within the SQL scripts.
*
Implicitly defaults to {@code "--"}.
- * @see ScriptUtils#DEFAULT_COMMENT_PREFIX
+ * @see org.springframework.jdbc.datasource.init.ScriptUtils#DEFAULT_COMMENT_PREFIX
*/
String commentPrefix() default "";
@@ -240,7 +278,7 @@ public @interface SqlConfig {
* The start delimiter that identifies block comments within the SQL scripts.
*
Implicitly defaults to {@code "/*"}.
* @see #blockCommentEndDelimiter
- * @see ScriptUtils#DEFAULT_BLOCK_COMMENT_START_DELIMITER
+ * @see org.springframework.jdbc.datasource.init.ScriptUtils#DEFAULT_BLOCK_COMMENT_START_DELIMITER
*/
String blockCommentStartDelimiter() default "";
@@ -248,7 +286,7 @@ public @interface SqlConfig {
* The end delimiter that identifies block comments within the SQL scripts.
*
Implicitly defaults to "*/".
* @see #blockCommentStartDelimiter
- * @see ScriptUtils#DEFAULT_BLOCK_COMMENT_END_DELIMITER
+ * @see org.springframework.jdbc.datasource.init.ScriptUtils#DEFAULT_BLOCK_COMMENT_END_DELIMITER
*/
String blockCommentEndDelimiter() default "";
diff --git a/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java
index 8bab99cead..6f75cc068e 100644
--- a/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java
+++ b/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java
@@ -43,6 +43,7 @@ import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
+import org.springframework.util.ReflectionUtils;
import org.springframework.util.ResourceUtils;
/**
@@ -165,24 +166,77 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen
logger.debug("Executing SQL scripts: " + ObjectUtils.nullSafeToString(scripts));
}
- final DataSource dataSource = TestContextTransactionUtils.retrieveDataSource(testContext,
- mergedSqlConfig.getDataSource());
+ String dsName = mergedSqlConfig.getDataSource();
+ String tmName = mergedSqlConfig.getTransactionManager();
+ DataSource dataSource = TestContextTransactionUtils.retrieveDataSource(testContext, dsName);
final PlatformTransactionManager transactionManager = TestContextTransactionUtils.retrieveTransactionManager(
- testContext, mergedSqlConfig.getTransactionManager());
+ testContext, tmName);
+ final boolean newTxRequired = mergedSqlConfig.getTransactionMode() == TransactionMode.ISOLATED;
- int propagation = (mergedSqlConfig.getTransactionMode() == TransactionMode.ISOLATED) ? TransactionDefinition.PROPAGATION_REQUIRES_NEW
- : TransactionDefinition.PROPAGATION_REQUIRED;
+ if (transactionManager == null) {
+ if (newTxRequired) {
+ throw new IllegalStateException(String.format("Failed to execute SQL scripts for test context %s: "
+ + "cannot execute SQL scripts using Transaction Mode "
+ + "[%s] without a PlatformTransactionManager.", testContext, TransactionMode.ISOLATED));
+ }
- TransactionAttribute transactionAttribute = TestContextTransactionUtils.createDelegatingTransactionAttribute(
- testContext, new DefaultTransactionAttribute(propagation));
+ if (dataSource == null) {
+ throw new IllegalStateException(String.format("Failed to execute SQL scripts for test context %s: "
+ + "supply at least a DataSource or PlatformTransactionManager.", testContext));
+ }
- new TransactionTemplate(transactionManager, transactionAttribute).execute(new TransactionCallbackWithoutResult() {
+ // Execute scripts directly against the DataSource
+ populator.execute(dataSource);
+ }
+ else {
+ DataSource dataSourceFromTxMgr = getDataSourceFromTransactionManager(transactionManager);
- @Override
- public void doInTransactionWithoutResult(TransactionStatus status) {
- populator.execute(dataSource);
- };
- });
+ // Ensure user configured an appropriate DataSource/TransactionManager pair.
+ if ((dataSource != null) && (dataSourceFromTxMgr != null) && !dataSource.equals(dataSourceFromTxMgr)) {
+ throw new IllegalStateException(String.format("Failed to execute SQL scripts for test context %s: "
+ + "the configured DataSource [%s] (named '%s') is not the one associated "
+ + "with transaction manager [%s] (named '%s').", testContext, dataSource.getClass().getName(),
+ dsName, transactionManager.getClass().getName(), tmName));
+ }
+
+ if (dataSource == null) {
+ dataSource = dataSourceFromTxMgr;
+ if (dataSource == null) {
+ throw new IllegalStateException(String.format("Failed to execute SQL scripts for test context %s: "
+ + "could not obtain DataSource from transaction manager [%s] (named '%s').", testContext,
+ transactionManager.getClass().getName(), tmName));
+ }
+ }
+
+ final DataSource finalDataSource = dataSource;
+ int propagation = newTxRequired ? TransactionDefinition.PROPAGATION_REQUIRES_NEW
+ : TransactionDefinition.PROPAGATION_REQUIRED;
+
+ TransactionAttribute transactionAttribute = TestContextTransactionUtils.createDelegatingTransactionAttribute(
+ testContext, new DefaultTransactionAttribute(propagation));
+
+ new TransactionTemplate(transactionManager, transactionAttribute).execute(new TransactionCallbackWithoutResult() {
+
+ @Override
+ public void doInTransactionWithoutResult(TransactionStatus status) {
+ populator.execute(finalDataSource);
+ }
+ });
+ }
+ }
+
+ private DataSource getDataSourceFromTransactionManager(PlatformTransactionManager transactionManager) {
+ try {
+ Method getDataSourceMethod = transactionManager.getClass().getMethod("getDataSource");
+ Object obj = ReflectionUtils.invokeMethod(getDataSourceMethod, transactionManager);
+ if (obj instanceof DataSource) {
+ return (DataSource) obj;
+ }
+ }
+ catch (Exception e) {
+ /* ignore */
+ }
+ return null;
}
private String[] getScripts(Sql sql, TestContext testContext, boolean classLevel) {
diff --git a/spring-test/src/main/java/org/springframework/test/context/transaction/TestContextTransactionUtils.java b/spring-test/src/main/java/org/springframework/test/context/transaction/TestContextTransactionUtils.java
index df8e813c66..1a75a9dee0 100644
--- a/spring-test/src/main/java/org/springframework/test/context/transaction/TestContextTransactionUtils.java
+++ b/spring-test/src/main/java/org/springframework/test/context/transaction/TestContextTransactionUtils.java
@@ -66,21 +66,23 @@ public abstract class TestContextTransactionUtils {
/**
* Retrieve the {@link DataSource} to use for the supplied {@linkplain TestContext
* test context}.
- *
The following algorithm is used to retrieve the {@code DataSource}
- * from the {@link org.springframework.context.ApplicationContext ApplicationContext}
+ *
The following algorithm is used to retrieve the {@code DataSource} from
+ * the {@link org.springframework.context.ApplicationContext ApplicationContext}
* of the supplied test context:
*
* - Look up the {@code DataSource} by type and name, if the supplied
- * {@code name} is non-empty.
- *
- Look up the {@code DataSource} by type.
- *
- Look up the {@code DataSource} by type and the
+ * {@code name} is non-empty, throwing a {@link BeansException} if the named
+ * {@code DataSource} does not exist.
+ *
- Attempt to look up a single {@code DataSource} by type.
+ *
- Attempt to look up the {@code DataSource} by type and the
* {@linkplain #DEFAULT_DATA_SOURCE_NAME default data source name}.
* @param testContext the test context for which the {@code DataSource}
* should be retrieved; never {@code null}
* @param name the name of the {@code DataSource} to retrieve; may be {@code null}
* or empty
- * @return the {@code DataSource} to use
- * @throws BeansException if an error occurs while retrieving the {@code DataSource}
+ * @return the {@code DataSource} to use, or {@code null} if not found
+ * @throws BeansException if an error occurs while retrieving an explicitly
+ * named {@code DataSource}
*/
public static DataSource retrieveDataSource(TestContext testContext, String name) {
Assert.notNull(testContext, "TestContext must not be null");
@@ -91,7 +93,14 @@ public abstract class TestContextTransactionUtils {
if (StringUtils.hasText(name)) {
return bf.getBean(name, DataSource.class);
}
+ }
+ catch (BeansException ex) {
+ logger.error(
+ String.format("Failed to retrieve DataSource named '%s' for test context %s", name, testContext), ex);
+ throw ex;
+ }
+ try {
if (bf instanceof ListableBeanFactory) {
ListableBeanFactory lbf = (ListableBeanFactory) bf;
@@ -107,10 +116,10 @@ public abstract class TestContextTransactionUtils {
return bf.getBean(DEFAULT_DATA_SOURCE_NAME, DataSource.class);
}
catch (BeansException ex) {
- if (logger.isWarnEnabled()) {
- logger.warn("Caught exception while retrieving DataSource for test context " + testContext, ex);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Caught exception while retrieving DataSource for test context " + testContext, ex);
}
- throw ex;
+ return null;
}
}
@@ -121,20 +130,22 @@ public abstract class TestContextTransactionUtils {
* from the {@link org.springframework.context.ApplicationContext ApplicationContext}
* of the supplied test context:
*
- * - Look up the transaction manager by type and name, if the supplied
- * {@code name} is non-empty.
- *
- Look up the transaction manager by type.
- *
- Look up the transaction manager via a {@link TransactionManagementConfigurer},
- * if present.
- *
- Look up the transaction manager by type and the
+ *
- Look up the transaction manager by type and explicit name, if the supplied
+ * {@code name} is non-empty, throwing a {@link BeansException} if the named
+ * transaction manager does not exist.
+ *
- Attempt to look up the transaction manager by type.
+ *
- Attempt to look up the transaction manager via a
+ * {@link TransactionManagementConfigurer}, if present.
+ *
- Attempt to look up the transaction manager by type and the
* {@linkplain #DEFAULT_TRANSACTION_MANAGER_NAME default transaction manager
* name}.
* @param testContext the test context for which the transaction manager
* should be retrieved; never {@code null}
* @param name the name of the transaction manager to retrieve; may be
* {@code null} or empty
- * @return the transaction manager to use
- * @throws BeansException if an error occurs while retrieving the transaction manager
+ * @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
*/
public static PlatformTransactionManager retrieveTransactionManager(TestContext testContext, String name) {
Assert.notNull(testContext, "TestContext must not be null");
@@ -145,7 +156,14 @@ public abstract class TestContextTransactionUtils {
if (StringUtils.hasText(name)) {
return bf.getBean(name, PlatformTransactionManager.class);
}
+ }
+ catch (BeansException ex) {
+ logger.error(String.format("Failed to retrieve transaction manager named '%s' for test context %s", name,
+ testContext), ex);
+ throw ex;
+ }
+ try {
if (bf instanceof ListableBeanFactory) {
ListableBeanFactory lbf = (ListableBeanFactory) bf;
@@ -172,10 +190,11 @@ public abstract class TestContextTransactionUtils {
return bf.getBean(DEFAULT_TRANSACTION_MANAGER_NAME, PlatformTransactionManager.class);
}
catch (BeansException ex) {
- if (logger.isWarnEnabled()) {
- logger.warn("Caught exception while retrieving transaction manager for test context " + testContext, ex);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Caught exception while retrieving transaction manager for test context " + testContext,
+ ex);
}
- throw ex;
+ return null;
}
}
diff --git a/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java
index 8e7bf5e37f..0cf9cab385 100644
--- a/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java
+++ b/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java
@@ -275,8 +275,10 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
}
catch (RuntimeException ex) {
if (logger.isWarnEnabled()) {
- logger.warn("Caught exception while retrieving transaction manager for test context " + testContext
- + " and qualifier [" + qualifier + "]", ex);
+ logger.warn(
+ String.format(
+ "Caught exception while retrieving transaction manager with qualifier '%s' for test context %s",
+ qualifier, testContext), ex);
}
throw ex;
}
@@ -294,7 +296,8 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
* @param testContext the test context for which the transaction manager
* should be retrieved
* @return the transaction manager to use, or {@code null} if not found
- * @throws BeansException if an error occurs while retrieving the transaction manager
+ * @throws BeansException if an error occurs while retrieving an explicitly
+ * named transaction manager
* @see #getTransactionManager(TestContext, String)
*/
protected PlatformTransactionManager getTransactionManager(TestContext testContext) {
diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/DataSourceOnlySqlScriptsTests.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/DataSourceOnlySqlScriptsTests.java
new file mode 100644
index 0000000000..29a27b5370
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/jdbc/DataSourceOnlySqlScriptsTests.java
@@ -0,0 +1,92 @@
+/*
+ * 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.jdbc;
+
+import javax.sql.DataSource;
+
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.jdbc.JdbcTestUtils;
+
+import static org.junit.Assert.*;
+import static org.springframework.test.transaction.TransactionTestUtils.*;
+
+/**
+ * Integration tests for {@link Sql @Sql} support with only a {@link DataSource}
+ * present in the context (i.e., no transaction manager).
+ *
+ * @author Sam Brannen
+ * @since 4.1
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@ContextConfiguration
+@Sql({ "schema.sql", "data.sql" })
+@DirtiesContext
+public class DataSourceOnlySqlScriptsTests {
+
+ private JdbcTemplate jdbcTemplate;
+
+
+ @Autowired
+ public void setDataSource(DataSource dataSource) {
+ this.jdbcTemplate = new JdbcTemplate(dataSource);
+ }
+
+ @Test
+ // test##_ prefix is required for @FixMethodOrder.
+ public void test01_classLevelScripts() {
+ assertInTransaction(false);
+ assertNumUsers(1);
+ }
+
+ @Test
+ @Sql({ "drop-schema.sql", "schema.sql", "data.sql", "data-add-dogbert.sql" })
+ // test##_ prefix is required for @FixMethodOrder.
+ public void test02_methodLevelScripts() {
+ assertInTransaction(false);
+ assertNumUsers(2);
+ }
+
+ protected void assertNumUsers(int expected) {
+ assertEquals("Number of rows in the 'user' table.", expected,
+ JdbcTestUtils.countRowsInTable(jdbcTemplate, "user"));
+ }
+
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public DataSource dataSource() {
+ return new EmbeddedDatabaseBuilder()//
+ .setName("empty-sql-scripts-without-tx-mgr-test-db")//
+ .build();
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/InferredDataSourceSqlScriptsTests.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/InferredDataSourceSqlScriptsTests.java
new file mode 100644
index 0000000000..649304b587
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/jdbc/InferredDataSourceSqlScriptsTests.java
@@ -0,0 +1,116 @@
+/*
+ * 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.jdbc;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import javax.sql.DataSource;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.datasource.DataSourceTransactionManager;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.transaction.PlatformTransactionManager;
+
+import static org.junit.Assert.*;
+import static org.springframework.test.transaction.TransactionTestUtils.*;
+
+/**
+ * Integration tests for {@link Sql @Sql} that verify support for inferring
+ * {@link DataSource}s from {@link PlatformTransactionManager}s.
+ *
+ * @author Sam Brannen
+ * @since 4.1
+ * @see InferredDataSourceTransactionalSqlScriptsTests
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+@DirtiesContext
+public class InferredDataSourceSqlScriptsTests {
+
+ @Autowired
+ private DataSource dataSource1;
+
+ @Autowired
+ private DataSource dataSource2;
+
+
+ @Test
+ @Sql(scripts = "data-add-dogbert.sql", config = @SqlConfig(transactionManager = "txMgr1"))
+ public void database1() {
+ assertInTransaction(false);
+ assertUsers(new JdbcTemplate(dataSource1), "Dilbert", "Dogbert");
+ }
+
+ @Test
+ @Sql(scripts = "data-add-catbert.sql", config = @SqlConfig(transactionManager = "txMgr2"))
+ public void database2() {
+ assertInTransaction(false);
+ assertUsers(new JdbcTemplate(dataSource2), "Dilbert", "Catbert");
+ }
+
+ private void assertUsers(JdbcTemplate jdbcTemplate, String... users) {
+ List expected = Arrays.asList(users);
+ Collections.sort(expected);
+ List actual = jdbcTemplate.queryForList("select name from user", String.class);
+ Collections.sort(actual);
+ assertEquals("Users in database;", expected, actual);
+ }
+
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public PlatformTransactionManager txMgr1() {
+ return new DataSourceTransactionManager(dataSource1());
+ }
+
+ @Bean
+ public PlatformTransactionManager txMgr2() {
+ return new DataSourceTransactionManager(dataSource2());
+ }
+
+ @Bean
+ public DataSource dataSource1() {
+ return new EmbeddedDatabaseBuilder()//
+ .setName("database1")//
+ .addScript("classpath:/org/springframework/test/context/jdbc/schema.sql")//
+ .addScript("classpath:/org/springframework/test/context/jdbc/data.sql")//
+ .build();
+ }
+
+ @Bean
+ public DataSource dataSource2() {
+ return new EmbeddedDatabaseBuilder()//
+ .setName("database2")//
+ .addScript("classpath:/org/springframework/test/context/jdbc/schema.sql")//
+ .addScript("classpath:/org/springframework/test/context/jdbc/data.sql")//
+ .build();
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/InferredDataSourceTransactionalSqlScriptsTests.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/InferredDataSourceTransactionalSqlScriptsTests.java
new file mode 100644
index 0000000000..ef3c306cbd
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/jdbc/InferredDataSourceTransactionalSqlScriptsTests.java
@@ -0,0 +1,120 @@
+/*
+ * 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.jdbc;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import javax.sql.DataSource;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.datasource.DataSourceTransactionManager;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.annotation.Transactional;
+
+import static org.springframework.test.transaction.TransactionTestUtils.*;
+
+import static org.junit.Assert.*;
+
+/**
+ * Exact copy of {@link InferredDataSourceSqlScriptsTests}, except that test
+ * methods are transactional.
+ *
+ * @author Sam Brannen
+ * @since 4.1
+ * @see InferredDataSourceSqlScriptsTests
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+@DirtiesContext
+public class InferredDataSourceTransactionalSqlScriptsTests {
+
+ @Autowired
+ private DataSource dataSource1;
+
+ @Autowired
+ private DataSource dataSource2;
+
+
+ @Test
+ @Transactional("txMgr1")
+ @Sql(scripts = "data-add-dogbert.sql", config = @SqlConfig(transactionManager = "txMgr1"))
+ public void database1() {
+ assertInTransaction(true);
+ assertUsers(new JdbcTemplate(dataSource1), "Dilbert", "Dogbert");
+ }
+
+ @Test
+ @Transactional("txMgr2")
+ @Sql(scripts = "data-add-catbert.sql", config = @SqlConfig(transactionManager = "txMgr2"))
+ public void database2() {
+ assertInTransaction(true);
+ assertUsers(new JdbcTemplate(dataSource2), "Dilbert", "Catbert");
+ }
+
+ private void assertUsers(JdbcTemplate jdbcTemplate, String... users) {
+ List expected = Arrays.asList(users);
+ Collections.sort(expected);
+ List actual = jdbcTemplate.queryForList("select name from user", String.class);
+ Collections.sort(actual);
+ assertEquals("Users in database;", expected, actual);
+ }
+
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public PlatformTransactionManager txMgr1() {
+ return new DataSourceTransactionManager(dataSource1());
+ }
+
+ @Bean
+ public PlatformTransactionManager txMgr2() {
+ return new DataSourceTransactionManager(dataSource2());
+ }
+
+ @Bean
+ public DataSource dataSource1() {
+ return new EmbeddedDatabaseBuilder()//
+ .setName("database1")//
+ .addScript("classpath:/org/springframework/test/context/jdbc/schema.sql")//
+ .addScript("classpath:/org/springframework/test/context/jdbc/data.sql")//
+ .build();
+ }
+
+ @Bean
+ public DataSource dataSource2() {
+ return new EmbeddedDatabaseBuilder()//
+ .setName("database2")//
+ .addScript("classpath:/org/springframework/test/context/jdbc/schema.sql")//
+ .addScript("classpath:/org/springframework/test/context/jdbc/data.sql")//
+ .build();
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListenerTests.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListenerTests.java
index 58d2a6d60d..c90d5f1572 100644
--- a/spring-test/src/test/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListenerTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListenerTests.java
@@ -18,9 +18,14 @@ package org.springframework.test.context.jdbc;
import org.junit.Test;
import org.mockito.Mockito;
+import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.io.Resource;
import org.springframework.test.context.TestContext;
+import org.springframework.test.context.jdbc.SqlConfig.TransactionMode;
import static org.junit.Assert.*;
+import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
/**
@@ -58,17 +63,46 @@ public class SqlScriptsTestExecutionListenerTests {
public void valueAndScriptsDeclared() throws Exception {
Class> clazz = ValueAndScriptsDeclared.class;
Mockito.> when(testContext.getTestClass()).thenReturn(clazz);
- when(testContext.getTestMethod()).thenReturn(clazz.getDeclaredMethod("valueAndScriptsDeclared"));
+ when(testContext.getTestMethod()).thenReturn(clazz.getDeclaredMethod("foo"));
assertExceptionContains("Only one declaration of SQL script paths is permitted");
}
+ @Test
+ public void isolatedTxModeDeclaredWithoutTxMgr() throws Exception {
+ ApplicationContext ctx = mock(ApplicationContext.class);
+ when(ctx.getResource(anyString())).thenReturn(mock(Resource.class));
+ when(ctx.getAutowireCapableBeanFactory()).thenReturn(mock(AutowireCapableBeanFactory.class));
+
+ Class> clazz = IsolatedWithoutTxMgr.class;
+ Mockito.> when(testContext.getTestClass()).thenReturn(clazz);
+ when(testContext.getTestMethod()).thenReturn(clazz.getDeclaredMethod("foo"));
+ when(testContext.getApplicationContext()).thenReturn(ctx);
+
+ assertExceptionContains("cannot execute SQL scripts using Transaction Mode [ISOLATED] without a PlatformTransactionManager");
+ }
+
+ @Test
+ public void missingDataSourceAndTxMgr() throws Exception {
+ ApplicationContext ctx = mock(ApplicationContext.class);
+ when(ctx.getResource(anyString())).thenReturn(mock(Resource.class));
+ when(ctx.getAutowireCapableBeanFactory()).thenReturn(mock(AutowireCapableBeanFactory.class));
+
+ Class> clazz = MissingDataSourceAndTxMgr.class;
+ Mockito.> when(testContext.getTestClass()).thenReturn(clazz);
+ when(testContext.getTestMethod()).thenReturn(clazz.getDeclaredMethod("foo"));
+ when(testContext.getApplicationContext()).thenReturn(ctx);
+
+ assertExceptionContains("supply at least a DataSource or PlatformTransactionManager");
+ }
+
private void assertExceptionContains(String msg) throws Exception {
try {
listener.beforeTestMethod(testContext);
fail("Should have thrown an IllegalStateException.");
}
catch (IllegalStateException e) {
+ // System.err.println(e.getMessage());
assertTrue("Exception message should contain: " + msg, e.getMessage().contains(msg));
}
}
@@ -93,7 +127,21 @@ public class SqlScriptsTestExecutionListenerTests {
static class ValueAndScriptsDeclared {
@Sql(value = "foo", scripts = "bar")
- public void valueAndScriptsDeclared() {
+ public void foo() {
+ }
+ }
+
+ static class IsolatedWithoutTxMgr {
+
+ @Sql(scripts = "foo.sql", config = @SqlConfig(transactionMode = TransactionMode.ISOLATED))
+ public void foo() {
+ }
+ }
+
+ static class MissingDataSourceAndTxMgr {
+
+ @Sql("foo.sql")
+ public void foo() {
}
}