Commit 75554ce2 authored by Stephane Nicoll's avatar Stephane Nicoll

Polish "Add support for Oracle UCP"

See gh-23403
parent a21ab392
...@@ -82,6 +82,8 @@ public class DocumentConfigurationProperties extends DefaultTask { ...@@ -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",
......
...@@ -24,6 +24,8 @@ dependencies { ...@@ -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")
...@@ -64,7 +66,6 @@ dependencies { ...@@ -64,7 +66,6 @@ dependencies {
optional("org.codehaus.groovy:groovy-templates") optional("org.codehaus.groovy:groovy-templates")
optional("com.github.ben-manes.caffeine:caffeine") optional("com.github.ben-manes.caffeine:caffeine")
optional("com.github.mxab.thymeleaf.extras:thymeleaf-extras-data-attribute") optional("com.github.mxab.thymeleaf.extras:thymeleaf-extras-data-attribute")
optional("com.oracle.database.jdbc:ucp")
optional("com.sendgrid:sendgrid-java") optional("com.sendgrid:sendgrid-java")
optional("com.unboundid:unboundid-ldapsdk") optional("com.unboundid:unboundid-ldapsdk")
optional("com.zaxxer:HikariCP") optional("com.zaxxer:HikariCP")
......
...@@ -48,7 +48,6 @@ import org.springframework.util.StringUtils; ...@@ -48,7 +48,6 @@ import org.springframework.util.StringUtils;
* @author Phillip Webb * @author Phillip Webb
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Kazuki Shimizu * @author Kazuki Shimizu
* @author Fabio Grassi
* @since 1.0.0 * @since 1.0.0
*/ */
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
...@@ -70,7 +69,7 @@ public class DataSourceAutoConfiguration { ...@@ -70,7 +69,7 @@ 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.Ucp.class, DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class,
DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class }) DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class })
protected static class PooledDataSourceConfiguration { protected static class PooledDataSourceConfiguration {
......
/* /*
* 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.
...@@ -21,7 +21,7 @@ import java.sql.SQLException; ...@@ -21,7 +21,7 @@ import java.sql.SQLException;
import javax.sql.DataSource; import javax.sql.DataSource;
import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.HikariDataSource;
import oracle.ucp.jdbc.PoolDataSource; import oracle.jdbc.OracleConnection;
import oracle.ucp.jdbc.PoolDataSourceImpl; import oracle.ucp.jdbc.PoolDataSourceImpl;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
...@@ -31,7 +31,6 @@ import org.springframework.boot.context.properties.ConfigurationProperties; ...@@ -31,7 +31,6 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DatabaseDriver; import org.springframework.boot.jdbc.DatabaseDriver;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
...@@ -116,26 +115,22 @@ abstract class DataSourceConfiguration { ...@@ -116,26 +115,22 @@ abstract class DataSourceConfiguration {
} }
/** /**
* UCP DataSource configuration. * Oracle UCP DataSource configuration.
*/ */
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@ConditionalOnClass(PoolDataSource.class) @ConditionalOnClass({ PoolDataSourceImpl.class, OracleConnection.class })
@ConditionalOnMissingBean(DataSource.class) @ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "oracle.ucp.jdbc.PoolDataSource", @ConditionalOnProperty(name = "spring.datasource.type", havingValue = "oracle.ucp.jdbc.PoolDataSource",
matchIfMissing = true) matchIfMissing = true)
static class Ucp { static class OracleUcp {
@Bean @Bean
@ConfigurationProperties(prefix = "spring.datasource.ucp") @ConfigurationProperties(prefix = "spring.datasource.oracleucp")
PoolDataSource dataSource(DataSourceProperties properties) { PoolDataSourceImpl dataSource(DataSourceProperties properties) throws SQLException {
PoolDataSource dataSource = createDataSource(properties, PoolDataSourceImpl.class); PoolDataSourceImpl dataSource = createDataSource(properties, PoolDataSourceImpl.class);
dataSource.setValidateConnectionOnBorrow(true);
if (StringUtils.hasText(properties.getName())) { if (StringUtils.hasText(properties.getName())) {
try { dataSource.setConnectionPoolName(properties.getName());
dataSource.setConnectionPoolName(properties.getName());
}
catch (SQLException se) {
throw new InvalidDataAccessApiUsageException("Error setting property connectionPoolName", se);
}
} }
return dataSource; return dataSource;
} }
......
/* /*
* 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,7 @@ ...@@ -17,6 +17,7 @@
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 oracle.ucp.jdbc.PoolDataSource;
import org.apache.commons.dbcp2.BasicDataSource; import org.apache.commons.dbcp2.BasicDataSource;
...@@ -25,8 +26,8 @@ import org.springframework.boot.jdbc.DataSourceUnwrapper; ...@@ -25,8 +26,8 @@ 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.boot.jdbc.metadata.UcpDataSourcePoolMetadata;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
...@@ -94,15 +95,15 @@ public class DataSourcePoolMetadataProvidersConfiguration { ...@@ -94,15 +95,15 @@ public class DataSourcePoolMetadataProvidersConfiguration {
} }
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@ConditionalOnClass(PoolDataSource.class) @ConditionalOnClass({ PoolDataSource.class, OracleConnection.class })
static class UcpPoolDataSourceMetadataProviderConfiguration { static class OracleUcpPoolDataSourceMetadataProviderConfiguration {
@Bean @Bean
DataSourcePoolMetadataProvider UcpPoolDataSourceMetadataProvider() { DataSourcePoolMetadataProvider oracleUcpPoolDataSourceMetadataProvider() {
return (dataSource) -> { return (dataSource) -> {
PoolDataSource ucpDataSource = DataSourceUnwrapper.unwrap(dataSource, PoolDataSource.class); PoolDataSource ucpDataSource = DataSourceUnwrapper.unwrap(dataSource, PoolDataSource.class);
if (ucpDataSource != null) { if (ucpDataSource != null) {
return new UcpDataSourcePoolMetadata(ucpDataSource); return new OracleUcpDataSourcePoolMetadata(ucpDataSource);
} }
return null; return null;
}; };
......
...@@ -34,6 +34,7 @@ import javax.sql.DataSource; ...@@ -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 { ...@@ -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 { ...@@ -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,
......
...@@ -16,25 +16,26 @@ ...@@ -16,25 +16,26 @@
package org.springframework.boot.autoconfigure.jdbc; package org.springframework.boot.autoconfigure.jdbc;
import static org.assertj.core.api.Assertions.assertThat; import java.sql.Connection;
import javax.sql.DataSource; import javax.sql.DataSource;
import oracle.ucp.jdbc.PoolDataSource;
import oracle.ucp.jdbc.PoolDataSourceImpl;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import oracle.ucp.jdbc.PoolDataSource; import static org.assertj.core.api.Assertions.assertThat;
import oracle.ucp.jdbc.PoolDataSourceImpl;
/** /**
* Tests for {@link DataSourceAutoConfiguration} with Oracle UCP. * Tests for {@link DataSourceAutoConfiguration} with Oracle UCP.
* *
* @author Fabio Grassi * @author Fabio Grassi
* @author Stephane Nicoll
*/ */
class UcpDataSourceConfigurationTests { class OracleUcpDataSourceConfigurationTests {
private static final String PREFIX = "spring.datasource.ucp.";
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class)) .withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
...@@ -46,13 +47,16 @@ class UcpDataSourceConfigurationTests { ...@@ -46,13 +47,16 @@ class UcpDataSourceConfigurationTests {
this.contextRunner.run((context) -> { this.contextRunner.run((context) -> {
assertThat(context.getBeansOfType(DataSource.class)).hasSize(1); assertThat(context.getBeansOfType(DataSource.class)).hasSize(1);
assertThat(context.getBeansOfType(PoolDataSourceImpl.class)).hasSize(1); assertThat(context.getBeansOfType(PoolDataSourceImpl.class)).hasSize(1);
try (Connection connection = context.getBean(DataSource.class).getConnection()) {
assertThat(connection.isValid(1000)).isTrue();
}
}); });
} }
@Test @Test
void testDataSourcePropertiesOverridden() { void testDataSourcePropertiesOverridden() {
this.contextRunner.withPropertyValues(PREFIX + "URL=jdbc:foo//bar/spam", PREFIX + "maxIdleTime=1234") this.contextRunner.withPropertyValues("spring.datasource.oracleucp.url=jdbc:foo//bar/spam",
.run((context) -> { "spring.datasource.oracleucp.max-idle-time=1234").run((context) -> {
PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class); PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class);
assertThat(ds.getURL()).isEqualTo("jdbc:foo//bar/spam"); assertThat(ds.getURL()).isEqualTo("jdbc:foo//bar/spam");
assertThat(ds.getMaxIdleTime()).isEqualTo(1234); assertThat(ds.getMaxIdleTime()).isEqualTo(1234);
...@@ -61,11 +65,11 @@ class UcpDataSourceConfigurationTests { ...@@ -61,11 +65,11 @@ class UcpDataSourceConfigurationTests {
@Test @Test
void testDataSourceConnectionPropertiesOverridden() { void testDataSourceConnectionPropertiesOverridden() {
this.contextRunner.withPropertyValues(PREFIX + "connectionProperties.autoCommit=false").run((context) -> { this.contextRunner.withPropertyValues("spring.datasource.oracleucp.connection-properties.autoCommit=false")
PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class); .run((context) -> {
assertThat(ds.getConnectionProperty("autoCommit")).isEqualTo("false"); PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class);
assertThat(ds.getConnectionProperty("autoCommit")).isEqualTo("false");
}); });
} }
@Test @Test
...@@ -89,16 +93,15 @@ class UcpDataSourceConfigurationTests { ...@@ -89,16 +93,15 @@ class UcpDataSourceConfigurationTests {
this.contextRunner.withPropertyValues("spring.datasource.name=myDS").run((context) -> { this.contextRunner.withPropertyValues("spring.datasource.name=myDS").run((context) -> {
PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class); PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class);
assertThat(ds.getConnectionPoolName()).isEqualTo("myDS"); assertThat(ds.getConnectionPoolName()).isEqualTo("myDS");
}); });
} }
@Test @Test
void connectionPoolNameTakesPrecedenceOverName() { void poolNameTakesPrecedenceOverName() {
this.contextRunner.withPropertyValues("spring.datasource.name=myDS", PREFIX + "connectionPoolName=myUcpDS") this.contextRunner.withPropertyValues("spring.datasource.name=myDS",
.run((context) -> { "spring.datasource.oracleucp.connection-pool-name=myOracleUcpDS").run((context) -> {
PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class); PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class);
assertThat(ds.getConnectionPoolName()).isEqualTo("myUcpDS"); assertThat(ds.getConnectionPoolName()).isEqualTo("myOracleUcpDS");
}); });
} }
......
...@@ -105,8 +105,8 @@ class LiquibaseAutoConfigurationTests { ...@@ -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);
......
...@@ -3826,7 +3826,8 @@ Spring Boot uses the following algorithm for choosing a specific implementation: ...@@ -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 ...@@ -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:
......
...@@ -206,8 +206,16 @@ public final class DataSourceBuilder<T extends DataSource> { ...@@ -206,8 +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, addIfAvailable(providers, create(classLoader, "oracle.ucp.jdbc.PoolDataSourceImpl", (type) -> {
create(classLoader, "oracle.ucp.jdbc.PoolDataSourceImpl", DataSourceSettings::new)); // 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;
} }
......
/* /*
* 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.
...@@ -20,20 +20,19 @@ import java.sql.SQLException; ...@@ -20,20 +20,19 @@ import java.sql.SQLException;
import javax.sql.DataSource; import javax.sql.DataSource;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.util.StringUtils;
import oracle.ucp.jdbc.PoolDataSource; 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 * @author Fabio Grassi
* @since 2.3.4 * @since 2.4.0
*/ */
public class UcpDataSourcePoolMetadata extends AbstractDataSourcePoolMetadata<PoolDataSource> { public class OracleUcpDataSourcePoolMetadata extends AbstractDataSourcePoolMetadata<PoolDataSource> {
public UcpDataSourcePoolMetadata(PoolDataSource dataSource) { public OracleUcpDataSourcePoolMetadata(PoolDataSource dataSource) {
super(dataSource); super(dataSource);
} }
...@@ -42,8 +41,8 @@ public class UcpDataSourcePoolMetadata extends AbstractDataSourcePoolMetadata<Po ...@@ -42,8 +41,8 @@ public class UcpDataSourcePoolMetadata extends AbstractDataSourcePoolMetadata<Po
try { try {
return getDataSource().getBorrowedConnectionsCount(); return getDataSource().getBorrowedConnectionsCount();
} }
catch (SQLException se) { catch (SQLException ex) {
throw new InvalidDataAccessApiUsageException("Error while reading property borrowedConnectionsCount", se); return null;
} }
} }
...@@ -52,8 +51,8 @@ public class UcpDataSourcePoolMetadata extends AbstractDataSourcePoolMetadata<Po ...@@ -52,8 +51,8 @@ public class UcpDataSourcePoolMetadata extends AbstractDataSourcePoolMetadata<Po
try { try {
return getDataSource().getAvailableConnectionsCount(); return getDataSource().getAvailableConnectionsCount();
} }
catch (SQLException se) { catch (SQLException ex) {
throw new InvalidDataAccessApiUsageException("Error while reading property availableConnectionsCount", se); return null;
} }
} }
...@@ -74,8 +73,8 @@ public class UcpDataSourcePoolMetadata extends AbstractDataSourcePoolMetadata<Po ...@@ -74,8 +73,8 @@ public class UcpDataSourcePoolMetadata extends AbstractDataSourcePoolMetadata<Po
@Override @Override
public Boolean getDefaultAutoCommit() { public Boolean getDefaultAutoCommit() {
String ac = getDataSource().getConnectionProperty("autoCommit"); String autoCommit = getDataSource().getConnectionProperty("autoCommit");
return StringUtils.hasText(ac) ? Boolean.valueOf(ac) : null; return StringUtils.hasText(autoCommit) ? Boolean.valueOf(autoCommit) : null;
} }
} }
...@@ -16,8 +16,6 @@ ...@@ -16,8 +16,6 @@
package org.springframework.boot.jdbc; package org.springframework.boot.jdbc;
import static org.assertj.core.api.Assertions.assertThat;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.net.URL; import java.net.URL;
...@@ -29,15 +27,15 @@ import javax.sql.DataSource; ...@@ -29,15 +27,15 @@ 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;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.jdbc.datasource.SimpleDriverDataSource;
import com.zaxxer.hikari.HikariDataSource; import org.springframework.jdbc.datasource.SimpleDriverDataSource;
import oracle.ucp.jdbc.PoolDataSourceImpl; import static org.assertj.core.api.Assertions.assertThat;
/** /**
* Tests for {@link DataSourceBuilder}. * Tests for {@link DataSourceBuilder}.
...@@ -80,7 +78,7 @@ class DataSourceBuilderTests { ...@@ -80,7 +78,7 @@ class DataSourceBuilderTests {
} }
@Test @Test
void defaultToUcpAsLastResort() { void defaultToOracleUcpAsLastResort() {
this.dataSource = DataSourceBuilder.create(new HidePackagesClassLoader("com.zaxxer.hikari", this.dataSource = DataSourceBuilder.create(new HidePackagesClassLoader("com.zaxxer.hikari",
"org.apache.tomcat.jdbc.pool", "org.apache.commons.dbcp2")).url("jdbc:h2:test").build(); "org.apache.tomcat.jdbc.pool", "org.apache.commons.dbcp2")).url("jdbc:h2:test").build();
assertThat(this.dataSource).isInstanceOf(PoolDataSourceImpl.class); assertThat(this.dataSource).isInstanceOf(PoolDataSourceImpl.class);
...@@ -111,6 +109,16 @@ class DataSourceBuilderTests { ...@@ -111,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")
...@@ -125,15 +133,6 @@ class DataSourceBuilderTests { ...@@ -125,15 +133,6 @@ class DataSourceBuilderTests {
assertThat(testDataSource.getDriverClass()).isNull(); 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 { final class HidePackagesClassLoader extends URLClassLoader {
private final String[] hiddenPackages; private final String[] hiddenPackages;
......
/* /*
* 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 ...@@ -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")
......
/* /*
* 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,61 +16,55 @@ ...@@ -16,61 +16,55 @@
package org.springframework.boot.jdbc.metadata; package org.springframework.boot.jdbc.metadata;
import static org.assertj.core.api.Assertions.assertThat;
import java.sql.SQLException; import java.sql.SQLException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import oracle.ucp.jdbc.PoolDataSource; import oracle.ucp.jdbc.PoolDataSource;
import oracle.ucp.jdbc.PoolDataSourceImpl; import oracle.ucp.jdbc.PoolDataSourceImpl;
import static org.assertj.core.api.Assertions.assertThat;
/** /**
* Tests for {@link UcpDataSourcePoolMetadata}. * Tests for {@link OracleUcpDataSourcePoolMetadata}.
* *
* @author Fabio Grassi * @author Fabio Grassi
*/ */
public class UcpDataSourcePoolMetadataTests extends AbstractDataSourcePoolMetadataTests<UcpDataSourcePoolMetadata> { class OracleUcpDataSourcePoolMetadataTests
extends AbstractDataSourcePoolMetadataTests<OracleUcpDataSourcePoolMetadata> {
private final UcpDataSourcePoolMetadata dataSourceMetadata = new UcpDataSourcePoolMetadata(createDataSource(0, 2)); private final OracleUcpDataSourcePoolMetadata dataSourceMetadata = new OracleUcpDataSourcePoolMetadata(
createDataSource(0, 2));
@Override @Override
protected UcpDataSourcePoolMetadata getDataSourceMetadata() { protected OracleUcpDataSourcePoolMetadata getDataSourceMetadata() {
return this.dataSourceMetadata; return this.dataSourceMetadata;
} }
@Override @Override
public void getValidationQuery() { void getValidationQuery() throws SQLException {
PoolDataSource dataSource = createDataSource(0, 4); PoolDataSource dataSource = createDataSource(0, 4);
try { dataSource.setSQLForValidateConnection("SELECT NULL FROM DUAL");
dataSource.setSQLForValidateConnection("SELECT NULL FROM DUAL"); assertThat(new OracleUcpDataSourcePoolMetadata(dataSource).getValidationQuery())
} catch (SQLException se) { .isEqualTo("SELECT NULL FROM DUAL");
throw new InvalidDataAccessApiUsageException("Error while setting property SQLForValidateConnection", se);
}
assertThat(new UcpDataSourcePoolMetadata(dataSource).getValidationQuery()).isEqualTo("SELECT NULL FROM DUAL");
} }
@Override @Override
public void getDefaultAutoCommit() { void getDefaultAutoCommit() throws SQLException {
PoolDataSource dataSource = createDataSource(0, 4); PoolDataSource dataSource = createDataSource(0, 4);
try { dataSource.setConnectionProperty("autoCommit", "false");
dataSource.setConnectionProperty("autoCommit", "false"); assertThat(new OracleUcpDataSourcePoolMetadata(dataSource).getDefaultAutoCommit()).isFalse();
} 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) { private PoolDataSource createDataSource(int minSize, int maxSize) {
PoolDataSource dataSource = initializeBuilder().type(PoolDataSourceImpl.class).build();
try { try {
PoolDataSource dataSource = initializeBuilder().type(PoolDataSourceImpl.class).build();
dataSource.setInitialPoolSize(minSize); dataSource.setInitialPoolSize(minSize);
dataSource.setMinPoolSize(minSize); dataSource.setMinPoolSize(minSize);
dataSource.setMaxPoolSize(maxSize); dataSource.setMaxPoolSize(maxSize);
} catch (SQLException se) { return dataSource;
throw new InvalidDataAccessApiUsageException("Error while setting a property", se); }
catch (SQLException ex) {
throw new IllegalStateException("Error while configuring PoolDataSource", ex);
} }
return dataSource;
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment