diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java index df6712f741..5fd4bda9e3 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java @@ -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()); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfigurationWithoutSpringJdbcTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfigurationWithoutSpringJdbcTests.java new file mode 100644 index 0000000000..df6f394e95 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfigurationWithoutSpringJdbcTests.java @@ -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 hideConnectionPools() { + return (runner) -> runner.withClassLoader(new FilteredClassLoader("org.apache.tomcat", "com.zaxxer.hikari", + "org.apache.commons.dbcp2", "oracle.ucp.jdbc", "com.mchange")); + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/DataSourceBuilder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/DataSourceBuilder.java index 697363e30c..c71a224b6e 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/DataSourceBuilder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/DataSourceBuilder.java @@ -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 { * @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)); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/EmbeddedDatabaseConnection.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/EmbeddedDatabaseConnection.java index 9a1e4e79e8..143e23a22f 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/EmbeddedDatabaseConnection.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/EmbeddedDatabaseConnection.java @@ -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 { + private static final class IsEmbedded implements ThrowingFunction { @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) {