From a21ab392b635b1c95b39d12d8c18d21bccbda4d8 Mon Sep 17 00:00:00 2001 From: Fabio Grassi Date: Thu, 17 Sep 2020 23:00:16 +0200 Subject: [PATCH 1/2] Add support for Oracle UCP See gh-23403 --- .../spring-boot-autoconfigure/build.gradle | 1 + .../jdbc/DataSourceAutoConfiguration.java | 5 +- .../jdbc/DataSourceConfiguration.java | 33 ++++++ ...rcePoolMetadataProvidersConfiguration.java | 20 ++++ .../jdbc/UcpDataSourceConfigurationTests.java | 105 ++++++++++++++++++ spring-boot-project/spring-boot/build.gradle | 3 +- .../boot/jdbc/DataSourceBuilder.java | 19 ++-- .../metadata/UcpDataSourcePoolMetadata.java | 81 ++++++++++++++ .../boot/jdbc/DataSourceBuilderTests.java | 26 ++++- .../UcpDataSourcePoolMetadataTests.java | 76 +++++++++++++ 10 files changed, 355 insertions(+), 14 deletions(-) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/UcpDataSourceConfigurationTests.java create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/metadata/UcpDataSourcePoolMetadata.java create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/metadata/UcpDataSourcePoolMetadataTests.java diff --git a/spring-boot-project/spring-boot-autoconfigure/build.gradle b/spring-boot-project/spring-boot-autoconfigure/build.gradle index decb94b99b..bcbe04aead 100644 --- a/spring-boot-project/spring-boot-autoconfigure/build.gradle +++ b/spring-boot-project/spring-boot-autoconfigure/build.gradle @@ -64,6 +64,7 @@ dependencies { optional("org.codehaus.groovy:groovy-templates") optional("com.github.ben-manes.caffeine:caffeine") optional("com.github.mxab.thymeleaf.extras:thymeleaf-extras-data-attribute") + optional("com.oracle.database.jdbc:ucp") optional("com.sendgrid:sendgrid-java") optional("com.unboundid:unboundid-ldapsdk") optional("com.zaxxer:HikariCP") 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 8e4f1fb0a8..53086b0ffb 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 @@ -48,6 +48,7 @@ import org.springframework.util.StringUtils; * @author Phillip Webb * @author Stephane Nicoll * @author Kazuki Shimizu + * @author Fabio Grassi * @since 1.0.0 */ @Configuration(proxyBeanMethods = false) @@ -69,8 +70,8 @@ public class DataSourceAutoConfiguration { @Conditional(PooledDataSourceCondition.class) @ConditionalOnMissingBean({ DataSource.class, XADataSource.class }) @Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class, - DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.Generic.class, - DataSourceJmxConfiguration.class }) + DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.Ucp.class, + DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class }) protected static class PooledDataSourceConfiguration { } 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 d94e9d3ec1..cbf96da469 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 @@ -16,9 +16,13 @@ package org.springframework.boot.autoconfigure.jdbc; +import java.sql.SQLException; + import javax.sql.DataSource; import com.zaxxer.hikari.HikariDataSource; +import oracle.ucp.jdbc.PoolDataSource; +import oracle.ucp.jdbc.PoolDataSourceImpl; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -27,6 +31,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DatabaseDriver; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.util.StringUtils; /** @@ -35,6 +40,7 @@ import org.springframework.util.StringUtils; * @author Dave Syer * @author Phillip Webb * @author Stephane Nicoll + * @author Fabio Grassi */ abstract class DataSourceConfiguration { @@ -109,6 +115,33 @@ abstract class DataSourceConfiguration { } + /** + * UCP DataSource configuration. + */ + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(PoolDataSource.class) + @ConditionalOnMissingBean(DataSource.class) + @ConditionalOnProperty(name = "spring.datasource.type", havingValue = "oracle.ucp.jdbc.PoolDataSource", + matchIfMissing = true) + static class Ucp { + + @Bean + @ConfigurationProperties(prefix = "spring.datasource.ucp") + PoolDataSource dataSource(DataSourceProperties properties) { + PoolDataSource dataSource = createDataSource(properties, PoolDataSourceImpl.class); + if (StringUtils.hasText(properties.getName())) { + try { + dataSource.setConnectionPoolName(properties.getName()); + } + catch (SQLException se) { + throw new InvalidDataAccessApiUsageException("Error setting property connectionPoolName", se); + } + } + return dataSource; + } + + } + /** * Generic DataSource configuration. */ diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/metadata/DataSourcePoolMetadataProvidersConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/metadata/DataSourcePoolMetadataProvidersConfiguration.java index d34a183bfb..ca12b29c0e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/metadata/DataSourcePoolMetadataProvidersConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/metadata/DataSourcePoolMetadataProvidersConfiguration.java @@ -17,6 +17,7 @@ package org.springframework.boot.autoconfigure.jdbc.metadata; import com.zaxxer.hikari.HikariDataSource; +import oracle.ucp.jdbc.PoolDataSource; import org.apache.commons.dbcp2.BasicDataSource; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -25,6 +26,7 @@ import org.springframework.boot.jdbc.metadata.CommonsDbcp2DataSourcePoolMetadata import org.springframework.boot.jdbc.metadata.DataSourcePoolMetadataProvider; import org.springframework.boot.jdbc.metadata.HikariDataSourcePoolMetadata; import org.springframework.boot.jdbc.metadata.TomcatDataSourcePoolMetadata; +import org.springframework.boot.jdbc.metadata.UcpDataSourcePoolMetadata; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -33,6 +35,7 @@ import org.springframework.context.annotation.Configuration; * sources. * * @author Stephane Nicoll + * @author Fabio Grassi * @since 1.2.0 */ @Configuration(proxyBeanMethods = false) @@ -90,4 +93,21 @@ public class DataSourcePoolMetadataProvidersConfiguration { } + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(PoolDataSource.class) + static class UcpPoolDataSourceMetadataProviderConfiguration { + + @Bean + DataSourcePoolMetadataProvider UcpPoolDataSourceMetadataProvider() { + return (dataSource) -> { + PoolDataSource ucpDataSource = DataSourceUnwrapper.unwrap(dataSource, PoolDataSource.class); + if (ucpDataSource != null) { + return new UcpDataSourcePoolMetadata(ucpDataSource); + } + return null; + }; + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/UcpDataSourceConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/UcpDataSourceConfigurationTests.java new file mode 100644 index 0000000000..a329908d7f --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/UcpDataSourceConfigurationTests.java @@ -0,0 +1,105 @@ +/* + * Copyright 2012-2020 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 static org.assertj.core.api.Assertions.assertThat; + +import javax.sql.DataSource; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +import oracle.ucp.jdbc.PoolDataSource; +import oracle.ucp.jdbc.PoolDataSourceImpl; + +/** + * Tests for {@link DataSourceAutoConfiguration} with Oracle UCP. + * + * @author Fabio Grassi + */ +class UcpDataSourceConfigurationTests { + + private static final String PREFIX = "spring.datasource.ucp."; + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class)) + .withPropertyValues("spring.datasource.initialization-mode=never", + "spring.datasource.type=" + PoolDataSource.class.getName()); + + @Test + void testDataSourceExists() { + this.contextRunner.run((context) -> { + assertThat(context.getBeansOfType(DataSource.class)).hasSize(1); + assertThat(context.getBeansOfType(PoolDataSourceImpl.class)).hasSize(1); + }); + } + + @Test + void testDataSourcePropertiesOverridden() { + this.contextRunner.withPropertyValues(PREFIX + "URL=jdbc:foo//bar/spam", PREFIX + "maxIdleTime=1234") + .run((context) -> { + PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class); + assertThat(ds.getURL()).isEqualTo("jdbc:foo//bar/spam"); + assertThat(ds.getMaxIdleTime()).isEqualTo(1234); + }); + } + + @Test + void testDataSourceConnectionPropertiesOverridden() { + this.contextRunner.withPropertyValues(PREFIX + "connectionProperties.autoCommit=false").run((context) -> { + PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class); + assertThat(ds.getConnectionProperty("autoCommit")).isEqualTo("false"); + + }); + } + + @Test + void testDataSourceDefaultsPreserved() { + this.contextRunner.run((context) -> { + PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class); + assertThat(ds.getInitialPoolSize()).isEqualTo(0); + assertThat(ds.getMinPoolSize()).isEqualTo(0); + assertThat(ds.getMaxPoolSize()).isEqualTo(Integer.MAX_VALUE); + assertThat(ds.getInactiveConnectionTimeout()).isEqualTo(0); + assertThat(ds.getConnectionWaitTimeout()).isEqualTo(3); + assertThat(ds.getTimeToLiveConnectionTimeout()).isEqualTo(0); + assertThat(ds.getAbandonedConnectionTimeout()).isEqualTo(0); + assertThat(ds.getTimeoutCheckInterval()).isEqualTo(30); + assertThat(ds.getFastConnectionFailoverEnabled()).isFalse(); + }); + } + + @Test + void nameIsAliasedToPoolName() { + this.contextRunner.withPropertyValues("spring.datasource.name=myDS").run((context) -> { + PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class); + assertThat(ds.getConnectionPoolName()).isEqualTo("myDS"); + + }); + } + + @Test + void connectionPoolNameTakesPrecedenceOverName() { + this.contextRunner.withPropertyValues("spring.datasource.name=myDS", PREFIX + "connectionPoolName=myUcpDS") + .run((context) -> { + PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class); + assertThat(ds.getConnectionPoolName()).isEqualTo("myUcpDS"); + }); + } + +} diff --git a/spring-boot-project/spring-boot/build.gradle b/spring-boot-project/spring-boot/build.gradle index 1db702c445..90d6bc9dbb 100644 --- a/spring-boot-project/spring-boot/build.gradle +++ b/spring-boot-project/spring-boot/build.gradle @@ -21,6 +21,7 @@ dependencies { optional("com.atomikos:transactions-jta") optional("com.fasterxml.jackson.core:jackson-databind") optional("com.google.code.gson:gson") + optional("com.oracle.database.jdbc:ucp") optional("com.samskivert:jmustache") optional("com.zaxxer:HikariCP") optional("io.netty:netty-tcnative-boringssl-static") @@ -77,7 +78,7 @@ dependencies { testImplementation("com.ibm.db2:jcc") testImplementation("com.jayway.jsonpath:json-path") testImplementation("com.microsoft.sqlserver:mssql-jdbc") - testImplementation("com.oracle.ojdbc:ojdbc8") + testImplementation("com.oracle.database.jdbc:ojdbc8") testImplementation("com.squareup.okhttp3:okhttp") testImplementation("com.sun.xml.messaging.saaj:saaj-impl") testImplementation("io.projectreactor:reactor-test") 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 9f5f2e698e..9bf1a3b388 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 @@ -37,16 +37,17 @@ import org.springframework.util.ClassUtils; /** * Convenience class for building a {@link DataSource} with common implementations and - * properties. If HikariCP, Tomcat or Commons DBCP are on the classpath one of them will - * be selected (in that order with Hikari first). In the interest of a uniform interface, - * and so that there can be a fallback to an embedded database if one can be detected on - * the classpath, only a small set of common configuration properties are supported. To - * inject additional properties into the result you can downcast it, or use + * properties. If HikariCP, Tomcat, Commons DBCP or Oracle UCP are on the classpath one of + * them will be selected (in that order with Hikari first). In the interest of a uniform + * interface, and so that there can be a fallback to an embedded database if one can be + * detected on the classpath, only a small set of common configuration properties are + * supported. To inject additional properties into the result you can downcast it, or use * {@code @ConfigurationProperties}. * * @param type of DataSource produced by the builder * @author Dave Syer * @author Madhura Bhave + * @author Fabio Grassi * @since 2.0.0 */ public final class DataSourceBuilder { @@ -166,9 +167,9 @@ public final class DataSourceBuilder { } - private static class OracleDataSourceSettings extends DataSourceSettings { + private static class OracleCommonDataSourceSettings extends DataSourceSettings { - OracleDataSourceSettings(Class type) { + OracleCommonDataSourceSettings(Class type) { super(type, (aliases) -> aliases.addAliases("username", "user")); } @@ -194,7 +195,7 @@ public final class DataSourceBuilder { (type) -> new DataSourceSettings(type, (aliases) -> aliases.addAliases("driver-class-name", "driver-class")))); addIfAvailable(this.allDataSourceSettings, create(classLoader, - "oracle.jdbc.datasource.OracleCommonDataSource", OracleDataSourceSettings::new)); + "oracle.jdbc.datasource.OracleCommonDataSource", OracleCommonDataSourceSettings::new)); } private static List resolveAvailableDataSourceSettings(ClassLoader classLoader) { @@ -205,6 +206,8 @@ public final class DataSourceBuilder { create(classLoader, "org.apache.tomcat.jdbc.pool.DataSource", DataSourceSettings::new)); addIfAvailable(providers, create(classLoader, "org.apache.commons.dbcp2.BasicDataSource", DataSourceSettings::new)); + addIfAvailable(providers, + create(classLoader, "oracle.ucp.jdbc.PoolDataSourceImpl", DataSourceSettings::new)); return providers; } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/metadata/UcpDataSourcePoolMetadata.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/metadata/UcpDataSourcePoolMetadata.java new file mode 100644 index 0000000000..d2f24bd185 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/metadata/UcpDataSourcePoolMetadata.java @@ -0,0 +1,81 @@ +/* + * Copyright 2012-2019 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.jdbc.metadata; + +import java.sql.SQLException; + +import javax.sql.DataSource; + +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.util.StringUtils; + +import oracle.ucp.jdbc.PoolDataSource; + +/** + * {@link DataSourcePoolMetadata} for a Oracle UCP {@link DataSource}. + * + * @author Fabio Grassi + * @since 2.3.4 + */ +public class UcpDataSourcePoolMetadata extends AbstractDataSourcePoolMetadata { + + public UcpDataSourcePoolMetadata(PoolDataSource dataSource) { + super(dataSource); + } + + @Override + public Integer getActive() { + try { + return getDataSource().getBorrowedConnectionsCount(); + } + catch (SQLException se) { + throw new InvalidDataAccessApiUsageException("Error while reading property borrowedConnectionsCount", se); + } + } + + @Override + public Integer getIdle() { + try { + return getDataSource().getAvailableConnectionsCount(); + } + catch (SQLException se) { + throw new InvalidDataAccessApiUsageException("Error while reading property availableConnectionsCount", se); + } + } + + @Override + public Integer getMax() { + return getDataSource().getMaxPoolSize(); + } + + @Override + public Integer getMin() { + return getDataSource().getMinPoolSize(); + } + + @Override + public String getValidationQuery() { + return getDataSource().getSQLForValidateConnection(); + } + + @Override + public Boolean getDefaultAutoCommit() { + String ac = getDataSource().getConnectionProperty("autoCommit"); + return StringUtils.hasText(ac) ? Boolean.valueOf(ac) : null; + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/DataSourceBuilderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/DataSourceBuilderTests.java index 779a02be42..8e59a40a30 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/DataSourceBuilderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/DataSourceBuilderTests.java @@ -16,6 +16,8 @@ package org.springframework.boot.jdbc; +import static org.assertj.core.api.Assertions.assertThat; + import java.io.Closeable; import java.io.IOException; import java.net.URL; @@ -31,15 +33,17 @@ import org.apache.commons.dbcp2.BasicDataSource; import org.h2.Driver; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; - import org.springframework.jdbc.datasource.SimpleDriverDataSource; -import static org.assertj.core.api.Assertions.assertThat; +import com.zaxxer.hikari.HikariDataSource; + +import oracle.ucp.jdbc.PoolDataSourceImpl; /** * Tests for {@link DataSourceBuilder}. * * @author Stephane Nicoll + * @author Fabio Grassi */ class DataSourceBuilderTests { @@ -68,13 +72,20 @@ class DataSourceBuilderTests { } @Test - void defaultToCommonsDbcp2AsLastResort() { + void defaultToCommonsDbcp2IfNeitherHikariNorTomcatIsNotAvailable() { this.dataSource = DataSourceBuilder .create(new HidePackagesClassLoader("com.zaxxer.hikari", "org.apache.tomcat.jdbc.pool")) .url("jdbc:h2:test").build(); assertThat(this.dataSource).isInstanceOf(BasicDataSource.class); } + @Test + void defaultToUcpAsLastResort() { + this.dataSource = DataSourceBuilder.create(new HidePackagesClassLoader("com.zaxxer.hikari", + "org.apache.tomcat.jdbc.pool", "org.apache.commons.dbcp2")).url("jdbc:h2:test").build(); + assertThat(this.dataSource).isInstanceOf(PoolDataSourceImpl.class); + } + @Test void specificTypeOfDataSource() { HikariDataSource hikariDataSource = DataSourceBuilder.create().type(HikariDataSource.class).build(); @@ -114,6 +125,15 @@ class DataSourceBuilderTests { assertThat(testDataSource.getDriverClass()).isNull(); } + @Test + void UcpCanBeCreatedByDriverClassNamee() { + this.dataSource = DataSourceBuilder.create().driverClassName("org.hsqldb.jdbc.JDBCDriver") + .type(PoolDataSourceImpl.class).build(); + assertThat(this.dataSource).isInstanceOf(PoolDataSourceImpl.class); + assertThat(((PoolDataSourceImpl) this.dataSource).getConnectionFactoryClassName()) + .isEqualTo("org.hsqldb.jdbc.JDBCDriver"); + } + final class HidePackagesClassLoader extends URLClassLoader { private final String[] hiddenPackages; diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/metadata/UcpDataSourcePoolMetadataTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/metadata/UcpDataSourcePoolMetadataTests.java new file mode 100644 index 0000000000..5774427231 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/metadata/UcpDataSourcePoolMetadataTests.java @@ -0,0 +1,76 @@ +/* + * Copyright 2012-2019 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.jdbc.metadata; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.sql.SQLException; + +import org.springframework.dao.InvalidDataAccessApiUsageException; + +import oracle.ucp.jdbc.PoolDataSource; +import oracle.ucp.jdbc.PoolDataSourceImpl; + +/** + * Tests for {@link UcpDataSourcePoolMetadata}. + * + * @author Fabio Grassi + */ +public class UcpDataSourcePoolMetadataTests extends AbstractDataSourcePoolMetadataTests { + + private final UcpDataSourcePoolMetadata dataSourceMetadata = new UcpDataSourcePoolMetadata(createDataSource(0, 2)); + + @Override + protected UcpDataSourcePoolMetadata getDataSourceMetadata() { + return this.dataSourceMetadata; + } + + @Override + public void getValidationQuery() { + PoolDataSource dataSource = createDataSource(0, 4); + try { + dataSource.setSQLForValidateConnection("SELECT NULL FROM DUAL"); + } catch (SQLException se) { + throw new InvalidDataAccessApiUsageException("Error while setting property SQLForValidateConnection", se); + } + assertThat(new UcpDataSourcePoolMetadata(dataSource).getValidationQuery()).isEqualTo("SELECT NULL FROM DUAL"); + } + + @Override + public void getDefaultAutoCommit() { + PoolDataSource dataSource = createDataSource(0, 4); + try { + dataSource.setConnectionProperty("autoCommit", "false"); + } catch (SQLException se) { + throw new InvalidDataAccessApiUsageException("Error while setting property connectionProperties.autoCommit", se); + } + assertThat(new UcpDataSourcePoolMetadata(dataSource).getDefaultAutoCommit()).isFalse(); + } + + private PoolDataSource createDataSource(int minSize, int maxSize) { + PoolDataSource dataSource = initializeBuilder().type(PoolDataSourceImpl.class).build(); + try { + dataSource.setInitialPoolSize(minSize); + dataSource.setMinPoolSize(minSize); + dataSource.setMaxPoolSize(maxSize); + } catch (SQLException se) { + throw new InvalidDataAccessApiUsageException("Error while setting a property", se); + } + return dataSource; + } + +} From 75554ce236dead9ed5e55fd2408af97b7a8bc686 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Thu, 24 Sep 2020 16:47:27 +0200 Subject: [PATCH 2/2] Polish "Add support for Oracle UCP" See gh-23403 --- .../DocumentConfigurationProperties.java | 2 + .../spring-boot-autoconfigure/build.gradle | 3 +- .../jdbc/DataSourceAutoConfiguration.java | 3 +- .../jdbc/DataSourceConfiguration.java | 25 +++--- ...rcePoolMetadataProvidersConfiguration.java | 13 ++-- .../DataSourceAutoConfigurationTests.java | 26 ++++++- ...racleUcpDataSourceConfigurationTests.java} | 39 +++++----- .../LiquibaseAutoConfigurationTests.java | 4 +- .../docs/asciidoc/spring-boot-features.adoc | 5 +- .../boot/jdbc/DataSourceBuilder.java | 12 ++- ...a => OracleUcpDataSourcePoolMetadata.java} | 27 ++++--- .../boot/jdbc/DataSourceBuilderTests.java | 29 ++++--- .../AbstractDataSourcePoolMetadataTests.java | 6 +- .../OracleUcpDataSourcePoolMetadataTests.java | 70 +++++++++++++++++ .../UcpDataSourcePoolMetadataTests.java | 76 ------------------- 15 files changed, 180 insertions(+), 160 deletions(-) rename spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/{UcpDataSourceConfigurationTests.java => OracleUcpDataSourceConfigurationTests.java} (78%) rename spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/metadata/{UcpDataSourcePoolMetadata.java => OracleUcpDataSourcePoolMetadata.java} (64%) create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/metadata/OracleUcpDataSourcePoolMetadataTests.java delete mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/metadata/UcpDataSourcePoolMetadataTests.java diff --git a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/DocumentConfigurationProperties.java b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/DocumentConfigurationProperties.java index ac6e3b7b83..e63cf80b6c 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/DocumentConfigurationProperties.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/DocumentConfigurationProperties.java @@ -82,6 +82,8 @@ public class DocumentConfigurationProperties extends DefaultTask { .withKeyPrefixes("spring.couchbase", "spring.elasticsearch", "spring.h2", "spring.influx", "spring.mongodb", "spring.neo4j", "spring.redis", "spring.dao", "spring.data", "spring.datasource", "spring.jooq", "spring.jdbc", "spring.jpa", "spring.r2dbc") + .addOverride("spring.datasource.oracleucp", + "Oracle UCP specific settings bound to an instance of Oracle UCP's PoolDataSource") .addOverride("spring.datasource.dbcp2", "Commons DBCP2 specific settings bound to an instance of DBCP2's BasicDataSource") .addOverride("spring.datasource.tomcat", diff --git a/spring-boot-project/spring-boot-autoconfigure/build.gradle b/spring-boot-project/spring-boot-autoconfigure/build.gradle index bcbe04aead..b20e4d383e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/build.gradle +++ b/spring-boot-project/spring-boot-autoconfigure/build.gradle @@ -24,6 +24,8 @@ dependencies { optional("com.hazelcast:hazelcast-spring") optional("com.h2database:h2") optional("com.nimbusds:oauth2-oidc-sdk") + optional("com.oracle.database.jdbc:ojdbc8") + optional("com.oracle.database.jdbc:ucp") optional("com.samskivert:jmustache") optional("com.sun.mail:jakarta.mail") optional("de.flapdoodle.embed:de.flapdoodle.embed.mongo") @@ -64,7 +66,6 @@ dependencies { optional("org.codehaus.groovy:groovy-templates") optional("com.github.ben-manes.caffeine:caffeine") optional("com.github.mxab.thymeleaf.extras:thymeleaf-extras-data-attribute") - optional("com.oracle.database.jdbc:ucp") optional("com.sendgrid:sendgrid-java") optional("com.unboundid:unboundid-ldapsdk") optional("com.zaxxer:HikariCP") 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 53086b0ffb..d3cb0a138e 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 @@ -48,7 +48,6 @@ import org.springframework.util.StringUtils; * @author Phillip Webb * @author Stephane Nicoll * @author Kazuki Shimizu - * @author Fabio Grassi * @since 1.0.0 */ @Configuration(proxyBeanMethods = false) @@ -70,7 +69,7 @@ public class DataSourceAutoConfiguration { @Conditional(PooledDataSourceCondition.class) @ConditionalOnMissingBean({ DataSource.class, XADataSource.class }) @Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class, - DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.Ucp.class, + DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class, DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class }) protected static class PooledDataSourceConfiguration { 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 cbf96da469..6dc95e5820 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-2019 the original author or authors. + * Copyright 2012-2020 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. @@ -21,7 +21,7 @@ import java.sql.SQLException; import javax.sql.DataSource; import com.zaxxer.hikari.HikariDataSource; -import oracle.ucp.jdbc.PoolDataSource; +import oracle.jdbc.OracleConnection; import oracle.ucp.jdbc.PoolDataSourceImpl; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -31,7 +31,6 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DatabaseDriver; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.util.StringUtils; /** @@ -116,26 +115,22 @@ abstract class DataSourceConfiguration { } /** - * UCP DataSource configuration. + * Oracle UCP DataSource configuration. */ @Configuration(proxyBeanMethods = false) - @ConditionalOnClass(PoolDataSource.class) + @ConditionalOnClass({ PoolDataSourceImpl.class, OracleConnection.class }) @ConditionalOnMissingBean(DataSource.class) @ConditionalOnProperty(name = "spring.datasource.type", havingValue = "oracle.ucp.jdbc.PoolDataSource", matchIfMissing = true) - static class Ucp { + static class OracleUcp { @Bean - @ConfigurationProperties(prefix = "spring.datasource.ucp") - PoolDataSource dataSource(DataSourceProperties properties) { - PoolDataSource dataSource = createDataSource(properties, PoolDataSourceImpl.class); + @ConfigurationProperties(prefix = "spring.datasource.oracleucp") + PoolDataSourceImpl dataSource(DataSourceProperties properties) throws SQLException { + PoolDataSourceImpl dataSource = createDataSource(properties, PoolDataSourceImpl.class); + dataSource.setValidateConnectionOnBorrow(true); if (StringUtils.hasText(properties.getName())) { - try { - dataSource.setConnectionPoolName(properties.getName()); - } - catch (SQLException se) { - throw new InvalidDataAccessApiUsageException("Error setting property connectionPoolName", se); - } + dataSource.setConnectionPoolName(properties.getName()); } return dataSource; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/metadata/DataSourcePoolMetadataProvidersConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/metadata/DataSourcePoolMetadataProvidersConfiguration.java index ca12b29c0e..44e73ad28a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/metadata/DataSourcePoolMetadataProvidersConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/metadata/DataSourcePoolMetadataProvidersConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 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. @@ -17,6 +17,7 @@ package org.springframework.boot.autoconfigure.jdbc.metadata; import com.zaxxer.hikari.HikariDataSource; +import oracle.jdbc.OracleConnection; import oracle.ucp.jdbc.PoolDataSource; import org.apache.commons.dbcp2.BasicDataSource; @@ -25,8 +26,8 @@ import org.springframework.boot.jdbc.DataSourceUnwrapper; import org.springframework.boot.jdbc.metadata.CommonsDbcp2DataSourcePoolMetadata; import org.springframework.boot.jdbc.metadata.DataSourcePoolMetadataProvider; import org.springframework.boot.jdbc.metadata.HikariDataSourcePoolMetadata; +import org.springframework.boot.jdbc.metadata.OracleUcpDataSourcePoolMetadata; import org.springframework.boot.jdbc.metadata.TomcatDataSourcePoolMetadata; -import org.springframework.boot.jdbc.metadata.UcpDataSourcePoolMetadata; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -94,15 +95,15 @@ public class DataSourcePoolMetadataProvidersConfiguration { } @Configuration(proxyBeanMethods = false) - @ConditionalOnClass(PoolDataSource.class) - static class UcpPoolDataSourceMetadataProviderConfiguration { + @ConditionalOnClass({ PoolDataSource.class, OracleConnection.class }) + static class OracleUcpPoolDataSourceMetadataProviderConfiguration { @Bean - DataSourcePoolMetadataProvider UcpPoolDataSourceMetadataProvider() { + DataSourcePoolMetadataProvider oracleUcpPoolDataSourceMetadataProvider() { return (dataSource) -> { PoolDataSource ucpDataSource = DataSourceUnwrapper.unwrap(dataSource, PoolDataSource.class); if (ucpDataSource != null) { - return new UcpDataSourcePoolMetadata(ucpDataSource); + return new OracleUcpDataSourcePoolMetadata(ucpDataSource); } return null; }; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfigurationTests.java index 8d4bf2945e..4c0fd37f75 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfigurationTests.java @@ -34,6 +34,7 @@ import javax.sql.DataSource; import com.zaxxer.hikari.HikariDataSource; import io.r2dbc.spi.ConnectionFactory; +import oracle.ucp.jdbc.PoolDataSourceImpl; import org.apache.commons.dbcp2.BasicDataSource; import org.junit.jupiter.api.Test; @@ -135,8 +136,25 @@ class DataSourceAutoConfigurationTests { assertDataSource(org.apache.commons.dbcp2.BasicDataSource.class, Arrays.asList("com.zaxxer.hikari", "org.apache.tomcat"), (dataSource) -> { assertThat(dataSource.getTestOnBorrow()).isTrue(); - assertThat(dataSource.getValidationQuery()).isNull(); // Use - // Connection#isValid() + // Use Connection#isValid() + assertThat(dataSource.getValidationQuery()).isNull(); + }); + } + + @Test + void oracleUcpIsFallback() { + assertDataSource(PoolDataSourceImpl.class, + Arrays.asList("com.zaxxer.hikari", "org.apache.tomcat", "org.apache.commons.dbcp2"), + (dataSource) -> assertThat(dataSource.getURL()).startsWith("jdbc:hsqldb:mem:testdb")); + } + + @Test + void oracleUcpValidatesConnectionByDefault() { + assertDataSource(PoolDataSourceImpl.class, + Arrays.asList("com.zaxxer.hikari", "org.apache.tomcat", "org.apache.commons.dbcp2"), (dataSource) -> { + assertThat(dataSource.getValidateConnectionOnBorrow()).isTrue(); + // Use an internal ping when using an Oracle JDBC driver + assertThat(dataSource.getSQLForValidateConnection()).isNull(); }); } @@ -225,8 +243,8 @@ class DataSourceAutoConfigurationTests { } private static Function hideConnectionPools() { - return (runner) -> runner.withClassLoader( - new FilteredClassLoader("org.apache.tomcat", "com.zaxxer.hikari", "org.apache.commons.dbcp2")); + return (runner) -> runner.withClassLoader(new FilteredClassLoader("org.apache.tomcat", "com.zaxxer.hikari", + "org.apache.commons.dbcp2", "oracle.ucp.jdbc")); } private void assertDataSource(Class expectedType, List hiddenPackages, diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/UcpDataSourceConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/OracleUcpDataSourceConfigurationTests.java similarity index 78% rename from spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/UcpDataSourceConfigurationTests.java rename to spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/OracleUcpDataSourceConfigurationTests.java index a329908d7f..93df579d57 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/UcpDataSourceConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/OracleUcpDataSourceConfigurationTests.java @@ -16,25 +16,26 @@ package org.springframework.boot.autoconfigure.jdbc; -import static org.assertj.core.api.Assertions.assertThat; +import java.sql.Connection; import javax.sql.DataSource; +import oracle.ucp.jdbc.PoolDataSource; +import oracle.ucp.jdbc.PoolDataSourceImpl; import org.junit.jupiter.api.Test; + import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import oracle.ucp.jdbc.PoolDataSource; -import oracle.ucp.jdbc.PoolDataSourceImpl; +import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link DataSourceAutoConfiguration} with Oracle UCP. * * @author Fabio Grassi + * @author Stephane Nicoll */ -class UcpDataSourceConfigurationTests { - - private static final String PREFIX = "spring.datasource.ucp."; +class OracleUcpDataSourceConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class)) @@ -46,13 +47,16 @@ class UcpDataSourceConfigurationTests { this.contextRunner.run((context) -> { assertThat(context.getBeansOfType(DataSource.class)).hasSize(1); assertThat(context.getBeansOfType(PoolDataSourceImpl.class)).hasSize(1); + try (Connection connection = context.getBean(DataSource.class).getConnection()) { + assertThat(connection.isValid(1000)).isTrue(); + } }); } @Test void testDataSourcePropertiesOverridden() { - this.contextRunner.withPropertyValues(PREFIX + "URL=jdbc:foo//bar/spam", PREFIX + "maxIdleTime=1234") - .run((context) -> { + this.contextRunner.withPropertyValues("spring.datasource.oracleucp.url=jdbc:foo//bar/spam", + "spring.datasource.oracleucp.max-idle-time=1234").run((context) -> { PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class); assertThat(ds.getURL()).isEqualTo("jdbc:foo//bar/spam"); assertThat(ds.getMaxIdleTime()).isEqualTo(1234); @@ -61,11 +65,11 @@ class UcpDataSourceConfigurationTests { @Test void testDataSourceConnectionPropertiesOverridden() { - this.contextRunner.withPropertyValues(PREFIX + "connectionProperties.autoCommit=false").run((context) -> { - PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class); - assertThat(ds.getConnectionProperty("autoCommit")).isEqualTo("false"); - - }); + this.contextRunner.withPropertyValues("spring.datasource.oracleucp.connection-properties.autoCommit=false") + .run((context) -> { + PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class); + assertThat(ds.getConnectionProperty("autoCommit")).isEqualTo("false"); + }); } @Test @@ -89,16 +93,15 @@ class UcpDataSourceConfigurationTests { this.contextRunner.withPropertyValues("spring.datasource.name=myDS").run((context) -> { PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class); assertThat(ds.getConnectionPoolName()).isEqualTo("myDS"); - }); } @Test - void connectionPoolNameTakesPrecedenceOverName() { - this.contextRunner.withPropertyValues("spring.datasource.name=myDS", PREFIX + "connectionPoolName=myUcpDS") - .run((context) -> { + void poolNameTakesPrecedenceOverName() { + this.contextRunner.withPropertyValues("spring.datasource.name=myDS", + "spring.datasource.oracleucp.connection-pool-name=myOracleUcpDS").run((context) -> { PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class); - assertThat(ds.getConnectionPoolName()).isEqualTo("myUcpDS"); + assertThat(ds.getConnectionPoolName()).isEqualTo("myOracleUcpDS"); }); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java index b4596651e9..d8190d03c9 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java @@ -105,8 +105,8 @@ class LiquibaseAutoConfigurationTests { @Test void createsDataSourceWhenSpringJdbcOnlyAvailableWithNoDataSourceBeanAndLiquibaseUrl() { this.contextRunner.withPropertyValues("spring.liquibase.url:jdbc:hsqldb:mem:liquibase") - .withClassLoader( - new FilteredClassLoader("org.apache.tomcat", "com.zaxxer.hikari", "org.apache.commons.dbcp2")) + .withClassLoader(new FilteredClassLoader("org.apache.tomcat", "com.zaxxer.hikari", + "org.apache.commons.dbcp2", "oracle.ucp.jdbc")) .run(assertLiquibase((liquibase) -> { DataSource dataSource = liquibase.getDataSource(); assertThat(dataSource).isInstanceOf(SimpleDriverDataSource.class); diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc index fa248d61d2..384f227bc6 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc @@ -3826,7 +3826,8 @@ Spring Boot uses the following algorithm for choosing a specific implementation: . We prefer https://github.com/brettwooldridge/HikariCP[HikariCP] for its performance and concurrency. If HikariCP is available, we always choose it. . Otherwise, if the Tomcat pooling `DataSource` is available, we use it. -. If neither HikariCP nor the Tomcat pooling datasource are available and if https://commons.apache.org/proper/commons-dbcp/[Commons DBCP2] is available, we use it. +. Otherwise, if https://commons.apache.org/proper/commons-dbcp/[Commons DBCP2] is available, we use it. +. If none of HikariCP, Tomcat, and DBCP2 are available and if Oracle UCP is available, we use it. If you use the `spring-boot-starter-jdbc` or `spring-boot-starter-data-jpa` "`starters`", you automatically get a dependency to `HikariCP`. @@ -3857,7 +3858,7 @@ In other words, if you set `spring.datasource.driver-class-name=com.mysql.jdbc.D See {spring-boot-autoconfigure-module-code}/jdbc/DataSourceProperties.java[`DataSourceProperties`] for more of the supported options. These are the standard options that work regardless of the actual implementation. -It is also possible to fine-tune implementation-specific settings by using their respective prefix (`+spring.datasource.hikari.*+`, `+spring.datasource.tomcat.*+`, and `+spring.datasource.dbcp2.*+`). +It is also possible to fine-tune implementation-specific settings by using their respective prefix (`+spring.datasource.hikari.*+`, `+spring.datasource.tomcat.*+`, `+spring.datasource.dbcp2.*+`, and `+spring.datasource.oracleucp.*+`). Refer to the documentation of the connection pool implementation you are using for more details. For instance, if you use the {tomcat-docs}/jdbc-pool.html#Common_Attributes[Tomcat connection pool], you could customize many additional settings, as shown in the following example: 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 9bf1a3b388..6120db18df 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 @@ -206,8 +206,16 @@ public final class DataSourceBuilder { create(classLoader, "org.apache.tomcat.jdbc.pool.DataSource", DataSourceSettings::new)); addIfAvailable(providers, create(classLoader, "org.apache.commons.dbcp2.BasicDataSource", DataSourceSettings::new)); - addIfAvailable(providers, - create(classLoader, "oracle.ucp.jdbc.PoolDataSourceImpl", DataSourceSettings::new)); + addIfAvailable(providers, create(classLoader, "oracle.ucp.jdbc.PoolDataSourceImpl", (type) -> { + // Unfortunately Oracle UCP has an import on the Oracle driver itself + if (ClassUtils.isPresent("oracle.jdbc.OracleConnection", classLoader)) { + return new DataSourceSettings(type, (aliases) -> { + aliases.addAliases("username", "user"); + aliases.addAliases("driver-class-name", "connection-factory-class-name"); + }); + } + return null; + })); return providers; } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/metadata/UcpDataSourcePoolMetadata.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/metadata/OracleUcpDataSourcePoolMetadata.java similarity index 64% rename from spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/metadata/UcpDataSourcePoolMetadata.java rename to spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/metadata/OracleUcpDataSourcePoolMetadata.java index d2f24bd185..65d9921213 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/metadata/UcpDataSourcePoolMetadata.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/metadata/OracleUcpDataSourcePoolMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 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. @@ -20,20 +20,19 @@ import java.sql.SQLException; import javax.sql.DataSource; -import org.springframework.dao.InvalidDataAccessApiUsageException; -import org.springframework.util.StringUtils; - import oracle.ucp.jdbc.PoolDataSource; +import org.springframework.util.StringUtils; + /** - * {@link DataSourcePoolMetadata} for a Oracle UCP {@link DataSource}. + * {@link DataSourcePoolMetadata} for an Oracle UCP {@link DataSource}. * * @author Fabio Grassi - * @since 2.3.4 + * @since 2.4.0 */ -public class UcpDataSourcePoolMetadata extends AbstractDataSourcePoolMetadata { +public class OracleUcpDataSourcePoolMetadata extends AbstractDataSourcePoolMetadata { - public UcpDataSourcePoolMetadata(PoolDataSource dataSource) { + public OracleUcpDataSourcePoolMetadata(PoolDataSource dataSource) { super(dataSource); } @@ -42,8 +41,8 @@ public class UcpDataSourcePoolMetadata extends AbstractDataSourcePoolMetadata initializeBuilder() { return DataSourceBuilder.create().driverClassName("org.hsqldb.jdbc.JDBCDriver").url("jdbc:hsqldb:mem:test") diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/metadata/OracleUcpDataSourcePoolMetadataTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/metadata/OracleUcpDataSourcePoolMetadataTests.java new file mode 100644 index 0000000000..b8379948c0 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/metadata/OracleUcpDataSourcePoolMetadataTests.java @@ -0,0 +1,70 @@ +/* + * Copyright 2012-2020 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.jdbc.metadata; + +import java.sql.SQLException; + +import oracle.ucp.jdbc.PoolDataSource; +import oracle.ucp.jdbc.PoolDataSourceImpl; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link OracleUcpDataSourcePoolMetadata}. + * + * @author Fabio Grassi + */ +class OracleUcpDataSourcePoolMetadataTests + extends AbstractDataSourcePoolMetadataTests { + + private final OracleUcpDataSourcePoolMetadata dataSourceMetadata = new OracleUcpDataSourcePoolMetadata( + createDataSource(0, 2)); + + @Override + protected OracleUcpDataSourcePoolMetadata getDataSourceMetadata() { + return this.dataSourceMetadata; + } + + @Override + void getValidationQuery() throws SQLException { + PoolDataSource dataSource = createDataSource(0, 4); + dataSource.setSQLForValidateConnection("SELECT NULL FROM DUAL"); + assertThat(new OracleUcpDataSourcePoolMetadata(dataSource).getValidationQuery()) + .isEqualTo("SELECT NULL FROM DUAL"); + } + + @Override + void getDefaultAutoCommit() throws SQLException { + PoolDataSource dataSource = createDataSource(0, 4); + dataSource.setConnectionProperty("autoCommit", "false"); + assertThat(new OracleUcpDataSourcePoolMetadata(dataSource).getDefaultAutoCommit()).isFalse(); + } + + private PoolDataSource createDataSource(int minSize, int maxSize) { + try { + PoolDataSource dataSource = initializeBuilder().type(PoolDataSourceImpl.class).build(); + dataSource.setInitialPoolSize(minSize); + dataSource.setMinPoolSize(minSize); + dataSource.setMaxPoolSize(maxSize); + return dataSource; + } + catch (SQLException ex) { + throw new IllegalStateException("Error while configuring PoolDataSource", ex); + } + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/metadata/UcpDataSourcePoolMetadataTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/metadata/UcpDataSourcePoolMetadataTests.java deleted file mode 100644 index 5774427231..0000000000 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/metadata/UcpDataSourcePoolMetadataTests.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2012-2019 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.jdbc.metadata; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.sql.SQLException; - -import org.springframework.dao.InvalidDataAccessApiUsageException; - -import oracle.ucp.jdbc.PoolDataSource; -import oracle.ucp.jdbc.PoolDataSourceImpl; - -/** - * Tests for {@link UcpDataSourcePoolMetadata}. - * - * @author Fabio Grassi - */ -public class UcpDataSourcePoolMetadataTests extends AbstractDataSourcePoolMetadataTests { - - private final UcpDataSourcePoolMetadata dataSourceMetadata = new UcpDataSourcePoolMetadata(createDataSource(0, 2)); - - @Override - protected UcpDataSourcePoolMetadata getDataSourceMetadata() { - return this.dataSourceMetadata; - } - - @Override - public void getValidationQuery() { - PoolDataSource dataSource = createDataSource(0, 4); - try { - dataSource.setSQLForValidateConnection("SELECT NULL FROM DUAL"); - } catch (SQLException se) { - throw new InvalidDataAccessApiUsageException("Error while setting property SQLForValidateConnection", se); - } - assertThat(new UcpDataSourcePoolMetadata(dataSource).getValidationQuery()).isEqualTo("SELECT NULL FROM DUAL"); - } - - @Override - public void getDefaultAutoCommit() { - PoolDataSource dataSource = createDataSource(0, 4); - try { - dataSource.setConnectionProperty("autoCommit", "false"); - } catch (SQLException se) { - throw new InvalidDataAccessApiUsageException("Error while setting property connectionProperties.autoCommit", se); - } - assertThat(new UcpDataSourcePoolMetadata(dataSource).getDefaultAutoCommit()).isFalse(); - } - - private PoolDataSource createDataSource(int minSize, int maxSize) { - PoolDataSource dataSource = initializeBuilder().type(PoolDataSourceImpl.class).build(); - try { - dataSource.setInitialPoolSize(minSize); - dataSource.setMinPoolSize(minSize); - dataSource.setMaxPoolSize(maxSize); - } catch (SQLException se) { - throw new InvalidDataAccessApiUsageException("Error while setting a property", se); - } - return dataSource; - } - -}