Make Integration DataSource init back off without s-b-sql

Previously, the DataSource initialization would back off without
spring-boot-jdbc but spring-boot-sql was a required dependency.
Without spring-boot-sql, a failure would occur due to the absence
of OnDatabaseInitializationCondition.

This commit updates the auto-configuration so that spring-boot-sql
is now an optional dependency and DataSource initialization
backs off in its absence.

Closes gh-46244
This commit is contained in:
Andy Wilkinson 2025-07-02 09:26:59 +01:00
parent a52d5538ee
commit ce9ffd17fd
7 changed files with 137 additions and 87 deletions

View File

@ -29,8 +29,6 @@ dependencies {
api(project(":spring-boot-project:spring-boot"))
api("org.springframework.integration:spring-integration-core")
implementation(project(":spring-boot-project:spring-boot-sql"))
optional(project(":spring-boot-project:spring-boot-actuator-autoconfigure"))
optional(project(":spring-boot-project:spring-boot-autoconfigure"))
optional(project(":spring-boot-project:spring-boot-jdbc"))

View File

@ -264,16 +264,25 @@ public class IntegrationAutoConfiguration {
* Integration JDBC configuration.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ JdbcMessageStore.class, DataSourceScriptDatabaseInitializer.class })
@ConditionalOnSingleCandidate(DataSource.class)
@Conditional(OnIntegrationDatasourceInitializationCondition.class)
@ConditionalOnClass({ JdbcMessageStore.class, DataSourceScriptDatabaseInitializer.class })
@EnableConfigurationProperties(IntegrationJdbcProperties.class)
protected static class IntegrationJdbcConfiguration {
@Bean
@ConditionalOnMissingBean
public IntegrationDataSourceScriptDatabaseInitializer integrationDataSourceInitializer(DataSource dataSource,
IntegrationProperties properties) {
return new IntegrationDataSourceScriptDatabaseInitializer(dataSource, properties.getJdbc());
@Conditional(OnIntegrationDatasourceInitializationCondition.class)
IntegrationDataSourceScriptDatabaseInitializer integrationDataSourceInitializer(DataSource dataSource,
IntegrationJdbcProperties properties) {
return new IntegrationDataSourceScriptDatabaseInitializer(dataSource, properties);
}
static class OnIntegrationDatasourceInitializationCondition extends OnDatabaseInitializationCondition {
OnIntegrationDatasourceInitializationCondition() {
super("Integration", "spring.integration.jdbc.initialize-schema");
}
}
}
@ -374,12 +383,4 @@ public class IntegrationAutoConfiguration {
}
static class OnIntegrationDatasourceInitializationCondition extends OnDatabaseInitializationCondition {
OnIntegrationDatasourceInitializationCondition() {
super("Integration", "spring.integration.jdbc.initialize-schema");
}
}
}

View File

