Skip to content

Commit b6bccc1

Browse files
committed
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
1 parent 58fab66 commit b6bccc1

File tree

4 files changed

+93
-16
lines changed

4 files changed

+93
-16
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
2424
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
2525
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
26+
import org.springframework.boot.autoconfigure.condition.ConditionMessage.Style;
2627
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
2728
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2829
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -42,6 +43,7 @@
4243
import org.springframework.core.env.Environment;
4344
import org.springframework.core.type.AnnotatedTypeMetadata;
4445
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
46+
import org.springframework.util.ClassUtils;
4547
import org.springframework.util.StringUtils;
4648

4749
/**
@@ -132,6 +134,8 @@ static class EmbeddedDatabaseCondition extends SpringBootCondition {
132134

133135
private static final String DATASOURCE_URL_PROPERTY = "spring.datasource.url";
134136

137+
private static final String EMBEDDED_DATABASE_TYPE = "org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType";
138+
135139
private final SpringBootCondition pooledCondition = new PooledDataSourceCondition();
136140

137141
@Override
@@ -143,6 +147,10 @@ public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeM
143147
if (anyMatches(context, metadata, this.pooledCondition)) {
144148
return ConditionOutcome.noMatch(message.foundExactly("supported pooled data source"));
145149
}
150+
if (!ClassUtils.isPresent(EMBEDDED_DATABASE_TYPE, context.getClassLoader())) {
151+
return ConditionOutcome
152+
.noMatch(message.didNotFind("required class").items(Style.QUOTE, EMBEDDED_DATABASE_TYPE));
153+
}
146154
EmbeddedDatabaseType type = EmbeddedDatabaseConnection.get(context.getClassLoader()).getType();
147155
if (type == null) {
148156
return ConditionOutcome.noMatch(message.didNotFind("embedded database").atAll());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright 2012-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.jdbc;
18+
19+
import java.util.Random;
20+
import java.util.function.Function;
21+
22+
import javax.sql.DataSource;
23+
24+
import com.zaxxer.hikari.HikariDataSource;
25+
import org.junit.jupiter.api.Test;
26+
27+
import org.springframework.boot.autoconfigure.AutoConfigurations;
28+
import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener;
29+
import org.springframework.boot.logging.LogLevel;
30+
import org.springframework.boot.test.context.FilteredClassLoader;
31+
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
32+
import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
33+
34+
import static org.assertj.core.api.Assertions.assertThat;
35+
36+
/**
37+
* Tests for {@link DataSourceAutoConfiguration} without spring-jdbc on the classpath.
38+
*
39+
* @author Andy Wilkinson
40+
*/
41+
@ClassPathExclusions("spring-jdbc-*.jar")
42+
class DataSourceAutoConfigurationWithoutSpringJdbcTests {
43+
44+
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
45+
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class));
46+
47+
@Test
48+
void pooledDataSourceCanBeAutoConfigured() {
49+
this.contextRunner.withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO))
50+
.run((context) -> {
51+
HikariDataSource dataSource = context.getBean(HikariDataSource.class);
52+
assertThat(dataSource.getJdbcUrl()).isNotNull();
53+
assertThat(dataSource.getDriverClassName()).isNotNull();
54+
});
55+
}
56+
57+
@Test
58+
void withoutConnectionPoolsAutoConfigurationBacksOff() {
59+
this.contextRunner.withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO))
60+
.with(hideConnectionPools())
61+
.run((context) -> assertThat(context).doesNotHaveBean(DataSource.class));
62+
}
63+
64+
@Test
65+
void withUrlAndWithoutConnectionPoolsAutoConfigurationBacksOff() {
66+
this.contextRunner.withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO))
67+
.with(hideConnectionPools())
68+
.withPropertyValues("spring.datasource.url:jdbc:hsqldb:mem:testdb-" + new Random().nextInt())
69+
.run((context) -> assertThat(context).doesNotHaveBean(DataSource.class));
70+
}
71+
72+
private static Function<ApplicationContextRunner, ApplicationContextRunner> hideConnectionPools() {
73+
return (runner) -> runner.withClassLoader(new FilteredClassLoader("org.apache.tomcat", "com.zaxxer.hikari",
74+
"org.apache.commons.dbcp2", "oracle.ucp.jdbc", "com.mchange"));
75+
}
76+
77+
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/DataSourceBuilder.java

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
import org.springframework.beans.BeanUtils;
4242
import org.springframework.core.ResolvableType;
4343
import org.springframework.jdbc.datasource.SimpleDriverDataSource;
44-
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
4544
import org.springframework.util.Assert;
4645
import org.springframework.util.ClassUtils;
4746
import org.springframework.util.ReflectionUtils;
@@ -233,14 +232,6 @@ public static DataSourceBuilder<?> create(ClassLoader classLoader) {
233232
* @since 2.5.0
234233
*/
235234
public static DataSourceBuilder<?> derivedFrom(DataSource dataSource) {
236-
if (dataSource instanceof EmbeddedDatabase) {
237-
try {
238-
dataSource = dataSource.unwrap(DataSource.class);
239-
}
240-
catch (SQLException ex) {
241-
throw new IllegalStateException("Unable to unwrap embedded database", ex);
242-
}
243-
}
244235
return new DataSourceBuilder<>(unwrap(dataSource));
245236
}
246237

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/EmbeddedDatabaseConnection.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,10 @@
2525
import javax.sql.DataSource;
2626

2727
import org.springframework.dao.DataAccessException;
28-
import org.springframework.jdbc.core.ConnectionCallback;
29-
import org.springframework.jdbc.core.JdbcTemplate;
3028
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
3129
import org.springframework.util.Assert;
3230
import org.springframework.util.ClassUtils;
31+
import org.springframework.util.function.ThrowingFunction;
3332

3433
/**
3534
* Connection details for {@link EmbeddedDatabaseType embedded databases}.
@@ -165,9 +164,11 @@ private static EmbeddedDatabaseConnection getEmbeddedDatabaseConnection(String d
165164
*/
166165
public static boolean isEmbedded(DataSource dataSource) {
167166
try {
168-
return new JdbcTemplate(dataSource).execute(new IsEmbedded());
167+
try (Connection connection = dataSource.getConnection()) {
168+
return new IsEmbedded().apply(connection);
169+
}
169170
}
170-
catch (DataAccessException ex) {
171+
catch (SQLException ex) {
171172
// Could not connect, which means it's not embedded
172173
return false;
173174
}
@@ -189,12 +190,12 @@ public static EmbeddedDatabaseConnection get(ClassLoader classLoader) {
189190
}
190191

191192
/**
192-
* {@link ConnectionCallback} to determine if a connection is embedded.
193+
* Determine if a {@link Connection} is embedded.
193194
*/
194-
private static final class IsEmbedded implements ConnectionCallback<Boolean> {
195+
private static final class IsEmbedded implements ThrowingFunction<Connection, Boolean> {
195196

196197
@Override
197-
public Boolean doInConnection(Connection connection) throws SQLException, DataAccessException {
198+
public Boolean applyWithException(Connection connection) throws SQLException, DataAccessException {
198199
DatabaseMetaData metaData = connection.getMetaData();
199200
String productName = metaData.getDatabaseProductName();
200201
if (productName == null) {

0 commit comments

Comments
 (0)