From 3e9dddb42c6a09dbdd3085497b3d84ccde5309dc Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Fri, 4 Apr 2025 10:17:51 +0100 Subject: [PATCH] Allow data source class name to be specified when using Hikari Closes gh-44938 --- .../jdbc/DataSourceConfiguration.java | 24 ++++-- .../jdbc/DataSourceProperties.java | 19 +++-- ...dbcConnectionDetailsBeanPostProcessor.java | 2 +- .../jdbc/PropertiesJdbcConnectionDetails.java | 2 +- .../HikariDataSourceConfigurationTests.java | 84 ++++++++++++++++++- 5 files changed, 114 insertions(+), 17 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration.java index 1cebf37cd5e..66f55037da8 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration.java @@ -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"); * you may not use this file except in compliance with the License. @@ -33,6 +33,7 @@ import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.boot.jdbc.DatabaseDriver; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; import org.springframework.util.StringUtils; /** @@ -50,10 +51,17 @@ abstract class DataSourceConfiguration { @SuppressWarnings("unchecked") private static T createDataSource(JdbcConnectionDetails connectionDetails, Class type, ClassLoader classLoader) { - return (T) DataSourceBuilder.create(classLoader) - .type(type) - .driverClassName(connectionDetails.getDriverClassName()) - .url(connectionDetails.getJdbcUrl()) + return createDataSource(connectionDetails, type, classLoader, true); + } + + @SuppressWarnings("unchecked") + private static T createDataSource(JdbcConnectionDetails connectionDetails, Class type, + ClassLoader classLoader, boolean applyDriverClassName) { + DataSourceBuilder builder = DataSourceBuilder.create(classLoader).type(type); + if (applyDriverClassName) { + builder.driverClassName(connectionDetails.getDriverClassName()); + } + return (T) builder.url(connectionDetails.getJdbcUrl()) .username(connectionDetails.getUsername()) .password(connectionDetails.getPassword()) .build(); @@ -113,9 +121,11 @@ abstract class DataSourceConfiguration { @Bean @ConfigurationProperties(prefix = "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, - properties.getClassLoader()); + properties.getClassLoader(), dataSourceClassName == null); if (StringUtils.hasText(properties.getName())) { dataSource.setPoolName(properties.getName()); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java index 1504b86b83e..42c45762cd5 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * 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. @@ -171,6 +171,15 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB * @since 1.4.0 */ 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)) { Assert.state(driverClassIsLoadable(), () -> "Cannot load driver class: " + this.driverClassName); return this.driverClassName; @@ -182,10 +191,6 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB if (!StringUtils.hasText(driverClassName)) { driverClassName = this.embeddedDatabaseConnection.getDriverClassName(); } - if (!StringUtils.hasText(driverClassName)) { - throw new DataSourceBeanCreationException("Failed to determine a suitable driver class", this, - this.embeddedDatabaseConnection); - } return driverClassName; } @@ -277,7 +282,7 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB if (StringUtils.hasText(this.username)) { return this.username; } - if (EmbeddedDatabaseConnection.isEmbedded(determineDriverClassName(), determineUrl())) { + if (EmbeddedDatabaseConnection.isEmbedded(findDriverClassName(), determineUrl())) { return "sa"; } return null; @@ -305,7 +310,7 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB if (StringUtils.hasText(this.password)) { return this.password; } - if (EmbeddedDatabaseConnection.isEmbedded(determineDriverClassName(), determineUrl())) { + if (EmbeddedDatabaseConnection.isEmbedded(findDriverClassName(), determineUrl())) { return ""; } return null; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/HikariJdbcConnectionDetailsBeanPostProcessor.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/HikariJdbcConnectionDetailsBeanPostProcessor.java index 2179f0ba97b..3f05a8ffb8f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/HikariJdbcConnectionDetailsBeanPostProcessor.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/HikariJdbcConnectionDetailsBeanPostProcessor.java @@ -41,7 +41,7 @@ class HikariJdbcConnectionDetailsBeanPostProcessor extends JdbcConnectionDetails dataSource.setPassword(connectionDetails.getPassword()); String driverClassName = connectionDetails.getDriverClassName(); if (driverClassName != null) { - dataSource.setDriverClassName(connectionDetails.getDriverClassName()); + dataSource.setDriverClassName(driverClassName); } return dataSource; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/PropertiesJdbcConnectionDetails.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/PropertiesJdbcConnectionDetails.java index 40109d89aa9..6310e8d8f3c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/PropertiesJdbcConnectionDetails.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/PropertiesJdbcConnectionDetails.java @@ -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"); * you may not use this file except in compliance with the License. diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/HikariDataSourceConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/HikariDataSourceConfigurationTests.java index 3019be54375..4841e2a557e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/HikariDataSourceConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/HikariDataSourceConfigurationTests.java @@ -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"); * you may not use this file except in compliance with the License. @@ -16,6 +16,12 @@ 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 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.HikariCheckpointRestoreLifecycle; 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.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.datasource.DelegatingDataSource; 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. @@ -80,7 +89,33 @@ class HikariDataSourceConfigurationTests { HikariDataSource ds = context.getBean(HikariDataSource.class); assertThat(ds.getDataSourceProperties().getProperty("dataSourceClassName")) .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 unwrap(Class 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; + } + + } + }