@ -42,8 +42,7 @@ public class IntegrationDataSourceScriptDatabaseInitializer extends DataSourceSc
* @param properties the Spring Integration JDBC properties
* @see #getSettings
*/
public IntegrationDataSourceScriptDatabaseInitializer(DataSource dataSource,
IntegrationProperties.Jdbc properties) {
public IntegrationDataSourceScriptDatabaseInitializer(DataSource dataSource, IntegrationJdbcProperties properties) {
this(dataSource, getSettings(dataSource, properties));
}
@ -59,7 +58,7 @@ public class IntegrationDataSourceScriptDatabaseInitializer extends DataSourceSc
}
/**
* Adapts {@link IntegrationProperties.Jdbc Spring Integration JDBC properties} to
* Adapts {@link IntegrationJdbcProperties Spring Integration JDBC properties} to
* {@link DatabaseInitializationSettings} replacing any {@literal @@platform@@}
* placeholders.
* @param dataSource the Spring Integration data source
@ -68,7 +67,7 @@ public class IntegrationDataSourceScriptDatabaseInitializer extends DataSourceSc
* @see #IntegrationDataSourceScriptDatabaseInitializer(DataSource,
* DatabaseInitializationSettings)
*/
static DatabaseInitializationSettings getSettings(DataSource dataSource, IntegrationProperties.Jdbc properties) {
static DatabaseInitializationSettings getSettings(DataSource dataSource, IntegrationJdbcProperties properties) {
DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
settings.setSchemaLocations(resolveSchemaLocations(dataSource, properties));
settings.setMode(properties.getInitializeSchema());
@ -76,7 +75,7 @@ public class IntegrationDataSourceScriptDatabaseInitializer extends DataSourceSc
return settings;
}
private static List<String> resolveSchemaLocations(DataSource dataSource, IntegrationProperties.Jdbc properties) {
private static List<String> resolveSchemaLocations(DataSource dataSource, IntegrationJdbcProperties properties) {
PlatformPlaceholderDatabaseDriverResolver platformResolver = new PlatformPlaceholderDatabaseDriverResolver();
platformResolver = platformResolver.withDriverPlatform(DatabaseDriver.MARIADB, "mysql");
if (StringUtils.hasText(properties.getPlatform())) {

View File

@ -0,0 +1,76 @@
/*
* Copyright 2012-present 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.boot.integration.autoconfigure;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.sql.init.DatabaseInitializationMode;
/**
* Configuration properties for Spring Integration JDBC.
*
* @author Vedran Pavic
* @author Stephane Nicoll
* @author Artem Bilan
* @since 4.0.0
*/
@ConfigurationProperties("spring.integration.jdbc")
public class IntegrationJdbcProperties {
private static final String DEFAULT_SCHEMA_LOCATION = "classpath:org/springframework/"
+ "integration/jdbc/schema-@@platform@@.sql";
/**
* Path to the SQL file to use to initialize the database schema.
*/
private String schema = DEFAULT_SCHEMA_LOCATION;
/**
* Platform to use in initialization scripts if the @@platform@@ placeholder is used.
* Auto-detected by default.
*/
private String platform;
/**
* Database schema initialization mode.
*/
private DatabaseInitializationMode initializeSchema = DatabaseInitializationMode.EMBEDDED;
public String getSchema() {
return this.schema;
}
public void setSchema(String schema) {
this.schema = schema;
}
public String getPlatform() {
return this.platform;
}
public void setPlatform(String platform) {
this.platform = platform;
}
public DatabaseInitializationMode getInitializeSchema() {
return this.initializeSchema;
}
public void setInitializeSchema(DatabaseInitializationMode initializeSchema) {
this.initializeSchema = initializeSchema;
}
}

View File

@ -22,7 +22,6 @@ import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.sql.init.DatabaseInitializationMode;
/**
* Configuration properties for Spring Integration.
@ -41,8 +40,6 @@ public class IntegrationProperties {
private final Error error = new Error();
private final Jdbc jdbc = new Jdbc();
private final RSocket rsocket = new RSocket();
private final Poller poller = new Poller();
@ -61,10 +58,6 @@ public class IntegrationProperties {
return this.error;
}
public Jdbc getJdbc() {
return this.jdbc;
}
public RSocket getRsocket() {
return this.rsocket;
}
@ -212,53 +205,6 @@ public class IntegrationProperties {
}
public static class Jdbc {
private static final String DEFAULT_SCHEMA_LOCATION = "classpath:org/springframework/"
+ "integration/jdbc/schema-@@platform@@.sql";
/**
* Path to the SQL file to use to initialize the database schema.
*/
private String schema = DEFAULT_SCHEMA_LOCATION;
/**
* Platform to use in initialization scripts if the @@platform@@ placeholder is
* used. Auto-detected by default.
*/
private String platform;
/**
* Database schema initialization mode.
*/
private DatabaseInitializationMode initializeSchema = DatabaseInitializationMode.EMBEDDED;
public String getSchema() {
return this.schema;
}
public void setSchema(String schema) {
this.schema = schema;
}
public String getPlatform() {
return this.platform;
}
public void setPlatform(String platform) {
this.platform = platform;
}
public DatabaseInitializationMode getInitializeSchema() {
return this.initializeSchema;
}
public void setInitializeSchema(DatabaseInitializationMode initializeSchema) {
this.initializeSchema = initializeSchema;
}
}
public static class RSocket {
private final Client client = new Client();

View File

@ -46,6 +46,7 @@ import org.springframework.boot.context.properties.source.MutuallyExclusiveConfi
import org.springframework.boot.flyway.autoconfigure.FlywayAutoConfiguration;
import org.springframework.boot.integration.autoconfigure.IntegrationAutoConfiguration.IntegrationComponentScanConfiguration;
import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration;
import org.springframework.boot.jdbc.autoconfigure.DataSourceProperties;
import org.springframework.boot.jdbc.autoconfigure.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.jdbc.autoconfigure.EmbeddedDataSourceConfiguration;
import org.springframework.boot.jdbc.autoconfigure.JdbcTemplateAutoConfiguration;
@ -56,6 +57,7 @@ import org.springframework.boot.rsocket.autoconfigure.RSocketServerAutoConfigura
import org.springframework.boot.rsocket.autoconfigure.RSocketStrategiesAutoConfiguration;
import org.springframework.boot.sql.init.DatabaseInitializationMode;
import org.springframework.boot.sql.init.DatabaseInitializationSettings;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.testsupport.assertj.SimpleAsyncTaskExecutorAssert;
import org.springframework.boot.testsupport.classpath.resources.WithResource;
@ -207,8 +209,8 @@ class IntegrationAutoConfigurationTests {
.withPropertyValues("spring.datasource.generate-unique-name=true",
"spring.integration.jdbc.initialize-schema=always")
.run((context) -> {
IntegrationProperties properties = context.getBean(IntegrationProperties.class);
assertThat(properties.getJdbc().getInitializeSchema()).isEqualTo(DatabaseInitializationMode.ALWAYS);
IntegrationJdbcProperties properties = context.getBean(IntegrationJdbcProperties.class);
assertThat(properties.getInitializeSchema()).isEqualTo(DatabaseInitializationMode.ALWAYS);
JdbcOperations jdbc = context.getBean(JdbcOperations.class);
assertThat(jdbc.queryForList("select * from INT_MESSAGE")).isEmpty();
assertThat(jdbc.queryForList("select * from INT_GROUP_TO_MESSAGE")).isEmpty();
@ -227,8 +229,8 @@ class IntegrationAutoConfigurationTests {
.withPropertyValues("spring.datasource.generate-unique-name=true",
"spring.integration.jdbc.initialize-schema=always")
.run((context) -> {
IntegrationProperties properties = context.getBean(IntegrationProperties.class);
assertThat(properties.getJdbc().getInitializeSchema()).isEqualTo(DatabaseInitializationMode.ALWAYS);
IntegrationJdbcProperties properties = context.getBean(IntegrationJdbcProperties.class);
assertThat(properties.getInitializeSchema()).isEqualTo(DatabaseInitializationMode.ALWAYS);
JdbcOperations jdbc = context.getBean(JdbcOperations.class);
assertThat(jdbc.queryForList("select * from INT_MESSAGE")).isEmpty();
assertThat(jdbc.queryForList("select * from INT_GROUP_TO_MESSAGE")).isEmpty();
@ -247,8 +249,8 @@ class IntegrationAutoConfigurationTests {
"spring.integration.jdbc.initialize-schema=never")
.run((context) -> {
assertThat(context).doesNotHaveBean(IntegrationDataSourceScriptDatabaseInitializer.class);
IntegrationProperties properties = context.getBean(IntegrationProperties.class);
assertThat(properties.getJdbc().getInitializeSchema()).isEqualTo(DatabaseInitializationMode.NEVER);
IntegrationJdbcProperties properties = context.getBean(IntegrationJdbcProperties.class);
assertThat(properties.getInitializeSchema()).isEqualTo(DatabaseInitializationMode.NEVER);
JdbcOperations jdbc = context.getBean(JdbcOperations.class);
assertThatExceptionOfType(BadSqlGrammarException.class)
.isThrownBy(() -> jdbc.queryForList("select * from INT_MESSAGE"));
@ -262,13 +264,29 @@ class IntegrationAutoConfigurationTests {
JdbcTemplateAutoConfiguration.class, IntegrationAutoConfiguration.class))
.withPropertyValues("spring.datasource.generate-unique-name=true")
.run((context) -> {
IntegrationProperties properties = context.getBean(IntegrationProperties.class);
assertThat(properties.getJdbc().getInitializeSchema()).isEqualTo(DatabaseInitializationMode.EMBEDDED);
IntegrationJdbcProperties properties = context.getBean(IntegrationJdbcProperties.class);
assertThat(properties.getInitializeSchema()).isEqualTo(DatabaseInitializationMode.EMBEDDED);
JdbcOperations jdbc = context.getBean(JdbcOperations.class);
assertThat(jdbc.queryForList("select * from INT_MESSAGE")).isEmpty();
});
}
@Test
void integrationJdbcDataSourceInitializerBacksOffWithoutSpringBootJdbc() {
this.contextRunner.withBean(DataSource.class, IntegrationAutoConfigurationTests::createTestDataSource)
.withClassLoader(new FilteredClassLoader("org.springframework.boot.jdbc"))
.run((context) -> assertThat(context)
.doesNotHaveBean(IntegrationDataSourceScriptDatabaseInitializer.class));
}
@Test
void integrationJdbcDataSourceInitializerBacksOffWithoutSpringBootJdbcAndSql() {
this.contextRunner.withBean(DataSource.class, IntegrationAutoConfigurationTests::createTestDataSource)
.withClassLoader(new FilteredClassLoader("org.springframework.boot.jdbc", "org.springframework.boot.sql"))
.run((context) -> assertThat(context)
.doesNotHaveBean(IntegrationDataSourceScriptDatabaseInitializer.class));
}
@Test
void rsocketSupportEnabled() {
this.contextRunner.withUserConfiguration(RSocketServerConfiguration.class)
@ -573,6 +591,18 @@ class IntegrationAutoConfigurationTests {
});
}
private static DataSource createTestDataSource() {
DataSourceProperties properties = new DataSourceProperties();
properties.setGenerateUniqueName(true);
try {
properties.afterPropertiesSet();
}
catch (Exception ex) {
throw new RuntimeException(ex);
}
return properties.initializeDataSourceBuilder().build();
}
@Configuration(proxyBeanMethods = false)
static class CustomMBeanExporter {
@ -632,8 +662,8 @@ class IntegrationAutoConfigurationTests {
@Bean
IntegrationDataSourceScriptDatabaseInitializer customInitializer(DataSource dataSource,
IntegrationProperties properties) {
return new IntegrationDataSourceScriptDatabaseInitializer(dataSource, properties.getJdbc());
IntegrationJdbcProperties properties) {
return new IntegrationDataSourceScriptDatabaseInitializer(dataSource, properties);
}
}

View File

@ -36,10 +36,10 @@ class IntegrationDataSourceScriptDatabaseInitializerTests {
@Test
void getSettingsWithPlatformDoesNotTouchDataSource() {
DataSource dataSource = mock(DataSource.class);
IntegrationProperties properties = new IntegrationProperties();
properties.getJdbc().setPlatform("test");
IntegrationJdbcProperties properties = new IntegrationJdbcProperties();
properties.setPlatform("test");
DatabaseInitializationSettings settings = IntegrationDataSourceScriptDatabaseInitializer.getSettings(dataSource,
properties.getJdbc());
properties);
assertThat(settings.getSchemaLocations())
.containsOnly("classpath:org/springframework/integration/jdbc/schema-test.sql");
then(dataSource).shouldHaveNoInteractions();