combinedProfiles = new LinkedHashSet<>();
- for (String profile : defaultActiveProfilesResolver.resolve(testClass)) {
- combinedProfiles.add(profile);
- }
- for (String profile : getSystemProfiles()) {
- combinedProfiles.add(profile);
- }
- for (String profile : getEnvironmentProfiles()) {
- combinedProfiles.add(profile);
- }
+ combinedProfiles.addAll(Arrays.asList(defaultActiveProfilesResolver.resolve(testClass)));
+ combinedProfiles.addAll(Arrays.asList(getSystemProfiles()));
+ combinedProfiles.addAll(Arrays.asList(getEnvironmentProfiles()));
return combinedProfiles.toArray(new String[0]);
}
- @NotNull
private static String[] getSystemProfiles() {
if (System.getProperties().containsKey(SPRING_PROFILES_ACTIVE)) {
- final String profiles = System.getProperty(SPRING_PROFILES_ACTIVE);
+ String profiles = System.getProperty(SPRING_PROFILES_ACTIVE);
return profiles.split("\\s*,\\s*");
}
+
return new String[0];
}
@@ -69,6 +64,7 @@ private String[] getEnvironmentProfiles() {
String profiles = System.getenv().get(SPRING_PROFILES_ACTIVE);
return profiles.split("\\s*,\\s*");
}
+
return new String[0];
}
diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/HsqlDbOnly.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/ConditionalOnDatabase.java
similarity index 55%
rename from spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/HsqlDbOnly.java
rename to spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/ConditionalOnDatabase.java
index 4fa3e01f56..53dc448800 100644
--- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/HsqlDbOnly.java
+++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/ConditionalOnDatabase.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019-2023 the original author or authors.
+ * Copyright 2023 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.
@@ -15,26 +15,34 @@
*/
package org.springframework.data.jdbc.testing;
-import org.springframework.test.annotation.IfProfileValue;
-
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;
+import org.springframework.context.annotation.Conditional;
+
/**
- * Run the annotated test only against a HsqlDb database.
- *
- * Requires the use of {@code @ProfileValueSourceConfiguration(DatabaseProfileValueSource.class)} on the test.
+ * Indicates that a component is eligible for registration/evaluation when a profile for a {@link DatabaseType} is
+ * activated.
+ *
+ * This annotation can be used on Spring components and on tests to indicate that a test should be only run when the
+ * appropriate profile is activated.
*
- * @author Jens Schauder
+ * @author Mark Paluch
+ * @see Conditional
+ * @see DatabaseTypeCondition
*/
-@Target({ElementType.TYPE, ElementType.METHOD})
+@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
-@Inherited
-@IfProfileValue(name = "current.database.is.not.hsqldb", value = "false")
-public @interface HsqlDbOnly {
+@Conditional(DatabaseTypeCondition.class)
+public @interface ConditionalOnDatabase {
+
+ /**
+ * Database type on which the annotated class should be enabled.
+ */
+ DatabaseType value();
+
}
diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/DataSourceConfiguration.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/DataSourceConfiguration.java
index d8b2ee290f..41ff35cbd4 100644
--- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/DataSourceConfiguration.java
+++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/DataSourceConfiguration.java
@@ -19,17 +19,14 @@
import java.sql.Connection;
import java.util.Arrays;
-import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.sql.DataSource;
-import org.awaitility.Awaitility;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-
-import org.springframework.beans.factory.annotation.Autowired;
+import org.awaitility.Awaitility;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -45,13 +42,18 @@
* @author Jens Schauder
* @author Oliver Gierke
*/
-@Configuration
+@Configuration(proxyBeanMethods = false)
abstract class DataSourceConfiguration {
private static final Log LOG = LogFactory.getLog(DataSourceConfiguration.class);
- @Autowired Class> testClass;
- @Autowired Environment environment;
+ private final TestClass testClass;
+ private final Environment environment;
+
+ public DataSourceConfiguration(TestClass testClass, Environment environment) {
+ this.testClass = testClass;
+ this.environment = environment;
+ }
@Bean
DataSource dataSource() {
@@ -61,15 +63,15 @@ DataSource dataSource() {
}
@Bean
- DataSourceInitializer initializer() {
+ DataSourceInitializer initializer(DataSource dataSource) {
DataSourceInitializer initializer = new DataSourceInitializer();
- initializer.setDataSource(dataSource());
+ initializer.setDataSource(dataSource);
String[] activeProfiles = environment.getActiveProfiles();
String profile = getDatabaseProfile(activeProfiles);
- ClassPathResource script = new ClassPathResource(TestUtils.createScriptName(testClass, profile));
+ ClassPathResource script = new ClassPathResource(TestUtils.createScriptName(testClass.getTestClass(), profile));
ResourceDatabasePopulator populator = new ResourceDatabasePopulator(script);
customizePopulator(populator);
initializer.setDatabasePopulator(populator);
@@ -79,7 +81,7 @@ DataSourceInitializer initializer() {
private static String getDatabaseProfile(String[] activeProfiles) {
- List validDbs = Arrays.asList("hsql", "h2", "mysql", "mariadb", "postgres", "db2", "oracle", "mssql");
+ List validDbs = Arrays.stream(DatabaseType.values()).map(DatabaseType::getProfile).toList();
for (String profile : activeProfiles) {
if (validDbs.contains(profile)) {
return profile;
@@ -122,6 +124,6 @@ private void verifyConnection(DataSource dataSource) {
}
});
- LOG.info("Connectivity verified");
+ LOG.debug("Connectivity verified");
}
}
diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/DatabaseType.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/DatabaseType.java
new file mode 100644
index 0000000000..6bd865e23b
--- /dev/null
+++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/DatabaseType.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2023 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.data.jdbc.testing;
+
+import java.util.Locale;
+
+/**
+ * Supported database types. Types are defined to express against which database a particular test is expected to run.
+ *
+ * @author Mark Paluch
+ */
+public enum DatabaseType {
+
+ DB2, HSQL, H2, MARIADB, MYSQL, ORACLE, POSTGRES, SQL_SEVER("mssql");
+
+ private final String profile;
+
+ DatabaseType() {
+ this.profile = name().toLowerCase(Locale.ROOT);
+ }
+
+ DatabaseType(String profile) {
+ this.profile = profile;
+ }
+
+ /**
+ * @return the profile string as used in Spring profiles.
+ */
+ public String getProfile() {
+ return profile;
+ }
+}
diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/DatabaseTypeCondition.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/DatabaseTypeCondition.java
new file mode 100644
index 0000000000..a356a3aa51
--- /dev/null
+++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/DatabaseTypeCondition.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2023 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.data.jdbc.testing;
+
+import static org.assertj.core.api.Assumptions.*;
+
+import java.lang.reflect.AnnotatedElement;
+import java.util.Optional;
+
+import org.junit.platform.commons.util.AnnotationUtils;
+import org.springframework.context.annotation.Condition;
+import org.springframework.context.annotation.ConditionContext;
+import org.springframework.core.annotation.Order;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.core.env.StandardEnvironment;
+import org.springframework.core.type.AnnotatedTypeMetadata;
+import org.springframework.test.context.TestContext;
+import org.springframework.test.context.TestExecutionListener;
+import org.springframework.util.MultiValueMap;
+
+/**
+ * {@link Condition} and {@link TestExecutionListener} to test whether the required {@link DatabaseType} configuration
+ * has been configured. The usage through {@link Condition} requires an existing application context while the
+ * {@link TestExecutionListener} usage detects the activated profiles early to avoid expensive application context
+ * startup if the condition does not match.
+ *
+ * @author Mark Paluch
+ */
+@Order(Integer.MIN_VALUE)
+class DatabaseTypeCondition implements Condition, TestExecutionListener {
+
+ @Override
+ public void prepareTestInstance(TestContext testContext) {
+ evaluate(testContext.getTestClass(), new StandardEnvironment(), true);
+ }
+
+ @Override
+ public void beforeTestMethod(TestContext testContext) {
+ evaluate(testContext.getTestMethod(),
+ (ConfigurableEnvironment) testContext.getApplicationContext().getEnvironment(), false);
+ }
+
+ private static void evaluate(AnnotatedElement element, ConfigurableEnvironment environment,
+ boolean enabledByDefault) {
+
+ Optional databaseType = AnnotationUtils.findAnnotation(element, ConditionalOnDatabase.class)
+ .map(ConditionalOnDatabase::value);
+
+ if (databaseType.isEmpty()) {
+ databaseType = AnnotationUtils.findAnnotation(element, EnabledOnDatabase.class).map(EnabledOnDatabase::value);
+ }
+
+ if (databaseType.isPresent()) {
+
+ DatabaseType type = databaseType.get();
+
+ if (enabledByDefault) {
+ EnabledOnDatabaseCustomizer.customizeEnvironment(environment, type);
+ }
+
+ assumeThat(environment.getActiveProfiles()).as("Enabled profiles").contains(type.getProfile());
+ }
+ }
+
+ @Override
+ public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
+
+ MultiValueMap attrs = metadata.getAllAnnotationAttributes(ConditionalOnDatabase.class.getName());
+ if (attrs != null) {
+ for (Object value : attrs.get("value")) {
+
+ DatabaseType type = (DatabaseType) value;
+
+ if (context.getEnvironment().matchesProfiles(type.getProfile())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ return true;
+ }
+
+}
diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/Db2DataSourceConfiguration.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/Db2DataSourceConfiguration.java
index 0879eef1da..e5f3d230c0 100644
--- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/Db2DataSourceConfiguration.java
+++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/Db2DataSourceConfiguration.java
@@ -19,12 +19,10 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-
import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Profile;
+import org.springframework.core.env.Environment;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
-
import org.testcontainers.containers.Db2Container;
/**
@@ -33,8 +31,8 @@
* @author Jens Schauder
* @author Mark Paluch
*/
-@Configuration
-@Profile("db2")
+@Configuration(proxyBeanMethods = false)
+@ConditionalOnDatabase(DatabaseType.DB2)
class Db2DataSourceConfiguration extends DataSourceConfiguration {
public static final String DOCKER_IMAGE_NAME = "ibmcom/db2:11.5.7.0a";
@@ -42,6 +40,10 @@ class Db2DataSourceConfiguration extends DataSourceConfiguration {
private static Db2Container DB_2_CONTAINER;
+ public Db2DataSourceConfiguration(TestClass testClass, Environment environment) {
+ super(testClass, environment);
+ }
+
@Override
protected DataSource createDataSource() {
diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/EnabledOnDatabase.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/EnabledOnDatabase.java
new file mode 100644
index 0000000000..d58b9990c7
--- /dev/null
+++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/EnabledOnDatabase.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2023 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.data.jdbc.testing;
+
+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;
+
+import org.springframework.core.env.Environment;
+import org.springframework.data.jdbc.testing.EnabledOnDatabaseCustomizer.EnabledOnDatabaseCustomizerFactory;
+import org.springframework.data.jdbc.testing.TestClassCustomizer.TestClassCustomizerFactory;
+import org.springframework.test.context.ContextCustomizerFactories;
+
+/**
+ * Selects a database configuration on which the test class is enabled.
+ *
+ * Using this annotation will enable the test configuration if no test environment is given. If a test environment is
+ * configured through {@link Environment#getActiveProfiles()}, then the test class will be skipped if the environment
+ * doesn't match the specified {@link DatabaseType}.
+ *
+ * If a test method is disabled via this annotation, that does not prevent the test class from being instantiated.
+ * Rather, it prevents the execution of the test method and method-level lifecycle callbacks such as {@code @BeforeEach}
+ * methods, {@code @AfterEach} methods, and corresponding extension APIs. When annotated on method and class level, all
+ * annotated features must match to run a test.
+ *
+ * @author Mark Paluch
+ * @see DatabaseTypeCondition
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+// required twice as the annotation lookup doesn't merge multiple occurences of the same annotation
+@ContextCustomizerFactories(value = { TestClassCustomizerFactory.class, EnabledOnDatabaseCustomizerFactory.class })
+@Documented
+@Inherited
+public @interface EnabledOnDatabase {
+
+ /**
+ * Database type on which the annotated class should be enabled.
+ */
+ DatabaseType value();
+}
diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/EnabledOnDatabaseCustomizer.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/EnabledOnDatabaseCustomizer.java
new file mode 100644
index 0000000000..90c69a9259
--- /dev/null
+++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/EnabledOnDatabaseCustomizer.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2023 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.data.jdbc.testing;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.core.annotation.MergedAnnotation;
+import org.springframework.core.annotation.MergedAnnotations;
+import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.test.context.ContextConfigurationAttributes;
+import org.springframework.test.context.ContextCustomizer;
+import org.springframework.test.context.ContextCustomizerFactory;
+import org.springframework.test.context.MergedContextConfiguration;
+
+/**
+ * {@link ContextCustomizer} to select a specific configuration profile based on the {@code @EnabledOnDatabase}
+ * annotation.
+ *
+ * @author Mark Paluch
+ * @see EnabledOnDatabase
+ */
+public class EnabledOnDatabaseCustomizer implements ContextCustomizer {
+
+ private final Class> testClass;
+
+ public EnabledOnDatabaseCustomizer(Class> testClass) {
+ this.testClass = testClass;
+ }
+
+ @Override
+ public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
+
+ MergedAnnotation annotation = MergedAnnotations.from(testClass, SearchStrategy.TYPE_HIERARCHY)
+ .get(EnabledOnDatabase.class);
+
+ if (annotation.isPresent()) {
+
+ DatabaseType value = annotation.getEnum("value", DatabaseType.class);
+
+ customizeEnvironment(context.getEnvironment(), value);
+ }
+ }
+
+ static void customizeEnvironment(ConfigurableEnvironment environment, DatabaseType value) {
+
+ List profiles = Arrays.asList(environment.getActiveProfiles());
+
+ for (DatabaseType databaseType : DatabaseType.values()) {
+
+ if (profiles.contains(databaseType.getProfile())) {
+ return;
+ }
+ }
+
+ environment.addActiveProfile(value.getProfile());
+ }
+
+ public static class EnabledOnDatabaseCustomizerFactory implements ContextCustomizerFactory {
+
+ @Override
+ public ContextCustomizer createContextCustomizer(Class> testClass,
+ List configAttributes) {
+ return new EnabledOnDatabaseCustomizer(testClass);
+ }
+
+ }
+
+}
diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/EnabledOnFeature.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/EnabledOnFeature.java
index cc35f33528..71795ae8b0 100644
--- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/EnabledOnFeature.java
+++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/EnabledOnFeature.java
@@ -22,7 +22,7 @@
import java.lang.annotation.Target;
/**
- * {@code @RequiredFeature} is used to signal that the annotated test class or test method is only enabled on
+ * {@code @RequiredFeature} is used to express that the annotated test class or test method is only enabled on
* one or more specified Spring Data JDBC {@link org.springframework.data.jdbc.testing.TestDatabaseFeatures.Feature
* features} are supported by the underlying database.
*
@@ -33,8 +33,6 @@
* Rather, it prevents the execution of the test method and method-level lifecycle callbacks such as {@code @BeforeEach}
* methods, {@code @AfterEach} methods, and corresponding extension APIs. When annotated on method and class level, all
* annotated features must match to run a test.
- *
- * This annotation cannot be used as meta-annotation.
*
* @author Jens Schauder
* @author Mark Paluch
diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/H2DataSourceConfiguration.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/H2DataSourceConfiguration.java
index 6be9b6bae0..cd8a291a8c 100644
--- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/H2DataSourceConfiguration.java
+++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/H2DataSourceConfiguration.java
@@ -17,10 +17,8 @@
import javax.sql.DataSource;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
@@ -29,11 +27,15 @@
*
* @author Mark Paluch
*/
-@Configuration
-@Profile({ "h2" })
+@Configuration(proxyBeanMethods = false)
+@ConditionalOnDatabase(DatabaseType.H2)
class H2DataSourceConfiguration {
- @Autowired Class> context;
+ private final TestClass testClass;
+
+ public H2DataSourceConfiguration(TestClass testClass) {
+ this.testClass = testClass;
+ }
@Bean
DataSource dataSource() {
@@ -43,7 +45,7 @@ DataSource dataSource() {
.setType(EmbeddedDatabaseType.H2) //
.setScriptEncoding("UTF-8") //
.ignoreFailedDrops(true) //
- .addScript(TestUtils.createScriptName(context, "h2")) //
+ .addScript(TestUtils.createScriptName(testClass.getTestClass(), "h2")) //
.build();
}
}
diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/HsqlDataSourceConfiguration.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/HsqlDataSourceConfiguration.java
index 69a2f4eaf9..be0da90ba5 100644
--- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/HsqlDataSourceConfiguration.java
+++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/HsqlDataSourceConfiguration.java
@@ -17,7 +17,6 @@
import javax.sql.DataSource;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
@@ -30,11 +29,15 @@
* @author Jens Schauder
* @author Oliver Gierke
*/
-@Configuration
+@Configuration(proxyBeanMethods = false)
@Profile({ "hsql", "!h2 && !mysql && !mariadb && !postgres && !oracle && !db2 && !mssql" })
class HsqlDataSourceConfiguration {
- @Autowired Class> context;
+ private final TestClass testClass;
+
+ public HsqlDataSourceConfiguration(TestClass testClass) {
+ this.testClass = testClass;
+ }
@Bean
DataSource dataSource() {
@@ -44,7 +47,7 @@ DataSource dataSource() {
.setType(EmbeddedDatabaseType.HSQL) //
.setScriptEncoding("UTF-8") //
.ignoreFailedDrops(true) //
- .addScript(TestUtils.createScriptName(context, "hsql")) //
+ .addScript(TestUtils.createScriptName(testClass.getTestClass(), "hsql")) //
.build();
}
}
diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/IntegrationTest.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/IntegrationTest.java
new file mode 100644
index 0000000000..dc00d4a02f
--- /dev/null
+++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/IntegrationTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2023 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.data.jdbc.testing;
+
+import static org.springframework.test.context.TestExecutionListeners.MergeMode.*;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.data.jdbc.testing.EnabledOnDatabaseCustomizer.EnabledOnDatabaseCustomizerFactory;
+import org.springframework.data.jdbc.testing.TestClassCustomizer.TestClassCustomizerFactory;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.ContextCustomizerFactories;
+import org.springframework.test.context.TestExecutionListeners;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * {@code @IntegrationTest} is a composed annotation that combines
+ * {@link ExtendWith @ExtendWith(SpringExtension.class)} from JUnit Jupiter with
+ * {@link ContextConfiguration @ContextConfiguration} and {@link TestExecutionListeners @TestExecutionListeners} from
+ * the Spring TestContext Framework enabling transaction management.
+ *
+ * Integration tests use the Spring Context and a potential profile to create an environment for tests to run against.
+ * As integration tests require a specific set of infrastructure components, test classes and configuration components
+ * can be annotated with {@link EnabledOnDatabase @EnabledOnDatabase} or
+ * {@link ConditionalOnDatabase @ConditionalOnDatabase} to enable and restrict or only restrict configuration on which
+ * tests are ran.
+ *
+ * @author Mark Paluch
+ * @see ConditionalOnDatabase
+ * @see EnabledOnDatabase
+ */
+@TestExecutionListeners(value = AssumeFeatureTestExecutionListener.class, mergeMode = MERGE_WITH_DEFAULTS)
+// required twice as the annotation lookup doesn't merge multiple occurences of the same annotation
+@ContextCustomizerFactories(value = { TestClassCustomizerFactory.class, EnabledOnDatabaseCustomizerFactory.class })
+@ActiveProfiles(resolver = CombiningActiveProfileResolver.class)
+@ExtendWith(SpringExtension.class)
+@Transactional
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface IntegrationTest {
+
+}
diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/LicenseListener.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/LicenseListener.java
index ec90bdf87a..a1c7824b93 100644
--- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/LicenseListener.java
+++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/LicenseListener.java
@@ -15,18 +15,15 @@
*/
package org.springframework.data.jdbc.testing;
+import static org.springframework.data.jdbc.testing.MsSqlDataSourceConfiguration.*;
+
import org.junit.AssumptionViolatedException;
import org.springframework.core.annotation.Order;
-import org.springframework.core.env.Profiles;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.TestExecutionListener;
-import org.testcontainers.containers.Db2Container;
-import org.testcontainers.containers.MSSQLServerContainer;
import org.testcontainers.utility.LicenseAcceptance;
-import static org.springframework.data.jdbc.testing.MsSqlDataSourceConfiguration.*;
-
/**
* {@link TestExecutionListener} to selectively skip tests if the license for a particular database container was not
* accepted.
@@ -35,20 +32,18 @@
* @author Jens Schauder
*/
@Order(Integer.MIN_VALUE)
-public class LicenseListener implements TestExecutionListener {
+class LicenseListener implements TestExecutionListener {
- private StandardEnvironment environment;
+ private final StandardEnvironment environment = new StandardEnvironment();
@Override
public void prepareTestInstance(TestContext testContext) {
- environment = new StandardEnvironment();
-
- if (environment.acceptsProfiles(Profiles.of("db2"))) {
+ if (environment.matchesProfiles(DatabaseType.DB2.getProfile())) {
assumeLicenseAccepted(Db2DataSourceConfiguration.DOCKER_IMAGE_NAME);
}
- if (environment.acceptsProfiles(Profiles.of("mssql"))) {
+ if (environment.matchesProfiles(DatabaseType.SQL_SEVER.getProfile())) {
assumeLicenseAccepted(MS_SQL_SERVER_VERSION);
}
}
diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/MariaDBDataSourceConfiguration.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/MariaDBDataSourceConfiguration.java
index 00a7a948cd..7b32593bd2 100644
--- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/MariaDBDataSourceConfiguration.java
+++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/MariaDBDataSourceConfiguration.java
@@ -23,7 +23,7 @@
import org.mariadb.jdbc.MariaDbDataSource;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Profile;
+import org.springframework.core.env.Environment;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.jdbc.datasource.init.ScriptUtils;
import org.testcontainers.containers.MariaDBContainer;
@@ -35,12 +35,16 @@
* @author Mark Paluch
* @author Jens Schauder
*/
-@Configuration
-@Profile("mariadb")
+@Configuration(proxyBeanMethods = false)
+@ConditionalOnDatabase(DatabaseType.MARIADB)
class MariaDBDataSourceConfiguration extends DataSourceConfiguration implements InitializingBean {
private static MariaDBContainer> MARIADB_CONTAINER;
+ public MariaDBDataSourceConfiguration(TestClass testClass, Environment environment) {
+ super(testClass, environment);
+ }
+
@Override
protected DataSource createDataSource() {
diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/MsSqlDataSourceConfiguration.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/MsSqlDataSourceConfiguration.java
index b70ce07e5b..6ad4e40858 100644
--- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/MsSqlDataSourceConfiguration.java
+++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/MsSqlDataSourceConfiguration.java
@@ -18,7 +18,7 @@
import javax.sql.DataSource;
import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Profile;
+import org.springframework.core.env.Environment;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.testcontainers.containers.MSSQLServerContainer;
@@ -33,13 +33,17 @@
* @author Mark Paluch
* @see
*/
-@Configuration
-@Profile({ "mssql" })
+@Configuration(proxyBeanMethods = false)
+@ConditionalOnDatabase(DatabaseType.MARIADB)
public class MsSqlDataSourceConfiguration extends DataSourceConfiguration {
public static final String MS_SQL_SERVER_VERSION = "mcr.microsoft.com/mssql/server:2022-CU5-ubuntu-20.04";
private static MSSQLServerContainer> MSSQL_CONTAINER;
+ public MsSqlDataSourceConfiguration(TestClass testClass, Environment environment) {
+ super(testClass, environment);
+ }
+
@Override
protected DataSource createDataSource() {
diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/MySqlDataSourceConfiguration.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/MySqlDataSourceConfiguration.java
index aa63a7aae6..7155e7741d 100644
--- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/MySqlDataSourceConfiguration.java
+++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/MySqlDataSourceConfiguration.java
@@ -21,7 +21,7 @@
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Profile;
+import org.springframework.core.env.Environment;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.jdbc.datasource.init.ScriptUtils;
import org.testcontainers.containers.MySQLContainer;
@@ -37,12 +37,16 @@
* @author Sedat Gokcen
* @author Mark Paluch
*/
-@Configuration
-@Profile("mysql")
+@Configuration(proxyBeanMethods = false)
+@ConditionalOnDatabase(DatabaseType.MYSQL)
class MySqlDataSourceConfiguration extends DataSourceConfiguration implements InitializingBean {
private static MySQLContainer> MYSQL_CONTAINER;
+ public MySqlDataSourceConfiguration(TestClass testClass, Environment environment) {
+ super(testClass, environment);
+ }
+
@Override
protected DataSource createDataSource() {
diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/OracleDataSourceConfiguration.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/OracleDataSourceConfiguration.java
index e7850f2fcd..b6e7f83713 100644
--- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/OracleDataSourceConfiguration.java
+++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/OracleDataSourceConfiguration.java
@@ -20,7 +20,7 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Profile;
+import org.springframework.core.env.Environment;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
@@ -37,14 +37,18 @@
* @author Thomas Lang
* @author Jens Schauder
*/
-@Configuration
-@Profile("oracle")
+@Configuration(proxyBeanMethods = false)
+@ConditionalOnDatabase(DatabaseType.ORACLE)
public class OracleDataSourceConfiguration extends DataSourceConfiguration {
private static final Log LOG = LogFactory.getLog(OracleDataSourceConfiguration.class);
private static OracleContainer ORACLE_CONTAINER;
+ public OracleDataSourceConfiguration(TestClass testClass, Environment environment) {
+ super(testClass, environment);
+ }
+
@Override
protected DataSource createDataSource() {
diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/PostgresDataSourceConfiguration.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/PostgresDataSourceConfiguration.java
index 022d27564e..ee4f8523d6 100644
--- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/PostgresDataSourceConfiguration.java
+++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/PostgresDataSourceConfiguration.java
@@ -18,11 +18,9 @@
import javax.sql.DataSource;
import org.postgresql.ds.PGSimpleDataSource;
-
import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Profile;
+import org.springframework.core.env.Environment;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
-
import org.testcontainers.containers.PostgreSQLContainer;
/**
@@ -33,12 +31,16 @@
* @author Sedat Gokcen
* @author Mark Paluch
*/
-@Configuration
-@Profile("postgres")
+@Configuration(proxyBeanMethods = false)
+@ConditionalOnDatabase(DatabaseType.POSTGRES)
public class PostgresDataSourceConfiguration extends DataSourceConfiguration {
private static PostgreSQLContainer> POSTGRESQL_CONTAINER;
+ public PostgresDataSourceConfiguration(TestClass testClass, Environment environment) {
+ super(testClass, environment);
+ }
+
@Override
protected DataSource createDataSource() {
diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestClass.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestClass.java
new file mode 100644
index 0000000000..76e48d8170
--- /dev/null
+++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestClass.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2023 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.data.jdbc.testing;
+
+import org.springframework.util.Assert;
+
+/**
+ * Value object to represent the underlying test class.
+ *
+ * @author Mark Paluch
+ */
+public final class TestClass {
+
+ private final Class> testClass;
+
+ private TestClass(Class> testClass) {
+ this.testClass = testClass;
+ }
+
+ /**
+ * Create a new {@link TestClass} given {@code testClass}.
+ *
+ * @param testClass must not be {@literal null}.
+ * @return the new {@link TestClass}.
+ */
+ public static TestClass of(Class> testClass) {
+
+ Assert.notNull(testClass, "TestClass must not be null");
+
+ return new TestClass(testClass);
+ }
+
+ public Class> getTestClass() {
+ return testClass;
+ }
+}
diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestClassCustomizer.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestClassCustomizer.java
new file mode 100644
index 0000000000..80cce4bea3
--- /dev/null
+++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestClassCustomizer.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2023 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.data.jdbc.testing;
+
+import java.util.List;
+
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.test.context.ContextConfigurationAttributes;
+import org.springframework.test.context.ContextCustomizer;
+import org.springframework.test.context.ContextCustomizerFactory;
+import org.springframework.test.context.MergedContextConfiguration;
+
+/**
+ * {@link ContextCustomizer} registering {@link TestClass}.
+ *
+ * @author Mark Paluch
+ */
+class TestClassCustomizer implements ContextCustomizer {
+
+ private final Class> testClass;
+
+ public TestClassCustomizer(Class> testClass) {
+ this.testClass = testClass;
+ }
+
+ @Override
+ public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
+ context.getBeanFactory().registerSingleton(TestClass.class.getSimpleName(), TestClass.of(testClass));
+ }
+
+ static class TestClassCustomizerFactory implements ContextCustomizerFactory {
+
+ @Override
+ public ContextCustomizer createContextCustomizer(Class> testClass,
+ List configAttributes) {
+ return new TestClassCustomizer(testClass);
+ }
+
+ }
+}
diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java
index 9db80b98a4..4d84e069a1 100644
--- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java
+++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java
@@ -31,6 +31,7 @@
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
+import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.jdbc.core.convert.*;
@@ -92,6 +93,7 @@ JdbcRepositoryFactory jdbcRepositoryFactory(
}
@Bean
+ @Primary
NamedParameterJdbcOperations namedParameterJdbcTemplate() {
return new NamedParameterJdbcTemplate(dataSource);
}
diff --git a/spring-data-jdbc/src/test/resources/META-INF/spring.factories b/spring-data-jdbc/src/test/resources/META-INF/spring.factories
index 8116a09259..9631bc279e 100644
--- a/spring-data-jdbc/src/test/resources/META-INF/spring.factories
+++ b/spring-data-jdbc/src/test/resources/META-INF/spring.factories
@@ -1 +1 @@
-org.springframework.test.context.TestExecutionListener=org.springframework.data.jdbc.testing.LicenseListener
+org.springframework.test.context.TestExecutionListener=org.springframework.data.jdbc.testing.LicenseListener,org.springframework.data.jdbc.testing.DatabaseTypeCondition
diff --git a/spring-data-r2dbc/pom.xml b/spring-data-r2dbc/pom.xml
index a60f8e183a..dea4bbfbae 100644
--- a/spring-data-r2dbc/pom.xml
+++ b/spring-data-r2dbc/pom.xml
@@ -6,7 +6,7 @@
4.0.0
spring-data-r2dbc
- 3.2.0-SNAPSHOT
+ 3.2.0-GH-1620-SNAPSHOT
Spring Data R2DBC
Spring Data module for R2DBC
@@ -15,7 +15,7 @@
org.springframework.data
spring-data-relational-parent
- 3.2.0-SNAPSHOT
+ 3.2.0-GH-1620-SNAPSHOT
diff --git a/spring-data-relational/pom.xml b/spring-data-relational/pom.xml
index 74f350faa8..a2196c3c62 100644
--- a/spring-data-relational/pom.xml
+++ b/spring-data-relational/pom.xml
@@ -6,7 +6,7 @@
4.0.0
spring-data-relational
- 3.2.0-SNAPSHOT
+ 3.2.0-GH-1620-SNAPSHOT
Spring Data Relational
Spring Data Relational support
@@ -14,7 +14,7 @@
org.springframework.data
spring-data-relational-parent
- 3.2.0-SNAPSHOT
+ 3.2.0-GH-1620-SNAPSHOT