Skip to content

Commit 75554ce

Browse files
committed
Polish "Add support for Oracle UCP"
See gh-23403
1 parent a21ab39 commit 75554ce

File tree

15 files changed

+180
-160
lines changed

15 files changed

+180
-160
lines changed

buildSrc/src/main/java/org/springframework/boot/build/context/properties/DocumentConfigurationProperties.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ void documentConfigurationProperties() throws IOException {
8282
.withKeyPrefixes("spring.couchbase", "spring.elasticsearch", "spring.h2", "spring.influx",
8383
"spring.mongodb", "spring.neo4j", "spring.redis", "spring.dao", "spring.data",
8484
"spring.datasource", "spring.jooq", "spring.jdbc", "spring.jpa", "spring.r2dbc")
85+
.addOverride("spring.datasource.oracleucp",
86+
"Oracle UCP specific settings bound to an instance of Oracle UCP's PoolDataSource")
8587
.addOverride("spring.datasource.dbcp2",
8688
"Commons DBCP2 specific settings bound to an instance of DBCP2's BasicDataSource")
8789
.addOverride("spring.datasource.tomcat",

spring-boot-project/spring-boot-autoconfigure/build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ dependencies {
2424
optional("com.hazelcast:hazelcast-spring")
2525
optional("com.h2database:h2")
2626
optional("com.nimbusds:oauth2-oidc-sdk")
27+
optional("com.oracle.database.jdbc:ojdbc8")
28+
optional("com.oracle.database.jdbc:ucp")
2729
optional("com.samskivert:jmustache")
2830
optional("com.sun.mail:jakarta.mail")
2931
optional("de.flapdoodle.embed:de.flapdoodle.embed.mongo")
@@ -64,7 +66,6 @@ dependencies {
6466
optional("org.codehaus.groovy:groovy-templates")
6567
optional("com.github.ben-manes.caffeine:caffeine")
6668
optional("com.github.mxab.thymeleaf.extras:thymeleaf-extras-data-attribute")
67-
optional("com.oracle.database.jdbc:ucp")
6869
optional("com.sendgrid:sendgrid-java")
6970
optional("com.unboundid:unboundid-ldapsdk")
7071
optional("com.zaxxer:HikariCP")

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@
4848
* @author Phillip Webb
4949
* @author Stephane Nicoll
5050
* @author Kazuki Shimizu
51-
* @author Fabio Grassi
5251
* @since 1.0.0
5352
*/
5453
@Configuration(proxyBeanMethods = false)
@@ -70,7 +69,7 @@ protected static class EmbeddedDatabaseConfiguration {
7069
@Conditional(PooledDataSourceCondition.class)
7170
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
7271
@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
73-
DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.Ucp.class,
72+
DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class,
7473
DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class })
7574
protected static class PooledDataSourceConfiguration {
7675

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration.java

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -21,7 +21,7 @@
2121
import javax.sql.DataSource;
2222

2323
import com.zaxxer.hikari.HikariDataSource;
24-
import oracle.ucp.jdbc.PoolDataSource;
24+
import oracle.jdbc.OracleConnection;
2525
import oracle.ucp.jdbc.PoolDataSourceImpl;
2626

2727
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@@ -31,7 +31,6 @@
3131
import org.springframework.boot.jdbc.DatabaseDriver;
3232
import org.springframework.context.annotation.Bean;
3333
import org.springframework.context.annotation.Configuration;
34-
import org.springframework.dao.InvalidDataAccessApiUsageException;
3534
import org.springframework.util.StringUtils;
3635

3736
/**
@@ -116,26 +115,22 @@ org.apache.commons.dbcp2.BasicDataSource dataSource(DataSourceProperties propert
116115
}
117116

118117
/**
119-
* UCP DataSource configuration.
118+
* Oracle UCP DataSource configuration.
120119
*/
121120
@Configuration(proxyBeanMethods = false)
122-
@ConditionalOnClass(PoolDataSource.class)
121+
@ConditionalOnClass({ PoolDataSourceImpl.class, OracleConnection.class })
123122
@ConditionalOnMissingBean(DataSource.class)
124123
@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "oracle.ucp.jdbc.PoolDataSource",
125124
matchIfMissing = true)
126-
static class Ucp {
125+
static class OracleUcp {
127126

128127
@Bean
129-
@ConfigurationProperties(prefix = "spring.datasource.ucp")
130-
PoolDataSource dataSource(DataSourceProperties properties) {
131-
PoolDataSource dataSource = createDataSource(properties, PoolDataSourceImpl.class);
128+
@ConfigurationProperties(prefix = "spring.datasource.oracleucp")
129+
PoolDataSourceImpl dataSource(DataSourceProperties properties) throws SQLException {
130+
PoolDataSourceImpl dataSource = createDataSource(properties, PoolDataSourceImpl.class);
131+
dataSource.setValidateConnectionOnBorrow(true);
132132
if (StringUtils.hasText(properties.getName())) {
133-
try {
134-
dataSource.setConnectionPoolName(properties.getName());
135-
}
136-
catch (SQLException se) {
137-
throw new InvalidDataAccessApiUsageException("Error setting property connectionPoolName", se);
138-
}
133+
dataSource.setConnectionPoolName(properties.getName());
139134
}
140135
return dataSource;
141136
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/metadata/DataSourcePoolMetadataProvidersConfiguration.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
1717
package org.springframework.boot.autoconfigure.jdbc.metadata;
1818

1919
import com.zaxxer.hikari.HikariDataSource;
20+
import oracle.jdbc.OracleConnection;
2021
import oracle.ucp.jdbc.PoolDataSource;
2122
import org.apache.commons.dbcp2.BasicDataSource;
2223

@@ -25,8 +26,8 @@
2526
import org.springframework.boot.jdbc.metadata.CommonsDbcp2DataSourcePoolMetadata;
2627
import org.springframework.boot.jdbc.metadata.DataSourcePoolMetadataProvider;
2728
import org.springframework.boot.jdbc.metadata.HikariDataSourcePoolMetadata;
29+
import org.springframework.boot.jdbc.metadata.OracleUcpDataSourcePoolMetadata;
2830
import org.springframework.boot.jdbc.metadata.TomcatDataSourcePoolMetadata;
29-
import org.springframework.boot.jdbc.metadata.UcpDataSourcePoolMetadata;
3031
import org.springframework.context.annotation.Bean;
3132
import org.springframework.context.annotation.Configuration;
3233

@@ -94,15 +95,15 @@ DataSourcePoolMetadataProvider commonsDbcp2PoolDataSourceMetadataProvider() {
9495
}
9596

9697
@Configuration(proxyBeanMethods = false)
97-
@ConditionalOnClass(PoolDataSource.class)
98-
static class UcpPoolDataSourceMetadataProviderConfiguration {
98+
@ConditionalOnClass({ PoolDataSource.class, OracleConnection.class })
99+
static class OracleUcpPoolDataSourceMetadataProviderConfiguration {
99100

100101
@Bean
101-
DataSourcePoolMetadataProvider UcpPoolDataSourceMetadataProvider() {
102+
DataSourcePoolMetadataProvider oracleUcpPoolDataSourceMetadataProvider() {
102103
return (dataSource) -> {
103104
PoolDataSource ucpDataSource = DataSourceUnwrapper.unwrap(dataSource, PoolDataSource.class);
104105
if (ucpDataSource != null) {
105-
return new UcpDataSourcePoolMetadata(ucpDataSource);
106+
return new OracleUcpDataSourcePoolMetadata(ucpDataSource);
106107
}
107108
return null;
108109
};

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfigurationTests.java

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434

3535
import com.zaxxer.hikari.HikariDataSource;
3636
import io.r2dbc.spi.ConnectionFactory;
37+
import oracle.ucp.jdbc.PoolDataSourceImpl;
3738
import org.apache.commons.dbcp2.BasicDataSource;
3839
import org.junit.jupiter.api.Test;
3940

@@ -135,8 +136,25 @@ void commonsDbcp2ValidatesConnectionByDefault() {
135136
assertDataSource(org.apache.commons.dbcp2.BasicDataSource.class,
136137
Arrays.asList("com.zaxxer.hikari", "org.apache.tomcat"), (dataSource) -> {
137138
assertThat(dataSource.getTestOnBorrow()).isTrue();
138-
assertThat(dataSource.getValidationQuery()).isNull(); // Use
139-
// Connection#isValid()
139+
// Use Connection#isValid()
140+
assertThat(dataSource.getValidationQuery()).isNull();
141+
});
142+
}
143+
144+
@Test
145+
void oracleUcpIsFallback() {
146+
assertDataSource(PoolDataSourceImpl.class,
147+
Arrays.asList("com.zaxxer.hikari", "org.apache.tomcat", "org.apache.commons.dbcp2"),
148+
(dataSource) -> assertThat(dataSource.getURL()).startsWith("jdbc:hsqldb:mem:testdb"));
149+
}
150+
151+
@Test
152+
void oracleUcpValidatesConnectionByDefault() {
153+
assertDataSource(PoolDataSourceImpl.class,
154+
Arrays.asList("com.zaxxer.hikari", "org.apache.tomcat", "org.apache.commons.dbcp2"), (dataSource) -> {
155+
assertThat(dataSource.getValidateConnectionOnBorrow()).isTrue();
156+
// Use an internal ping when using an Oracle JDBC driver
157+
assertThat(dataSource.getSQLForValidateConnection()).isNull();
140158
});
141159
}
142160

@@ -225,8 +243,8 @@ void testDataSourceIsInitializedEarly() {
225243
}
226244

227245
private static Function<ApplicationContextRunner, ApplicationContextRunner> hideConnectionPools() {
228-
return (runner) -> runner.withClassLoader(
229-
new FilteredClassLoader("org.apache.tomcat", "com.zaxxer.hikari", "org.apache.commons.dbcp2"));
246+
return (runner) -> runner.withClassLoader(new FilteredClassLoader("org.apache.tomcat", "com.zaxxer.hikari",
247+
"org.apache.commons.dbcp2", "oracle.ucp.jdbc"));
230248
}
231249

232250
private <T extends DataSource> void assertDataSource(Class<T> expectedType, List<String> hiddenPackages,
Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,26 @@
1616

1717
package org.springframework.boot.autoconfigure.jdbc;
1818

19-
import static org.assertj.core.api.Assertions.assertThat;
19+
import java.sql.Connection;
2020

2121
import javax.sql.DataSource;
2222

23+
import oracle.ucp.jdbc.PoolDataSource;
24+
import oracle.ucp.jdbc.PoolDataSourceImpl;
2325
import org.junit.jupiter.api.Test;
26+
2427
import org.springframework.boot.autoconfigure.AutoConfigurations;
2528
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
2629

27-
import oracle.ucp.jdbc.PoolDataSource;
28-
import oracle.ucp.jdbc.PoolDataSourceImpl;
30+
import static org.assertj.core.api.Assertions.assertThat;
2931

3032
/**
3133
* Tests for {@link DataSourceAutoConfiguration} with Oracle UCP.
3234
*
3335
* @author Fabio Grassi
36+
* @author Stephane Nicoll
3437
*/
35-
class UcpDataSourceConfigurationTests {
36-
37-
private static final String PREFIX = "spring.datasource.ucp.";
38+
class OracleUcpDataSourceConfigurationTests {
3839

3940
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
4041
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
@@ -46,13 +47,16 @@ void testDataSourceExists() {
4647
this.contextRunner.run((context) -> {
4748
assertThat(context.getBeansOfType(DataSource.class)).hasSize(1);
4849
assertThat(context.getBeansOfType(PoolDataSourceImpl.class)).hasSize(1);
50+
try (Connection connection = context.getBean(DataSource.class).getConnection()) {
51+
assertThat(connection.isValid(1000)).isTrue();
52+
}
4953
});
5054
}
5155

5256
@Test
5357
void testDataSourcePropertiesOverridden() {
54-
this.contextRunner.withPropertyValues(PREFIX + "URL=jdbc:foo//bar/spam", PREFIX + "maxIdleTime=1234")
55-
.run((context) -> {
58+
this.contextRunner.withPropertyValues("spring.datasource.oracleucp.url=jdbc:foo//bar/spam",
59+
"spring.datasource.oracleucp.max-idle-time=1234").run((context) -> {
5660
PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class);
5761
assertThat(ds.getURL()).isEqualTo("jdbc:foo//bar/spam");
5862
assertThat(ds.getMaxIdleTime()).isEqualTo(1234);
@@ -61,11 +65,11 @@ void testDataSourcePropertiesOverridden() {
6165

6266
@Test
6367
void testDataSourceConnectionPropertiesOverridden() {
64-
this.contextRunner.withPropertyValues(PREFIX + "connectionProperties.autoCommit=false").run((context) -> {
65-
PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class);
66-
assertThat(ds.getConnectionProperty("autoCommit")).isEqualTo("false");
67-
68-
});
68+
this.contextRunner.withPropertyValues("spring.datasource.oracleucp.connection-properties.autoCommit=false")
69+
.run((context) -> {
70+
PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class);
71+
assertThat(ds.getConnectionProperty("autoCommit")).isEqualTo("false");
72+
});
6973
}
7074

7175
@Test
@@ -89,16 +93,15 @@ void nameIsAliasedToPoolName() {
8993
this.contextRunner.withPropertyValues("spring.datasource.name=myDS").run((context) -> {
9094
PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class);
9195
assertThat(ds.getConnectionPoolName()).isEqualTo("myDS");
92-
9396
});
9497
}
9598

9699
@Test
97-
void connectionPoolNameTakesPrecedenceOverName() {
98-
this.contextRunner.withPropertyValues("spring.datasource.name=myDS", PREFIX + "connectionPoolName=myUcpDS")
99-
.run((context) -> {
100+
void poolNameTakesPrecedenceOverName() {
101+
this.contextRunner.withPropertyValues("spring.datasource.name=myDS",
102+
"spring.datasource.oracleucp.connection-pool-name=myOracleUcpDS").run((context) -> {
100103
PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class);
101-
assertThat(ds.getConnectionPoolName()).isEqualTo("myUcpDS");
104+
assertThat(ds.getConnectionPoolName()).isEqualTo("myOracleUcpDS");
102105
});
103106
}
104107

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,8 @@ void createsDataSourceWithNoDataSourceBeanAndLiquibaseUrl() {
105105
@Test
106106
void createsDataSourceWhenSpringJdbcOnlyAvailableWithNoDataSourceBeanAndLiquibaseUrl() {
107107
this.contextRunner.withPropertyValues("spring.liquibase.url:jdbc:hsqldb:mem:liquibase")
108-
.withClassLoader(
109-
new FilteredClassLoader("org.apache.tomcat", "com.zaxxer.hikari", "org.apache.commons.dbcp2"))
108+
.withClassLoader(new FilteredClassLoader("org.apache.tomcat", "com.zaxxer.hikari",
109+
"org.apache.commons.dbcp2", "oracle.ucp.jdbc"))
110110
.run(assertLiquibase((liquibase) -> {
111111
DataSource dataSource = liquibase.getDataSource();
112112
assertThat(dataSource).isInstanceOf(SimpleDriverDataSource.class);

spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3826,7 +3826,8 @@ Spring Boot uses the following algorithm for choosing a specific implementation:
38263826
. We prefer https://github.com/brettwooldridge/HikariCP[HikariCP] for its performance and concurrency.
38273827
If HikariCP is available, we always choose it.
38283828
. Otherwise, if the Tomcat pooling `DataSource` is available, we use it.
3829-
. 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.
3829+
. Otherwise, if https://commons.apache.org/proper/commons-dbcp/[Commons DBCP2] is available, we use it.
3830+
. If none of HikariCP, Tomcat, and DBCP2 are available and if Oracle UCP is available, we use it.
38303831

38313832
If you use the `spring-boot-starter-jdbc` or `spring-boot-starter-data-jpa` "`starters`", you automatically get a dependency to `HikariCP`.
38323833

@@ -3857,7 +3858,7 @@ In other words, if you set `spring.datasource.driver-class-name=com.mysql.jdbc.D
38573858

38583859
See {spring-boot-autoconfigure-module-code}/jdbc/DataSourceProperties.java[`DataSourceProperties`] for more of the supported options.
38593860
These are the standard options that work regardless of the actual implementation.
3860-
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.*+`).
3861+
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.*+`).
38613862
Refer to the documentation of the connection pool implementation you are using for more details.
38623863

38633864
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:

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/DataSourceBuilder.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,8 +206,16 @@ private static List<DataSourceSettings> resolveAvailableDataSourceSettings(Class
206206
create(classLoader, "org.apache.tomcat.jdbc.pool.DataSource", DataSourceSettings::new));
207207
addIfAvailable(providers,
208208
create(classLoader, "org.apache.commons.dbcp2.BasicDataSource", DataSourceSettings::new));
209-
addIfAvailable(providers,
210-
create(classLoader, "oracle.ucp.jdbc.PoolDataSourceImpl", DataSourceSettings::new));
209+
addIfAvailable(providers, create(classLoader, "oracle.ucp.jdbc.PoolDataSourceImpl", (type) -> {
210+
// Unfortunately Oracle UCP has an import on the Oracle driver itself
211+
if (ClassUtils.isPresent("oracle.jdbc.OracleConnection", classLoader)) {
212+
return new DataSourceSettings(type, (aliases) -> {
213+
aliases.addAliases("username", "user");
214+
aliases.addAliases("driver-class-name", "connection-factory-class-name");
215+
});
216+
}
217+
return null;
218+
}));
211219
return providers;
212220
}
213221

0 commit comments

Comments
 (0)