Merge pull request #23403 from fabio-grassi-gbs

* pr/23403:
  Polish "Add support for Oracle UCP"
  Add support for Oracle UCP

Closes gh-23403
This commit is contained in:
Stephane Nicoll 2020-09-29 16:18:05 +02:00
commit a983fb9559
15 changed files with 386 additions and 25 deletions

View File

@ -82,6 +82,8 @@ public class DocumentConfigurationProperties extends DefaultTask {
.withKeyPrefixes("spring.couchbase", "spring.elasticsearch", "spring.h2", "spring.influx", .withKeyPrefixes("spring.couchbase", "spring.elasticsearch", "spring.h2", "spring.influx",
"spring.mongodb", "spring.neo4j", "spring.redis", "spring.dao", "spring.data", "spring.mongodb", "spring.neo4j", "spring.redis", "spring.dao", "spring.data",
"spring.datasource", "spring.jooq", "spring.jdbc", "spring.jpa", "spring.r2dbc") "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", .addOverride("spring.datasource.dbcp2",
"Commons DBCP2 specific settings bound to an instance of DBCP2's BasicDataSource") "Commons DBCP2 specific settings bound to an instance of DBCP2's BasicDataSource")
.addOverride("spring.datasource.tomcat", .addOverride("spring.datasource.tomcat",

View File

@ -24,6 +24,8 @@ dependencies {
optional("com.hazelcast:hazelcast-spring") optional("com.hazelcast:hazelcast-spring")
optional("com.h2database:h2") optional("com.h2database:h2")
optional("com.nimbusds:oauth2-oidc-sdk") optional("com.nimbusds:oauth2-oidc-sdk")
optional("com.oracle.database.jdbc:ojdbc8")
optional("com.oracle.database.jdbc:ucp")
optional("com.samskivert:jmustache") optional("com.samskivert:jmustache")
optional("com.sun.mail:jakarta.mail") optional("com.sun.mail:jakarta.mail")
optional("de.flapdoodle.embed:de.flapdoodle.embed.mongo") optional("de.flapdoodle.embed:de.flapdoodle.embed.mongo")

View File

@ -69,8 +69,8 @@ public class DataSourceAutoConfiguration {
@Conditional(PooledDataSourceCondition.class) @Conditional(PooledDataSourceCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class }) @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class, @Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.Generic.class, DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class,
DataSourceJmxConfiguration.class }) DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class })
protected static class PooledDataSourceConfiguration { protected static class PooledDataSourceConfiguration {
} }

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,9 +16,13 @@
package org.springframework.boot.autoconfigure.jdbc; package org.springframework.boot.autoconfigure.jdbc;
import java.sql.SQLException;
import javax.sql.DataSource; import javax.sql.DataSource;
import com.zaxxer.hikari.HikariDataSource; 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.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@ -35,6 +39,7 @@ import org.springframework.util.StringUtils;
* @author Dave Syer * @author Dave Syer
* @author Phillip Webb * @author Phillip Webb
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Fabio Grassi
*/ */
abstract class DataSourceConfiguration { 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. * Generic DataSource configuration.
*/ */

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,6 +17,8 @@
package org.springframework.boot.autoconfigure.jdbc.metadata; package org.springframework.boot.autoconfigure.jdbc.metadata;
import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.HikariDataSource;
import oracle.jdbc.OracleConnection;
import oracle.ucp.jdbc.PoolDataSource;
import org.apache.commons.dbcp2.BasicDataSource; import org.apache.commons.dbcp2.BasicDataSource;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 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.CommonsDbcp2DataSourcePoolMetadata;
import org.springframework.boot.jdbc.metadata.DataSourcePoolMetadataProvider; import org.springframework.boot.jdbc.metadata.DataSourcePoolMetadataProvider;
import org.springframework.boot.jdbc.metadata.HikariDataSourcePoolMetadata; 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.TomcatDataSourcePoolMetadata;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -33,6 +36,7 @@ import org.springframework.context.annotation.Configuration;
* sources. * sources.
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Fabio Grassi
* @since 1.2.0 * @since 1.2.0
*/ */
@Configuration(proxyBeanMethods = false) @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;
};
}
}
} }

