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 ae11d7d5be4..c2fdc3e7a00 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
@@ -28,8 +28,9 @@ import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
/**
- * {@code @Sql} is used to annotate a test class or test method to configure SQL
- * scripts to be executed against a given database during integration tests.
+ * {@code @Sql} is used to annotate a test class or test method to configure
+ * SQL {@link #scripts} and {@link #statements} to be executed against a given
+ * database during integration tests.
*
*
Method-level declarations override class-level declarations.
*
@@ -77,14 +78,14 @@ public @interface Sql {
static enum ExecutionPhase {
/**
- * The configured SQL scripts will be executed before the
- * corresponding test method.
+ * The configured SQL scripts and statements will be executed
+ * before the corresponding test method.
*/
BEFORE_TEST_METHOD,
/**
- * The configured SQL scripts will be executed after the
- * corresponding test method.
+ * The configured SQL scripts and statements will be executed
+ * after the corresponding test method.
*/
AFTER_TEST_METHOD
}
@@ -94,6 +95,8 @@ public @interface Sql {
* Alias for {@link #scripts}.
*
This attribute may not be used in conjunction with
* {@link #scripts}, but it may be used instead of {@link #scripts}.
+ * @see #scripts
+ * @see #statements
*/
@AliasFor(attribute = "scripts")
String[] value() default {};
@@ -101,7 +104,10 @@ public @interface Sql {
/**
* The paths to the SQL scripts to execute.
*
This attribute may not be used in conjunction with
- * {@link #value}, but it may be used instead of {@link #value}.
+ * {@link #value}, but it may be used instead of {@link #value}. Similarly,
+ * this attribute may be used in conjunction with or instead of
+ * {@link #statements}.
+ *
*
Path Resource Semantics
* Each path will be interpreted as a Spring
* {@link org.springframework.core.io.Resource Resource}. A plain path
@@ -114,11 +120,12 @@ public @interface Sql {
* {@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.
- * If a default cannot be detected, an {@link IllegalStateException} will be
- * thrown.
+ *
If no SQL scripts or {@link #statements} are specified, an attempt will
+ * be made to detect a default script depending on where this
+ * annotation is declared. If a default cannot be detected, an
+ * {@link IllegalStateException} will be thrown.
*
* - class-level declaration: if the annotated test class
* is {@code com.example.MyTest}, the corresponding default script is
@@ -128,19 +135,38 @@ public @interface Sql {
* {@code com.example.MyTest}, the corresponding default script is
* {@code "classpath:com/example/MyTest.testMethod.sql"}.
*
+ *
+ * @see #value
+ * @see #statements
*/
@AliasFor(attribute = "value")
String[] scripts() default {};
/**
- * When the SQL scripts should be executed.
+ * Inlined SQL statements to execute.
+ * This attribute may be used in conjunction with or instead of
+ * {@link #scripts}.
+ *
+ *
Ordering
+ * Statements declared via this attribute will be executed after
+ * statements loaded from resource {@link #scripts}. If you wish to have
+ * inlined statements executed before scripts, simply declare multiple
+ * instances of {@code @Sql} on the same class or method.
+ *
+ * @since 4.2
+ * @see #scripts
+ */
+ String[] statements() default {};
+
+ /**
+ * When the SQL scripts and statements should be executed.
*
Defaults to {@link ExecutionPhase#BEFORE_TEST_METHOD BEFORE_TEST_METHOD}.
*/
ExecutionPhase executionPhase() default ExecutionPhase.BEFORE_TEST_METHOD;
/**
- * Local configuration for the SQL scripts declared within this
- * {@code @Sql} annotation.
+ * Local configuration for the SQL scripts and statements declared within
+ * this {@code @Sql} annotation.
*
See the class-level javadocs for {@link SqlConfig} for explanations of
* local vs. global configuration, inheritance, overrides, etc.
*
Defaults to an empty {@link SqlConfig @SqlConfig} instance.
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 a7de6071939..71e0e1f6390 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
@@ -17,7 +17,9 @@
package org.springframework.test.context.jdbc;
import java.lang.reflect.Method;
+import java.util.List;
import java.util.Set;
+
import javax.sql.DataSource;
import org.apache.commons.logging.Log;
@@ -25,7 +27,9 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.jdbc.Sql.ExecutionPhase;
@@ -45,23 +49,25 @@ import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ResourceUtils;
+import org.springframework.util.StringUtils;
/**
- * {@code TestExecutionListener} that provides support for executing SQL scripts
+ * {@code TestExecutionListener} that provides support for executing SQL
+ * {@link Sql#scripts scripts} and inlined {@link Sql#statements statements}
* configured via the {@link Sql @Sql} annotation.
*
- *
Scripts will be executed {@linkplain #beforeTestMethod(TestContext) before}
+ *
Scripts and inlined statements will be executed {@linkplain #beforeTestMethod(TestContext) before}
* or {@linkplain #afterTestMethod(TestContext) after} execution of the corresponding
* {@linkplain java.lang.reflect.Method test method}, depending on the configured
* value of the {@link Sql#executionPhase executionPhase} flag.
*
- *
Scripts will be executed without a transaction, within an existing
- * Spring-managed transaction, or within an isolated transaction, depending
- * on the configured value of {@link SqlConfig#transactionMode} and the
+ *
Scripts and inlined statements will be executed without a transaction,
+ * within an existing Spring-managed transaction, or within an isolated transaction,
+ * depending on the configured value of {@link SqlConfig#transactionMode} and the
* presence of a transaction manager.
*
*
Script Resources
- * For details on default script detection and how explicit script locations
+ *
For details on default script detection and how script resource locations
* are interpreted, see {@link Sql#scripts}.
*
*
Required Spring Beans
@@ -175,9 +181,19 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen
String[] scripts = getScripts(sql, testContext, classLevel);
scripts = TestContextResourceUtils.convertToClasspathResourcePaths(testContext.getTestClass(), scripts);
- populator.setScripts(TestContextResourceUtils.convertToResources(testContext.getApplicationContext(), scripts));
+ List scriptResources = TestContextResourceUtils.convertToResourceList(
+ testContext.getApplicationContext(), scripts);
+
+ for (String statement : sql.statements()) {
+ if (StringUtils.hasText(statement)) {
+ statement = statement.trim();
+ scriptResources.add(new ByteArrayResource(statement.getBytes(), "from inlined SQL statement: " + statement));
+ }
+ }
+
+ populator.setScripts(scriptResources.toArray(new Resource[scriptResources.size()]));
if (logger.isDebugEnabled()) {
- logger.debug("Executing SQL scripts: " + ObjectUtils.nullSafeToString(scripts));
+ logger.debug("Executing SQL scripts: " + ObjectUtils.nullSafeToString(scriptResources));
}
String dsName = mergedSqlConfig.getDataSource();
@@ -255,7 +271,7 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen
private String[] getScripts(Sql sql, TestContext testContext, boolean classLevel) {
String[] scripts = sql.scripts();
- if (ObjectUtils.isEmpty(scripts)) {
+ if (ObjectUtils.isEmpty(scripts) && ObjectUtils.isEmpty(sql.statements())) {
scripts = new String[] { detectDefaultScript(testContext, classLevel) };
}
return scripts;
@@ -289,7 +305,7 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen
}
else {
String msg = String.format("Could not detect default SQL script for test %s [%s]: "
- + "%s does not exist. Either declare scripts via @Sql or make the "
+ + "%s does not exist. Either declare statements or scripts via @Sql or make the "
+ "default SQL script available.", elementType, elementName, classPathResource);
logger.error(msg);
throw new IllegalStateException(msg);
diff --git a/spring-test/src/main/java/org/springframework/test/context/util/TestContextResourceUtils.java b/spring-test/src/main/java/org/springframework/test/context/util/TestContextResourceUtils.java
index 1f2db0eafd7..dd3e74d4e44 100644
--- a/spring-test/src/main/java/org/springframework/test/context/util/TestContextResourceUtils.java
+++ b/spring-test/src/main/java/org/springframework/test/context/util/TestContextResourceUtils.java
@@ -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.
@@ -88,20 +88,37 @@ public abstract class TestContextResourceUtils {
}
/**
- * Convert the supplied paths to {@link Resource} handles using the given
- * {@link ResourceLoader}.
+ * Convert the supplied paths to an array of {@link Resource} handles using
+ * the given {@link ResourceLoader}.
*
* @param resourceLoader the {@code ResourceLoader} to use to convert the paths
* @param paths the paths to be converted
* @return a new array of resources
+ * @see #convertToResourceList(ResourceLoader, String...)
* @see #convertToClasspathResourcePaths
*/
public static Resource[] convertToResources(ResourceLoader resourceLoader, String... paths) {
+ List list = convertToResourceList(resourceLoader, paths);
+ return list.toArray(new Resource[list.size()]);
+ }
+
+ /**
+ * Convert the supplied paths to a list of {@link Resource} handles using
+ * the given {@link ResourceLoader}.
+ *
+ * @param resourceLoader the {@code ResourceLoader} to use to convert the paths
+ * @param paths the paths to be converted
+ * @return a new list of resources
+ * @since 4.2
+ * @see #convertToResources(ResourceLoader, String...)
+ * @see #convertToClasspathResourcePaths
+ */
+ public static List convertToResourceList(ResourceLoader resourceLoader, String... paths) {
List list = new ArrayList();
for (String path : paths) {
list.add(resourceLoader.getResource(path));
}
- return list.toArray(new Resource[list.size()]);
+ return list;
}
}
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 ed91023988b..7267b1acf10 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
@@ -50,8 +50,8 @@ public class SqlScriptsTestExecutionListenerTests {
@Test
- public void missingValueAndScriptsAtClassLevel() throws Exception {
- Class> clazz = MissingValueAndScriptsAtClassLevel.class;
+ public void missingValueAndScriptsAndStatementsAtClassLevel() throws Exception {
+ Class> clazz = MissingValueAndScriptsAndStatementsAtClassLevel.class;
BDDMockito.> given(testContext.getTestClass()).willReturn(clazz);
given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("foo"));
@@ -59,8 +59,8 @@ public class SqlScriptsTestExecutionListenerTests {
}
@Test
- public void missingValueAndScriptsAtMethodLevel() throws Exception {
- Class> clazz = MissingValueAndScriptsAtMethodLevel.class;
+ public void missingValueAndScriptsAndStatementsAtMethodLevel() throws Exception {
+ Class> clazz = MissingValueAndScriptsAndStatementsAtMethodLevel.class;
BDDMockito.> given(testContext.getTestClass()).willReturn(clazz);
given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("foo"));
@@ -126,13 +126,13 @@ public class SqlScriptsTestExecutionListenerTests {
// -------------------------------------------------------------------------
@Sql
- static class MissingValueAndScriptsAtClassLevel {
+ static class MissingValueAndScriptsAndStatementsAtClassLevel {
public void foo() {
}
}
- static class MissingValueAndScriptsAtMethodLevel {
+ static class MissingValueAndScriptsAndStatementsAtMethodLevel {
@Sql
public void foo() {
diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/TransactionalInlinedStatementsSqlScriptsTests.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/TransactionalInlinedStatementsSqlScriptsTests.java
new file mode 100644
index 00000000000..6a97aea5414
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/jdbc/TransactionalInlinedStatementsSqlScriptsTests.java
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ * 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.jdbc.core.JdbcTemplate;
+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 org.springframework.transaction.annotation.Transactional;
+
+import static org.junit.Assert.*;
+
+/**
+ * Transactional integration tests for {@link Sql @Sql} support with
+ * inlined SQL {@link Sql#statements statements}.
+ *
+ * @author Sam Brannen
+ * @since 4.2
+ * @see TransactionalSqlScriptsTests
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@ContextConfiguration(classes = EmptyDatabaseConfig.class)
+@Transactional
+@Sql(
+ scripts = "schema.sql",
+ statements = "INSERT INTO user VALUES('Dilbert')"
+)
+@DirtiesContext
+public class TransactionalInlinedStatementsSqlScriptsTests {
+
+ protected JdbcTemplate jdbcTemplate;
+
+
+ @Autowired
+ public void setDataSource(DataSource dataSource) {
+ this.jdbcTemplate = new JdbcTemplate(dataSource);
+ }
+
+ @Test
+ // test##_ prefix is required for @FixMethodOrder.
+ public void test01_classLevelScripts() {
+ assertNumUsers(1);
+ }
+
+ @Test
+ @Sql(statements = "DROP TABLE user IF EXISTS")
+ @Sql("schema.sql")
+ @Sql(statements = "INSERT INTO user VALUES ('Dilbert'), ('Dogbert'), ('Catbert')")
+ // test##_ prefix is required for @FixMethodOrder.
+ public void test02_methodLevelScripts() {
+ assertNumUsers(3);
+ }
+
+ protected int countRowsInTable(String tableName) {
+ return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName);
+ }
+
+ protected void assertNumUsers(int expected) {
+ assertEquals("Number of rows in the 'user' table.", expected, countRowsInTable("user"));
+ }
+
+}
diff --git a/src/asciidoc/whats-new.adoc b/src/asciidoc/whats-new.adoc
index bab1e9c2f31..c807036d099 100644
--- a/src/asciidoc/whats-new.adoc
+++ b/src/asciidoc/whats-new.adoc
@@ -573,6 +573,8 @@ public @interface MyTestConfig {
_before_ a test -- for example, if some rogue (i.e., yet to be
determined) test within a large test suite has corrupted the original
configuration for the `ApplicationContext`.
+* `@Sql` now supports execution of _inlined SQL statements_ via a new
+ `statements` attribute.
* The JDBC XML namespace supports a new `database-name` attribute in
``, allowing developers to set unique names
for embedded databases –- for example, via a SpEL expression or a