Support DataSource auto-configuration without spring-jdbc
Prior to these changes, auto-configured a DataSource required spring-jdbc to be on the classpath even if the app made no use of any of its features. The changes largely remove the use of spring-jdbc during DataSoruce auto-configure or disable some support (configuring an unpooled, embedded database) in its absense. The extra unwrapping in DataSourceBuilder has been removed as it appears to be redundant. The existing test for deriving from an embedded database continues to work without it. Closes gh-43786
This commit is contained in:
@@ -23,6 +23,7 @@ import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionMessage.Style;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
@@ -42,6 +43,7 @@ import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -132,6 +134,8 @@ public class DataSourceAutoConfiguration {
|
||||
|
||||
private static final String DATASOURCE_URL_PROPERTY = "spring.datasource.url";
|
||||
|
||||
private static final String EMBEDDED_DATABASE_TYPE = "org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType";
|
||||
|
||||
private final SpringBootCondition pooledCondition = new PooledDataSourceCondition();
|
||||
|
||||
@Override
|
||||
@@ -143,6 +147,10 @@ public class DataSourceAutoConfiguration {
|
||||
if (anyMatches(context, metadata, this.pooledCondition)) {
|
||||
return ConditionOutcome.noMatch(message.foundExactly("supported pooled data source"));
|
||||
}
|
||||
if (!ClassUtils.isPresent(EMBEDDED_DATABASE_TYPE, context.getClassLoader())) {
|
||||
return ConditionOutcome
|
||||
.noMatch(message.didNotFind("required class").items(Style.QUOTE, EMBEDDED_DATABASE_TYPE));
|
||||
}
|
||||
EmbeddedDatabaseType type = EmbeddedDatabaseConnection.get(context.getClassLoader()).getType();
|
||||
if (type == null) {
|
||||
return ConditionOutcome.noMatch(message.didNotFind("embedded database").atAll());
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.autoconfigure.jdbc;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener;
|
||||
import org.springframework.boot.logging.LogLevel;
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link DataSourceAutoConfiguration} without spring-jdbc on the classpath.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@ClassPathExclusions("spring-jdbc-*.jar")
|
||||
class DataSourceAutoConfigurationWithoutSpringJdbcTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void pooledDataSourceCanBeAutoConfigured() {
|
||||
this.contextRunner.withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO))
|
||||
.run((context) -> {
|
||||
HikariDataSource dataSource = context.getBean(HikariDataSource.class);
|
||||
assertThat(dataSource.getJdbcUrl()).isNotNull();
|
||||
assertThat(dataSource.getDriverClassName()).isNotNull();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void withoutConnectionPoolsAutoConfigurationBacksOff() {
|
||||
this.contextRunner.withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO))
|
||||
.with(hideConnectionPools())
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(DataSource.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void withUrlAndWithoutConnectionPoolsAutoConfigurationBacksOff() {
|
||||
this.contextRunner.withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO))
|
||||
.with(hideConnectionPools())
|
||||
.withPropertyValues("spring.datasource.url:jdbc:hsqldb:mem:testdb-" + new Random().nextInt())
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(DataSource.class));
|
||||
}
|
||||
|
||||
private static Function<ApplicationContextRunner, ApplicationContextRunner> hideConnectionPools() {
|
||||
return (runner) -> runner.withClassLoader(new FilteredClassLoader("org.apache.tomcat", "com.zaxxer.hikari",
|
||||
"org.apache.commons.dbcp2", "oracle.ucp.jdbc", "com.mchange"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -41,7 +41,6 @@ import org.vibur.dbcp.ViburDBCPDataSource;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.jdbc.datasource.SimpleDriverDataSource;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
@@ -233,14 +232,6 @@ public final class DataSourceBuilder<T extends DataSource> {
|
||||
* @since 2.5.0
|
||||
*/
|
||||
public static DataSourceBuilder<?> derivedFrom(DataSource dataSource) {
|
||||
if (dataSource instanceof EmbeddedDatabase) {
|
||||
try {
|
||||
dataSource = dataSource.unwrap(DataSource.class);
|
||||
}
|
||||
catch (SQLException ex) {
|
||||
throw new IllegalStateException("Unable to unwrap embedded database", ex);
|
||||
}
|
||||
}
|
||||
return new DataSourceBuilder<>(unwrap(dataSource));
|
||||
}
|
||||
|
||||
|
||||
@@ -25,11 +25,10 @@ import java.util.stream.Stream;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.jdbc.core.ConnectionCallback;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.function.ThrowingFunction;
|
||||
|
||||
/**
|
||||
* Connection details for {@link EmbeddedDatabaseType embedded databases}.
|
||||
@@ -165,9 +164,11 @@ public enum EmbeddedDatabaseConnection {
|
||||
*/
|
||||
public static boolean isEmbedded(DataSource dataSource) {
|
||||
try {
|
||||
return new JdbcTemplate(dataSource).execute(new IsEmbedded());
|
||||
try (Connection connection = dataSource.getConnection()) {
|
||||
return new IsEmbedded().apply(connection);
|
||||
}
|
||||
}
|
||||
catch (DataAccessException ex) {
|
||||
catch (SQLException ex) {
|
||||
// Could not connect, which means it's not embedded
|
||||
return false;
|
||||
}
|
||||
@@ -189,12 +190,12 @@ public enum EmbeddedDatabaseConnection {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ConnectionCallback} to determine if a connection is embedded.
|
||||
* Determine if a {@link Connection} is embedded.
|
||||
*/
|
||||
private static final class IsEmbedded implements ConnectionCallback<Boolean> {
|
||||
private static final class IsEmbedded implements ThrowingFunction<Connection, Boolean> {
|
||||
|
||||
@Override
|
||||
public Boolean doInConnection(Connection connection) throws SQLException, DataAccessException {
|
||||
public Boolean applyWithException(Connection connection) throws SQLException, DataAccessException {
|
||||
DatabaseMetaData metaData = connection.getMetaData();
|
||||
String productName = metaData.getDatabaseProductName();
|
||||
if (productName == null) {
|
||||
|
||||
Reference in New Issue
Block a user