View File

@ -34,6 +34,7 @@ import javax.sql.DataSource;
import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.HikariDataSource;
import io.r2dbc.spi.ConnectionFactory; import io.r2dbc.spi.ConnectionFactory;
import oracle.ucp.jdbc.PoolDataSourceImpl;
import org.apache.commons.dbcp2.BasicDataSource; import org.apache.commons.dbcp2.BasicDataSource;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -135,8 +136,25 @@ class DataSourceAutoConfigurationTests {
assertDataSource(org.apache.commons.dbcp2.BasicDataSource.class, assertDataSource(org.apache.commons.dbcp2.BasicDataSource.class,
Arrays.asList("com.zaxxer.hikari", "org.apache.tomcat"), (dataSource) -> { Arrays.asList("com.zaxxer.hikari", "org.apache.tomcat"), (dataSource) -> {
assertThat(dataSource.getTestOnBorrow()).isTrue(); assertThat(dataSource.getTestOnBorrow()).isTrue();
assertThat(dataSource.getValidationQuery()).isNull(); // Use // Use Connection#isValid()
// 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<ApplicationContextRunner, ApplicationContextRunner> hideConnectionPools() { private static Function<ApplicationContextRunner, ApplicationContextRunner> hideConnectionPools() {
return (runner) -> runner.withClassLoader( return (runner) -> runner.withClassLoader(new FilteredClassLoader("org.apache.tomcat", "com.zaxxer.hikari",
new FilteredClassLoader("org.apache.tomcat", "com.zaxxer.hikari", "org.apache.commons.dbcp2")); "org.apache.commons.dbcp2", "oracle.ucp.jdbc"));
} }
private <T extends DataSource> void assertDataSource(Class<T> expectedType, List<String> hiddenPackages, private <T extends DataSource> void assertDataSource(Class<T> expectedType, List<String> hiddenPackages,

View File

@ -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");
});
}
}

View File

@ -105,8 +105,8 @@ class LiquibaseAutoConfigurationTests {
@Test @Test
void createsDataSourceWhenSpringJdbcOnlyAvailableWithNoDataSourceBeanAndLiquibaseUrl() { void createsDataSourceWhenSpringJdbcOnlyAvailableWithNoDataSourceBeanAndLiquibaseUrl() {
this.contextRunner.withPropertyValues("spring.liquibase.url:jdbc:hsqldb:mem:liquibase") this.contextRunner.withPropertyValues("spring.liquibase.url:jdbc:hsqldb:mem:liquibase")
.withClassLoader( .withClassLoader(new FilteredClassLoader("org.apache.tomcat", "com.zaxxer.hikari",
new FilteredClassLoader("org.apache.tomcat", "com.zaxxer.hikari", "org.apache.commons.dbcp2")) "org.apache.commons.dbcp2", "oracle.ucp.jdbc"))
.run(assertLiquibase((liquibase) -> { .run(assertLiquibase((liquibase) -> {
DataSource dataSource = liquibase.getDataSource(); DataSource dataSource = liquibase.getDataSource();
assertThat(dataSource).isInstanceOf(SimpleDriverDataSource.class); assertThat(dataSource).isInstanceOf(SimpleDriverDataSource.class);

View File

@ -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. . We prefer https://github.com/brettwooldridge/HikariCP[HikariCP] for its performance and concurrency.
If HikariCP is available, we always choose it. If HikariCP is available, we always choose it.
. Otherwise, if the Tomcat pooling `DataSource` is available, we use 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`. 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. 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. 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. 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: 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:

View File

@ -21,6 +21,7 @@ dependencies {
optional("com.atomikos:transactions-jta") optional("com.atomikos:transactions-jta")
optional("com.fasterxml.jackson.core:jackson-databind") optional("com.fasterxml.jackson.core:jackson-databind")
optional("com.google.code.gson:gson") optional("com.google.code.gson:gson")
optional("com.oracle.database.jdbc:ucp")
optional("com.samskivert:jmustache") optional("com.samskivert:jmustache")
optional("com.zaxxer:HikariCP") optional("com.zaxxer:HikariCP")
optional("io.netty:netty-tcnative-boringssl-static") optional("io.netty:netty-tcnative-boringssl-static")
@ -77,7 +78,7 @@ dependencies {
testImplementation("com.ibm.db2:jcc") testImplementation("com.ibm.db2:jcc")
testImplementation("com.jayway.jsonpath:json-path") testImplementation("com.jayway.jsonpath:json-path")
testImplementation("com.microsoft.sqlserver:mssql-jdbc") testImplementation("com.microsoft.sqlserver:mssql-jdbc")
testImplementation("com.oracle.ojdbc:ojdbc8") testImplementation("com.oracle.database.jdbc:ojdbc8")
testImplementation("com.squareup.okhttp3:okhttp") testImplementation("com.squareup.okhttp3:okhttp")
testImplementation("com.sun.xml.messaging.saaj:saaj-impl") testImplementation("com.sun.xml.messaging.saaj:saaj-impl")
testImplementation("io.projectreactor:reactor-test") testImplementation("io.projectreactor:reactor-test")

View File

@ -37,16 +37,17 @@ import org.springframework.util.ClassUtils;
/** /**
* Convenience class for building a {@link DataSource} with common implementations and * 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 * properties. If HikariCP, Tomcat, Commons DBCP or Oracle UCP are on the classpath one of
* be selected (in that order with Hikari first). In the interest of a uniform interface, * them will be selected (in that order with Hikari first). In the interest of a uniform
* and so that there can be a fallback to an embedded database if one can be detected on * interface, and so that there can be a fallback to an embedded database if one can be
* the classpath, only a small set of common configuration properties are supported. To * detected on the classpath, only a small set of common configuration properties are
* inject additional properties into the result you can downcast it, or use * supported. To inject additional properties into the result you can downcast it, or use
* {@code @ConfigurationProperties}. * {@code @ConfigurationProperties}.
* *
* @param <T> type of DataSource produced by the builder * @param <T> type of DataSource produced by the builder
* @author Dave Syer * @author Dave Syer
* @author Madhura Bhave * @author Madhura Bhave
* @author Fabio Grassi
* @since 2.0.0 * @since 2.0.0
*/ */
public final class DataSourceBuilder<T extends DataSource> { public final class DataSourceBuilder<T extends DataSource> {
@ -166,9 +167,9 @@ public final class DataSourceBuilder<T extends DataSource> {
} }
private static class OracleDataSourceSettings extends DataSourceSettings { private static class OracleCommonDataSourceSettings extends DataSourceSettings {
OracleDataSourceSettings(Class<? extends DataSource> type) { OracleCommonDataSourceSettings(Class<? extends DataSource> type) {
super(type, (aliases) -> aliases.addAliases("username", "user")); super(type, (aliases) -> aliases.addAliases("username", "user"));
} }
@ -194,7 +195,7 @@ public final class DataSourceBuilder<T extends DataSource> {
(type) -> new DataSourceSettings(type, (type) -> new DataSourceSettings(type,
(aliases) -> aliases.addAliases("driver-class-name", "driver-class")))); (aliases) -> aliases.addAliases("driver-class-name", "driver-class"))));
addIfAvailable(this.allDataSourceSettings, create(classLoader, addIfAvailable(this.allDataSourceSettings, create(classLoader,
"oracle.jdbc.datasource.OracleCommonDataSource", OracleDataSourceSettings::new)); "oracle.jdbc.datasource.OracleCommonDataSource", OracleCommonDataSourceSettings::new));
} }
private static List<DataSourceSettings> resolveAvailableDataSourceSettings(ClassLoader classLoader) { private static List<DataSourceSettings> resolveAvailableDataSourceSettings(ClassLoader classLoader) {
@ -205,6 +206,16 @@ public final class DataSourceBuilder<T extends DataSource> {
create(classLoader, "org.apache.tomcat.jdbc.pool.DataSource", DataSourceSettings::new)); create(classLoader, "org.apache.tomcat.jdbc.pool.DataSource", DataSourceSettings::new));
addIfAvailable(providers, addIfAvailable(providers,
create(classLoader, "org.apache.commons.dbcp2.BasicDataSource", DataSourceSettings::new)); 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; return providers;
} }

View File

@ -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<PoolDataSource> {
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;
}
}

View File

@ -27,6 +27,7 @@ import javax.sql.DataSource;
import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.HikariDataSource;
import oracle.jdbc.pool.OracleDataSource; import oracle.jdbc.pool.OracleDataSource;
import oracle.ucp.jdbc.PoolDataSourceImpl;
import org.apache.commons.dbcp2.BasicDataSource; import org.apache.commons.dbcp2.BasicDataSource;
import org.h2.Driver; import org.h2.Driver;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
@ -40,6 +41,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* Tests for {@link DataSourceBuilder}. * Tests for {@link DataSourceBuilder}.
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Fabio Grassi
*/ */
class DataSourceBuilderTests { class DataSourceBuilderTests {
@ -68,13 +70,20 @@ class DataSourceBuilderTests {
} }
@Test @Test
void defaultToCommonsDbcp2AsLastResort() { void defaultToCommonsDbcp2IfNeitherHikariNorTomcatIsNotAvailable() {
this.dataSource = DataSourceBuilder this.dataSource = DataSourceBuilder
.create(new HidePackagesClassLoader("com.zaxxer.hikari", "org.apache.tomcat.jdbc.pool")) .create(new HidePackagesClassLoader("com.zaxxer.hikari", "org.apache.tomcat.jdbc.pool"))
.url("jdbc:h2:test").build(); .url("jdbc:h2:test").build();
assertThat(this.dataSource).isInstanceOf(BasicDataSource.class); 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 @Test
void specificTypeOfDataSource() { void specificTypeOfDataSource() {
HikariDataSource hikariDataSource = DataSourceBuilder.create().type(HikariDataSource.class).build(); HikariDataSource hikariDataSource = DataSourceBuilder.create().type(HikariDataSource.class).build();
@ -100,6 +109,16 @@ class DataSourceBuilderTests {
assertThat(oracleDataSource.getUser()).isEqualTo("test"); 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 @Test
void dataSourceAliasesAreOnlyAppliedToRelevantDataSource() { void dataSourceAliasesAreOnlyAppliedToRelevantDataSource() {
this.dataSource = DataSourceBuilder.create().url("jdbc:h2:test").type(TestDataSource.class).username("test") this.dataSource = DataSourceBuilder.create().url("jdbc:h2:test").type(TestDataSource.class).username("test")

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -90,10 +90,10 @@ abstract class AbstractDataSourcePoolMetadataTests<D extends AbstractDataSourceP
} }
@Test @Test
abstract void getValidationQuery(); abstract void getValidationQuery() throws Exception;
@Test @Test
abstract void getDefaultAutoCommit(); abstract void getDefaultAutoCommit() throws Exception;
protected DataSourceBuilder<?> initializeBuilder() { protected DataSourceBuilder<?> initializeBuilder() {
return DataSourceBuilder.create().driverClassName("org.hsqldb.jdbc.JDBCDriver").url("jdbc:hsqldb:mem:test") return DataSourceBuilder.create().driverClassName("org.hsqldb.jdbc.JDBCDriver").url("jdbc:hsqldb:mem:test")

View File

@ -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<OracleUcpDataSourcePoolMetadata> {
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);
}
}
}