Merge branch '3.4.x'

Closes gh-45003
This commit is contained in:
Andy Wilkinson
2025-04-04 12:10:09 +01:00
5 changed files with 112 additions and 15 deletions

View File

@@ -33,6 +33,7 @@ import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.jdbc.DatabaseDriver; import org.springframework.boot.jdbc.DatabaseDriver;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
@@ -50,10 +51,17 @@ abstract class DataSourceConfiguration {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private static <T> T createDataSource(JdbcConnectionDetails connectionDetails, Class<? extends DataSource> type, private static <T> T createDataSource(JdbcConnectionDetails connectionDetails, Class<? extends DataSource> type,
ClassLoader classLoader) { ClassLoader classLoader) {
return (T) DataSourceBuilder.create(classLoader) return createDataSource(connectionDetails, type, classLoader, true);
.type(type) }
.driverClassName(connectionDetails.getDriverClassName())
.url(connectionDetails.getJdbcUrl()) @SuppressWarnings("unchecked")
private static <T> T createDataSource(JdbcConnectionDetails connectionDetails, Class<? extends DataSource> type,
ClassLoader classLoader, boolean applyDriverClassName) {
DataSourceBuilder<? extends DataSource> builder = DataSourceBuilder.create(classLoader).type(type);
if (applyDriverClassName) {
builder.driverClassName(connectionDetails.getDriverClassName());
}
return (T) builder.url(connectionDetails.getJdbcUrl())
.username(connectionDetails.getUsername()) .username(connectionDetails.getUsername())
.password(connectionDetails.getPassword()) .password(connectionDetails.getPassword())
.build(); .build();
@@ -113,9 +121,11 @@ abstract class DataSourceConfiguration {
@Bean @Bean
@ConfigurationProperties("spring.datasource.hikari") @ConfigurationProperties("spring.datasource.hikari")
HikariDataSource dataSource(DataSourceProperties properties, JdbcConnectionDetails connectionDetails) { HikariDataSource dataSource(DataSourceProperties properties, JdbcConnectionDetails connectionDetails,
Environment environment) {
String dataSourceClassName = environment.getProperty("spring.datasource.hikari.data-source-class-name");
HikariDataSource dataSource = createDataSource(connectionDetails, HikariDataSource.class, HikariDataSource dataSource = createDataSource(connectionDetails, HikariDataSource.class,
properties.getClassLoader()); properties.getClassLoader(), dataSourceClassName == null);
if (StringUtils.hasText(properties.getName())) { if (StringUtils.hasText(properties.getName())) {
dataSource.setPoolName(properties.getName()); dataSource.setPoolName(properties.getName());
} }

View File

@@ -171,6 +171,15 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB
* @since 1.4.0 * @since 1.4.0
*/ */
public String determineDriverClassName() { public String determineDriverClassName() {
String driverClassName = findDriverClassName();
if (!StringUtils.hasText(driverClassName)) {
throw new DataSourceBeanCreationException("Failed to determine a suitable driver class", this,
this.embeddedDatabaseConnection);
}
return driverClassName;
}
String findDriverClassName() {
if (StringUtils.hasText(this.driverClassName)) { if (StringUtils.hasText(this.driverClassName)) {
Assert.state(driverClassIsLoadable(), () -> "Cannot load driver class: " + this.driverClassName); Assert.state(driverClassIsLoadable(), () -> "Cannot load driver class: " + this.driverClassName);
return this.driverClassName; return this.driverClassName;
@@ -182,10 +191,6 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB
if (!StringUtils.hasText(driverClassName)) { if (!StringUtils.hasText(driverClassName)) {
driverClassName = this.embeddedDatabaseConnection.getDriverClassName(); driverClassName = this.embeddedDatabaseConnection.getDriverClassName();
} }
if (!StringUtils.hasText(driverClassName)) {
throw new DataSourceBeanCreationException("Failed to determine a suitable driver class", this,
this.embeddedDatabaseConnection);
}
return driverClassName; return driverClassName;
} }
@@ -277,7 +282,7 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB
if (StringUtils.hasText(this.username)) { if (StringUtils.hasText(this.username)) {
return this.username; return this.username;
} }
if (EmbeddedDatabaseConnection.isEmbedded(determineDriverClassName(), determineUrl())) { if (EmbeddedDatabaseConnection.isEmbedded(findDriverClassName(), determineUrl())) {
return "sa"; return "sa";
} }
return null; return null;
@@ -305,7 +310,7 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB
if (StringUtils.hasText(this.password)) { if (StringUtils.hasText(this.password)) {
return this.password; return this.password;
} }
if (EmbeddedDatabaseConnection.isEmbedded(determineDriverClassName(), determineUrl())) { if (EmbeddedDatabaseConnection.isEmbedded(findDriverClassName(), determineUrl())) {
return ""; return "";
} }
return null; return null;

View File

@@ -41,7 +41,7 @@ class HikariJdbcConnectionDetailsBeanPostProcessor extends JdbcConnectionDetails
dataSource.setPassword(connectionDetails.getPassword()); dataSource.setPassword(connectionDetails.getPassword());
String driverClassName = connectionDetails.getDriverClassName(); String driverClassName = connectionDetails.getDriverClassName();
if (driverClassName != null) { if (driverClassName != null) {
dataSource.setDriverClassName(connectionDetails.getDriverClassName()); dataSource.setDriverClassName(driverClassName);
} }
return dataSource; return dataSource;
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2023 the original author or authors. * Copyright 2012-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2023 the original author or authors. * Copyright 2012-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,6 +16,12 @@
package org.springframework.boot.autoconfigure.jdbc; package org.springframework.boot.autoconfigure.jdbc;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
import javax.sql.DataSource; import javax.sql.DataSource;
import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.HikariDataSource;
@@ -28,12 +34,15 @@ import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.jdbc.HikariCheckpointRestoreLifecycle; import org.springframework.boot.jdbc.HikariCheckpointRestoreLifecycle;
import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
import org.springframework.boot.testsupport.classpath.ClassPathOverrides; import org.springframework.boot.testsupport.classpath.ClassPathOverrides;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DelegatingDataSource; import org.springframework.jdbc.datasource.DelegatingDataSource;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.mockito.Mockito.mock;
/** /**
* Tests for {@link DataSourceAutoConfiguration} with Hikari. * Tests for {@link DataSourceAutoConfiguration} with Hikari.
@@ -80,7 +89,33 @@ class HikariDataSourceConfigurationTests {
HikariDataSource ds = context.getBean(HikariDataSource.class); HikariDataSource ds = context.getBean(HikariDataSource.class);
assertThat(ds.getDataSourceProperties().getProperty("dataSourceClassName")) assertThat(ds.getDataSourceProperties().getProperty("dataSourceClassName"))
.isEqualTo("org.h2.JDBCDataSource"); .isEqualTo("org.h2.JDBCDataSource");
});
}
@Test
@SuppressWarnings("resource")
@ClassPathExclusions({ "h2-*.jar", "hsqldb-*.jar" })
void configureDataSourceClassNameWithNoEmbeddedDatabaseAvailable() {
this.contextRunner
.withPropertyValues("spring.datasource.url=jdbc:example//",
"spring.datasource.hikari.data-source-class-name=" + MockDataSource.class.getName())
.run((context) -> {
HikariDataSource ds = context.getBean(HikariDataSource.class);
assertThat(ds.getDataSourceClassName()).isEqualTo(MockDataSource.class.getName());
assertThatNoException().isThrownBy(() -> ds.getConnection().close());
});
}
@Test
@SuppressWarnings("resource")
void configureDataSourceClassNameToOverrideUseOfAnEmbeddedDatabase() {
this.contextRunner
.withPropertyValues("spring.datasource.url=jdbc:example//",
"spring.datasource.hikari.data-source-class-name=" + MockDataSource.class.getName())
.run((context) -> {
HikariDataSource ds = context.getBean(HikariDataSource.class);
assertThat(ds.getDataSourceClassName()).isEqualTo(MockDataSource.class.getName());
assertThatNoException().isThrownBy(() -> ds.getConnection().close());
}); });
} }
@@ -201,4 +236,51 @@ class HikariDataSourceConfigurationTests {
} }
public static class MockDataSource implements DataSource {
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
@Override
public Connection getConnection() throws SQLException {
return mock(Connection.class);
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return getConnection();
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return -1;
}
}
} }