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 decb94b99b..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") 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..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 @@ -69,8 +69,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.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 d94e9d3ec1..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. @@ -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.jdbc.OracleConnection; +import oracle.ucp.jdbc.PoolDataSourceImpl; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -35,6 +39,7 @@ import org.springframework.util.StringUtils; * @author Dave Syer * @author Phillip Webb * @author Stephane Nicoll + * @author Fabio Grassi */ abstract class DataSourceConfiguration { @@ -109,6 +114,29 @@ abstract class DataSourceConfiguration { } + /** + * Oracle UCP DataSource configuration. + */ + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass({ PoolDataSourceImpl.class, OracleConnection.class }) + @ConditionalOnMissingBean(DataSource.class) + @ConditionalOnProperty(name = "spring.datasource.type", havingValue = "oracle.ucp.jdbc.PoolDataSource", + matchIfMissing = true) + static class OracleUcp { + + @Bean + @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())) { + dataSource.setConnectionPoolName(properties.getName()); + } + 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..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,8 @@ 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; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -24,6 +26,7 @@ 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.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -33,6 +36,7 @@ import org.springframework.context.annotation.Configuration; * sources. * * @author Stephane Nicoll + * @author Fabio Grassi * @since 1.2.0 */ @Configuration(proxyBeanMethods = false) @@ -90,4 +94,21 @@ public class DataSourcePoolMetadataProvidersConfiguration { } + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass({ PoolDataSource.class, OracleConnection.class }) + static class OracleUcpPoolDataSourceMetadataProviderConfiguration { + + @Bean + DataSourcePoolMetadataProvider oracleUcpPoolDataSourceMetadataProvider() { + return (dataSource) -> { + PoolDataSource ucpDataSource = DataSourceUnwrapper.unwrap(dataSource, PoolDataSource.class); + if (ucpDataSource != null) { + 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/OracleUcpDataSourceConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/OracleUcpDataSourceConfigurationTests.java new file mode 100644 index 0000000000..93df579d57 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/OracleUcpDataSourceConfigurationTests.java @@ -0,0 +1,108 @@ +/* + * 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 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 static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link DataSourceAutoConfiguration} with Oracle UCP. + * + * @author Fabio Grassi + * @author Stephane Nicoll + */ +class OracleUcpDataSourceConfigurationTests { + + 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); + try (Connection connection = context.getBean(DataSource.class).getConnection()) { + assertThat(connection.isValid(1000)).isTrue(); + } + }); + } + + @Test + void testDataSourcePropertiesOverridden() { + 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); + }); + } + + @Test + void testDataSourceConnectionPropertiesOverridden() { + 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 + 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 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("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/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..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 @@ -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,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", (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/OracleUcpDataSourcePoolMetadata.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/metadata/OracleUcpDataSourcePoolMetadata.java new file mode 100644 index 0000000000..65d9921213 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/metadata/OracleUcpDataSourcePoolMetadata.java @@ -0,0 +1,80 @@ +/* + * 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 javax.sql.DataSource; + +import oracle.ucp.jdbc.PoolDataSource; + +import org.springframework.util.StringUtils; + +/** + * {@link DataSourcePoolMetadata} for an Oracle UCP {@link DataSource}. + * + * @author Fabio Grassi + * @since 2.4.0 + */ +public class OracleUcpDataSourcePoolMetadata extends AbstractDataSourcePoolMetadata { + + public OracleUcpDataSourcePoolMetadata(PoolDataSource dataSource) { + super(dataSource); + } + + @Override + public Integer getActive() { + try { + return getDataSource().getBorrowedConnectionsCount(); + } + catch (SQLException ex) { + return null; + } + } + + @Override + public Integer getIdle() { + try { + return getDataSource().getAvailableConnectionsCount(); + } + catch (SQLException ex) { + return null; + } + } + + @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 autoCommit = getDataSource().getConnectionProperty("autoCommit"); + return StringUtils.hasText(autoCommit) ? Boolean.valueOf(autoCommit) : 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..397df4a4e7 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 @@ -27,6 +27,7 @@ import javax.sql.DataSource; import com.zaxxer.hikari.HikariDataSource; import oracle.jdbc.pool.OracleDataSource; +import oracle.ucp.jdbc.PoolDataSourceImpl; import org.apache.commons.dbcp2.BasicDataSource; import org.h2.Driver; import org.junit.jupiter.api.AfterEach; @@ -40,6 +41,7 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link DataSourceBuilder}. * * @author Stephane Nicoll + * @author Fabio Grassi */ class DataSourceBuilderTests { @@ -68,13 +70,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 defaultToOracleUcpAsLastResort() { + 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(); @@ -100,6 +109,16 @@ class DataSourceBuilderTests { assertThat(oracleDataSource.getUser()).isEqualTo("test"); } + @Test + void dataSourceCanBeCreatedWithOracleUcpDataSource() { + this.dataSource = DataSourceBuilder.create().driverClassName("org.hsqldb.jdbc.JDBCDriver") + .type(PoolDataSourceImpl.class).username("test").build(); + assertThat(this.dataSource).isInstanceOf(PoolDataSourceImpl.class); + PoolDataSourceImpl upcDataSource = (PoolDataSourceImpl) this.dataSource; + assertThat(upcDataSource.getConnectionFactoryClassName()).isEqualTo("org.hsqldb.jdbc.JDBCDriver"); + assertThat(upcDataSource.getUser()).isEqualTo("test"); + } + @Test void dataSourceAliasesAreOnlyAppliedToRelevantDataSource() { this.dataSource = DataSourceBuilder.create().url("jdbc:h2:test").type(TestDataSource.class).username("test") diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/metadata/AbstractDataSourcePoolMetadataTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/metadata/AbstractDataSourcePoolMetadataTests.java index d2b6f35848..af494307e4 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/metadata/AbstractDataSourcePoolMetadataTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/metadata/AbstractDataSourcePoolMetadataTests.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. @@ -90,10 +90,10 @@ abstract class AbstractDataSourcePoolMetadataTests 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); + } + } + +}