Merge class-level and method-level @Sql declarations

Prior to these commits method-level @Sql declarations always overrode
class-level @Sql declarations, which required developers to redeclare
class-level @Sql declarations on test methods (e.g., via
copy-and-paste) in order to combine them with method-level @Sql
declarations.

These commits provide developers the ability to optionally merge
class-level and method-level @Sql declarations via a new @SqlMergeMode
annotation that can be applied at the class level or method level, with
method-level declarations of @SqlMergeMode overriding class-level
declarations. For example, @SqlMergeMode(MERGE) switches from the
default OVERRIDE behavior to merging behavior, with class-level SQL
scripts and statements executed before method-level SQL scripts and
statements.

Closes gh-1835
This commit is contained in:
Sam Brannen 2019-07-21 16:20:25 +02:00
commit 9cb15ba602
7 changed files with 401 additions and 40 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 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.
@ -31,7 +31,8 @@ import org.springframework.core.annotation.AliasFor;
* SQL {@link #scripts} and {@link #statements} to be executed against a given
* database during integration tests.
*
* <p>Method-level declarations override class-level declarations.
* <p>Method-level declarations override class-level declarations by default,
* but this behavior can be configured via {@link SqlMergeMode @SqlMergeMode}.
*
* <p>Script execution is performed by the {@link SqlScriptsTestExecutionListener},
* which is enabled by default.
@ -55,6 +56,7 @@ import org.springframework.core.annotation.AliasFor;
* @author Sam Brannen
* @since 4.1
* @see SqlConfig
* @see SqlMergeMode
* @see SqlGroup
* @see SqlScriptsTestExecutionListener
* @see org.springframework.transaction.annotation.Transactional

View File

@ -0,0 +1,82 @@
/*
* Copyright 2002-2019 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
*
* https://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.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* {@code @SqlMergeMode} is used to annotate a test class or test method to
* configure whether method-level {@code @Sql} declarations are merged with
* class-level {@code @Sql} declarations.
*
* <p>A method-level {@code @SqlMergeMode} declaration overrides a class-level
* declaration.
*
* <p>If {@code @SqlMergeMode} is not declared on a test class or test method,
* {@link MergeMode#OVERRIDE} will be used by default.
*
* <p>This annotation may be used as a <em>meta-annotation</em> to create custom
* <em>composed annotations</em> with attribute overrides.
*
* @author Sam Brannen
* @author Dmitry Semukhin
* @since 5.2
* @see Sql
* @see MergeMode#MERGE
* @see MergeMode#OVERRIDE
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface SqlMergeMode {
/**
* Indicates whether method-level {@code @Sql} annotations should be merged
* with class-level {@code @Sql} annotations or override them.
*/
MergeMode value();
/**
* Enumeration of <em>modes</em> that dictate whether method-level {@code @Sql}
* declarations are merged with class-level {@code @Sql} declarations.
*/
enum MergeMode {
/**
* Indicates that method-level {@code @Sql} declarations should be merged
* with class-level {@code @Sql} declarations, with class-level SQL
* scripts and statements executed before method-level scripts and
* statements.
*/
MERGE,
/**
* Indicates that method-level {@code @Sql} declarations should override
* class-level {@code @Sql} declarations.
*/
OVERRIDE
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 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.
@ -16,6 +16,7 @@
package org.springframework.test.context.jdbc;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Set;
@ -30,11 +31,13 @@ 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.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.jdbc.Sql.ExecutionPhase;
import org.springframework.test.context.jdbc.SqlConfig.ErrorMode;
import org.springframework.test.context.jdbc.SqlConfig.TransactionMode;
import org.springframework.test.context.jdbc.SqlMergeMode.MergeMode;
import org.springframework.test.context.support.AbstractTestExecutionListener;
import org.springframework.test.context.transaction.TestContextTransactionUtils;
import org.springframework.test.context.util.TestContextResourceUtils;
@ -81,6 +84,7 @@ import org.springframework.util.StringUtils;
* locate these beans.
*
* @author Sam Brannen
* @author Dmitry Semukhin
* @since 4.1
* @see Sql
* @see SqlConfig
@ -108,7 +112,7 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen
* {@link TestContext} <em>before</em> the current test method.
*/
@Override
public void beforeTestMethod(TestContext testContext) throws Exception {
public void beforeTestMethod(TestContext testContext) {
executeSqlScripts(testContext, ExecutionPhase.BEFORE_TEST_METHOD);
}
@ -117,7 +121,7 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen
* {@link TestContext} <em>after</em> the current test method.
*/
@Override
public void afterTestMethod(TestContext testContext) throws Exception {
public void afterTestMethod(TestContext testContext) {
executeSqlScripts(testContext, ExecutionPhase.AFTER_TEST_METHOD);
}
@ -125,22 +129,58 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen
* Execute SQL scripts configured via {@link Sql @Sql} for the supplied
* {@link TestContext} and {@link ExecutionPhase}.
*/
private void executeSqlScripts(TestContext testContext, ExecutionPhase executionPhase) throws Exception {
boolean classLevel = false;
private void executeSqlScripts(TestContext testContext, ExecutionPhase executionPhase) {
Method testMethod = testContext.getTestMethod();
Class<?> testClass = testContext.getTestClass();
Set<Sql> sqlAnnotations = AnnotatedElementUtils.getMergedRepeatableAnnotations(
testContext.getTestMethod(), Sql.class, SqlGroup.class);
if (sqlAnnotations.isEmpty()) {
sqlAnnotations = AnnotatedElementUtils.getMergedRepeatableAnnotations(
testContext.getTestClass(), Sql.class, SqlGroup.class);
if (!sqlAnnotations.isEmpty()) {
classLevel = true;
if (mergeSqlAnnotations(testContext)) {
executeSqlScripts(getSqlAnnotationsFor(testClass), testContext, executionPhase, true);
executeSqlScripts(getSqlAnnotationsFor(testMethod), testContext, executionPhase, false);
}
else {
Set<Sql> methodLevelSqlAnnotations = getSqlAnnotationsFor(testMethod);
if (!methodLevelSqlAnnotations.isEmpty()) {
executeSqlScripts(methodLevelSqlAnnotations, testContext, executionPhase, false);
}
else {
executeSqlScripts(getSqlAnnotationsFor(testClass), testContext, executionPhase, true);
}
}
}
for (Sql sql : sqlAnnotations) {
executeSqlScripts(sql, executionPhase, testContext, classLevel);
/**
* Determine if method-level {@code @Sql} annotations should be merged with
* class-level {@code @Sql} annotations.
*/
private boolean mergeSqlAnnotations(TestContext testContext) {
SqlMergeMode sqlMergeMode = getSqlMergeModeFor(testContext.getTestMethod());
if (sqlMergeMode == null) {
sqlMergeMode = getSqlMergeModeFor(testContext.getTestClass());
}
return (sqlMergeMode != null && sqlMergeMode.value() == MergeMode.MERGE);
}
/**
* Get the {@code @SqlMergeMode} annotation declared on the supplied {@code element}.
*/
private SqlMergeMode getSqlMergeModeFor(AnnotatedElement element) {
return AnnotatedElementUtils.findMergedAnnotation(element, SqlMergeMode.class);
}
/**
* Get the {@code @Sql} annotations declared on the supplied {@code element}.
*/
private Set<Sql> getSqlAnnotationsFor(AnnotatedElement element) {
return AnnotatedElementUtils.getMergedRepeatableAnnotations(element, Sql.class, SqlGroup.class);
}
/**
* Execute SQL scripts for the supplied {@link Sql @Sql} annotations.
*/
private void executeSqlScripts(
Set<Sql> sqlAnnotations, TestContext testContext, ExecutionPhase executionPhase, boolean classLevel) {
sqlAnnotations.forEach(sql -> executeSqlScripts(sql, executionPhase, testContext, classLevel));
}
/**
@ -153,8 +193,8 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen
* @param testContext the current {@code TestContext}
* @param classLevel {@code true} if {@link Sql @Sql} was declared at the class level
*/
private void executeSqlScripts(Sql sql, ExecutionPhase executionPhase, TestContext testContext, boolean classLevel)
throws Exception {
private void executeSqlScripts(
Sql sql, ExecutionPhase executionPhase, TestContext testContext, boolean classLevel) {
if (executionPhase != sql.executionPhase()) {
return;
@ -166,15 +206,6 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen
mergedSqlConfig, executionPhase, testContext));
}
final ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
populator.setSqlScriptEncoding(mergedSqlConfig.getEncoding());
populator.setSeparator(mergedSqlConfig.getSeparator());
populator.setCommentPrefix(mergedSqlConfig.getCommentPrefix());
populator.setBlockCommentStartDelimiter(mergedSqlConfig.getBlockCommentStartDelimiter());
populator.setBlockCommentEndDelimiter(mergedSqlConfig.getBlockCommentEndDelimiter());
populator.setContinueOnError(mergedSqlConfig.getErrorMode() == ErrorMode.CONTINUE_ON_ERROR);
populator.setIgnoreFailedDrops(mergedSqlConfig.getErrorMode() == ErrorMode.IGNORE_FAILED_DROPS);
String[] scripts = getScripts(sql, testContext, classLevel);
scripts = TestContextResourceUtils.convertToClasspathResourcePaths(testContext.getTestClass(), scripts);
List<Resource> scriptResources = TestContextResourceUtils.convertToResourceList(
@ -185,6 +216,8 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen
scriptResources.add(new ByteArrayResource(stmt.getBytes(), "from inlined SQL statement: " + stmt));
}
}
ResourceDatabasePopulator populator = createDatabasePopulator(mergedSqlConfig);
populator.setScripts(scriptResources.toArray(new Resource[0]));
if (logger.isDebugEnabled()) {
logger.debug("Executing SQL scripts: " + ObjectUtils.nullSafeToString(scriptResources));
@ -225,13 +258,23 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen
TransactionDefinition.PROPAGATION_REQUIRED);
TransactionAttribute txAttr = TestContextTransactionUtils.createDelegatingTransactionAttribute(
testContext, new DefaultTransactionAttribute(propagation));
new TransactionTemplate(txMgr, txAttr).execute(status -> {
populator.execute(finalDataSource);
return null;
});
new TransactionTemplate(txMgr, txAttr).execute(() -> populator.execute(finalDataSource));
}
}
@NonNull
private ResourceDatabasePopulator createDatabasePopulator(MergedSqlConfig mergedSqlConfig) {
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
populator.setSqlScriptEncoding(mergedSqlConfig.getEncoding());
populator.setSeparator(mergedSqlConfig.getSeparator());
populator.setCommentPrefix(mergedSqlConfig.getCommentPrefix());
populator.setBlockCommentStartDelimiter(mergedSqlConfig.getBlockCommentStartDelimiter());
populator.setBlockCommentEndDelimiter(mergedSqlConfig.getBlockCommentEndDelimiter());
populator.setContinueOnError(mergedSqlConfig.getErrorMode() == ErrorMode.CONTINUE_ON_ERROR);
populator.setIgnoreFailedDrops(mergedSqlConfig.getErrorMode() == ErrorMode.IGNORE_FAILED_DROPS);
return populator;
}
@Nullable
private DataSource getDataSourceFromTransactionManager(PlatformTransactionManager transactionManager) {
try {

View File

@ -0,0 +1,45 @@
/*
* Copyright 2002-2019 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
*
* https://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.merging;
import java.util.List;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.jdbc.EmptyDatabaseConfig;
import org.springframework.test.context.jdbc.SqlMergeMode;
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.annotation.DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD;
/**
* Abstract base class for tests involving {@link SqlMergeMode @SqlMergeMode}.
*
* @author Sam Brannen
* @since 5.2
*/
@ContextConfiguration(classes = EmptyDatabaseConfig.class)
@DirtiesContext(classMode = AFTER_EACH_TEST_METHOD)
abstract class AbstractSqlMergeModeTests extends AbstractTransactionalJUnit4SpringContextTests {
protected void assertUsers(String... expectedUsers) {
List<String> actualUsers = super.jdbcTemplate.queryForList("select name from user", String.class);
assertThat(actualUsers).containsExactlyInAnyOrder(expectedUsers);
}
}

View File

@ -0,0 +1,59 @@
/*
* Copyright 2002-2019 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
*
* https://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.merging;
import org.junit.Test;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.SqlMergeMode;
import static org.springframework.test.context.jdbc.SqlMergeMode.MergeMode.MERGE;
import static org.springframework.test.context.jdbc.SqlMergeMode.MergeMode.OVERRIDE;
/**
* Transactional integration tests that verify proper merging and overriding support
* for class-level and method-level {@link Sql @Sql} declarations when
* {@link SqlMergeMode @SqlMergeMode} is declared at the class level with
* {@link SqlMergeMode.MergeMode#MERGE MERGE} mode.
*
* @author Sam Brannen
* @author Dmitry Semukhin
* @since 5.2
*/
@Sql({ "../schema.sql", "../data-add-catbert.sql" })
@SqlMergeMode(MERGE)
public class ClassLevelMergeSqlMergeModeTests extends AbstractSqlMergeModeTests {
@Test
public void classLevelScripts() {
assertUsers("Catbert");
}
@Test
@Sql("../data-add-dogbert.sql")
public void merged() {
assertUsers("Catbert", "Dogbert");
}
@Test
@Sql({ "../schema.sql", "../data.sql", "../data-add-dogbert.sql", "../data-add-catbert.sql" })
@SqlMergeMode(OVERRIDE)
public void overridden() {
assertUsers("Dilbert", "Dogbert", "Catbert");
}
}

View File

@ -0,0 +1,59 @@
/*
* Copyright 2002-2019 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
*
* https://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.merging;
import org.junit.Test;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.SqlMergeMode;
import static org.springframework.test.context.jdbc.SqlMergeMode.MergeMode.MERGE;
import static org.springframework.test.context.jdbc.SqlMergeMode.MergeMode.OVERRIDE;
/**
* Transactional integration tests that verify proper merging and overriding support
* for class-level and method-level {@link Sql @Sql} declarations when
* {@link SqlMergeMode @SqlMergeMode} is declared at the class level with
* {@link SqlMergeMode.MergeMode#OVERRIDE OVERRIDE} mode.
*
* @author Sam Brannen
* @author Dmitry Semukhin
* @since 5.2
*/
@Sql({ "../schema.sql", "../data-add-catbert.sql" })
@SqlMergeMode(OVERRIDE)
public class ClassLevelOverrideSqlMergeModeTests extends AbstractSqlMergeModeTests {
@Test
public void classLevelScripts() {
assertUsers("Catbert");
}
@Test
@Sql("../data-add-dogbert.sql")
@SqlMergeMode(MERGE)
public void merged() {
assertUsers("Catbert", "Dogbert");
}
@Test
@Sql({ "../schema.sql", "../data.sql", "../data-add-dogbert.sql", "../data-add-catbert.sql" })
public void overridden() {
assertUsers("Dilbert", "Dogbert", "Catbert");
}
}

View File

@ -419,6 +419,7 @@ Spring's testing annotations include the following:
* <<spring-testing-annotation-aftertransaction>>
* <<spring-testing-annotation-sql>>
* <<spring-testing-annotation-sqlconfig>>
* <<spring-testing-annotation-sqlmergemode>>
* <<spring-testing-annotation-sqlgroup>>
[[spring-testing-annotation-bootstrapwith]]
@ -968,9 +969,9 @@ it:
----
<1> Run two scripts for this test.
See <<testcontext-executing-sql-declaratively>> for further details.
[[spring-testing-annotation-sqlconfig]]
===== `@SqlConfig`
@ -992,6 +993,56 @@ configured with the `@Sql` annotation. The following example shows how to use it
<1> Set the comment prefix and the separator in SQL scripts.
[[spring-testing-annotation-sqlmergemode]]
===== `@SqlMergeMode`
`@SqlMergeMode` is used to annotate a test class or test method to configure whether
method-level `@Sql` declarations are merged with class-level `@Sql` declarations. If
`@SqlMergeMode` is not declared on a test class or test method, the `OVERRIDE` merge mode
will be used by default. With the `OVERRIDE` mode, method-level `@Sql` declarations will
effectively override class-level `@Sql` declarations.
Note that a method-level `@SqlMergeMode` declaration overrides a class-level declaration.
The following example shows how to use `@SqlMergeMode` at the class level.
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig.class)
@Sql("/test-schema.sql")
@SqlMergeMode(MERGE) <1>
class UserTests {
@Test
@Sql("/user-test-data-001.sql")
void standardUserProfile {
// execute code that relies on test data set 001
}
}
----
<1> Set the `@Sql` merge mode to `MERGE` for all test methods in the class.
The following example shows how to use `@SqlMergeMode` at the method level.
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig.class)
@Sql("/test-schema.sql")
class UserTests {
@Test
@Sql("/user-test-data-001.sql")
@SqlMergeMode(MERGE) <1>
void standardUserProfile {
// execute code that relies on test data set 001
}
}
----
<1> Set the `@Sql` merge mode to `MERGE` for a specific test method.
[[spring-testing-annotation-sqlgroup]]
===== `@SqlGroup`
@ -1411,6 +1462,7 @@ You can use each of the following as a meta-annotation in conjunction with the
* `@Rollback`
* `@Sql`
* `@SqlConfig`
* `@SqlMergeMode`
* `@SqlGroup`
* `@Repeat` _(only supported on JUnit 4)_
* `@Timed` _(only supported on JUnit 4)_
@ -3997,11 +4049,16 @@ various `executeSqlScript(..)` methods for further details.
In addition to the aforementioned mechanisms for running SQL scripts programmatically,
you can declaratively configure SQL scripts in the Spring TestContext Framework.
Specifically, you can declare the `@Sql` annotation on a test class or test method to
configure the resource paths to SQL scripts that should be run against a given database
before or after an integration test method. Note that method-level declarations override
class-level declarations and that support for `@Sql` is provided by the
`SqlScriptsTestExecutionListener`, which is enabled by default.
configure individual SQL statements or the resource paths to SQL scripts that should be
run against a given database before or after an integration test method. Support for
`@Sql` is provided by the `SqlScriptsTestExecutionListener`, which is enabled by default.
NOTE: Method-level `@Sql` declarations override class-level declarations by default. As
of Spring Framework 5.2, however, this behavior may be configured per test class or per
test method via `@SqlMergeMode`. See
<<testcontext-executing-sql-declaratively-script-merging>> for further details.
[[testcontext-executing-sql-declaratively-script-resources]]
====== Path Resource Semantics
Each path is interpreted as a Spring `Resource`. A plain path (for example,
@ -4034,10 +4091,11 @@ within a JUnit Jupiter based integration test class:
}
----
[[testcontext-executing-sql-declaratively-script-detection]]
====== Default Script Detection
If no SQL scripts are specified, an attempt is made to detect a `default` script,
depending on where `@Sql` is declared. If a default cannot be detected, an
If no SQL scripts or statements are specified, an attempt is made to detect a `default`
script, depending on where `@Sql` is declared. If a default cannot be detected, an
`IllegalStateException` is thrown.
* Class-level declaration: If the annotated test class is `com.example.MyTest`, the
@ -4046,6 +4104,7 @@ depending on where `@Sql` is declared. If a default cannot be detected, an
defined in the class `com.example.MyTest`, the corresponding default script is
`classpath:com/example/MyTest.testMethod.sql`.
[[testcontext-executing-sql-declaratively-multiple-annotations]]
====== Declaring Multiple `@Sql` Sets
If you need to configure multiple sets of SQL scripts for a given test class or test
@ -4088,6 +4147,7 @@ Java 7.
}
----
[[testcontext-executing-sql-declaratively-script-execution-phases]]
====== Script Execution Phases
By default, SQL scripts are executed before the corresponding test method. However, if
@ -4117,6 +4177,7 @@ following example shows:
Note that `ISOLATED` and `AFTER_TEST_METHOD` are statically imported from
`Sql.TransactionMode` and `Sql.ExecutionPhase`, respectively.
[[testcontext-executing-sql-declaratively-script-configuration]]
====== Script Configuration with `@SqlConfig`
You can configure script parsing and error handling by using the `@SqlConfig` annotation.
@ -4142,8 +4203,6 @@ provided by the `<jdbc:initialize-database/>` XML namespace element. See the jav
individual attributes in {api-spring-framework}/test/context/jdbc/Sql.html[`@Sql`] and
{api-spring-framework}/test/context/jdbc/SqlConfig.html[`@SqlConfig`] for details.
[[testcontext-executing-sql-declaratively-tx]]
*Transaction management for `@Sql`*
@ -4208,6 +4267,18 @@ run, since any changes made to the database (either within the test method or wi
`TransactionalTestExecutionListener` (see <<testcontext-tx,transaction management>> for
details).
[[testcontext-executing-sql-declaratively-script-merging]]
====== Merging and Overriding Configuration with `@SqlMergeMode`
As of Spring Framework 5.2, it is possible to merge method-level `@Sql` declarations with
class-level declarations. For example, this allows you to provide the configuration for a
database schema or some common test data once per test class and then provide additional,
use case specific test data per test method. To enable `@Sql` merging, annotate either
your test class or test method with `@SqlMergeMode(MERGE)`. To disable merging for a
specific test method (or specific test subclass), you can switch back to the default mode
via `@SqlMergeMode(OVERRIDE)`. Consult the <<spring-testing-annotation-sqlmergemode,
`@SqlMergeMode` annotation documentation section>> for examples and further details.
[[testcontext-parallel-test-execution]]
==== Parallel Test Execution