Skip to content

Commit a983fb9

Browse files
committed
Merge pull request #23403 from fabio-grassi-gbs
* pr/23403: Polish "Add support for Oracle UCP" Add support for Oracle UCP Closes gh-23403
2 parents a19a565 + 75554ce commit a983fb9

File tree

15 files changed

+386
-25
lines changed

15 files changed

+386
-25
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 & 0 deletions
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")

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,8 @@ protected static class EmbeddedDatabaseConfiguration {
6969
@Conditional(PooledDataSourceCondition.class)
7070
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
7171
@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
72-
DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.Generic.class,
73-
DataSourceJmxConfiguration.class })
72+
DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class,
73+
DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class })
7474
protected static class PooledDataSourceConfiguration {
7575

7676
}

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

Lines changed: 29 additions & 1 deletion
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.
@@ -16,9 +16,13 @@
1616

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

19+
import java.sql.SQLException;
20+
1921
import javax.sql.DataSource;
2022

2123
import com.zaxxer.hikari.HikariDataSource;
24+
import oracle.jdbc.OracleConnection;
25+
import oracle.ucp.jdbc.PoolDataSourceImpl;
2226

2327
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2428
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -35,6 +39,7 @@
3539
* @author Dave Syer
3640
* @author Phillip Webb
3741
* @author Stephane Nicoll
42+
* @author Fabio Grassi
3843
*/
3944
abstract class DataSourceConfiguration {
4045

@@ -109,6 +114,29 @@ org.apache.commons.dbcp2.BasicDataSource dataSource(DataSourceProperties propert
109114

110115
}
111116

117+
/**
118+
* Oracle UCP DataSource configuration.
119+
*/
120+
@Configuration(proxyBeanMethods = false)
121+
@ConditionalOnClass({ PoolDataSourceImpl.class, OracleConnection.class })
122+
@ConditionalOnMissingBean(DataSource.class)
123+
@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "oracle.ucp.jdbc.PoolDataSource",
124+
matchIfMissing = true)
125+
static class OracleUcp {
126+
127+
@Bean
128+
@ConfigurationProperties(prefix = "spring.datasource.oracleucp")
129+
PoolDataSourceImpl dataSource(DataSourceProperties properties) throws SQLException {
130+
PoolDataSourceImpl dataSource = createDataSource(properties, PoolDataSourceImpl.class);
131+
dataSource.setValidateConnectionOnBorrow(true);
132+
if (StringUtils.hasText(properties.getName())) {
133+
dataSource.setConnectionPoolName(properties.getName());
134+
}
135+
return dataSource;
136+
}
137+
138+
}
139+
112140
/**
113141
* Generic DataSource configuration.
114142
*/

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

Lines changed: 22 additions & 1 deletion
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,13 +17,16 @@
1717
package org.springframework.boot.autoconfigure.jdbc.metadata;
1818

1919
import com.zaxxer.hikari.HikariDataSource;
20+
import oracle.jdbc.OracleConnection;
21+
import oracle.ucp.jdbc.PoolDataSource;
2022
import org.apache.commons.dbcp2.BasicDataSource;
2123

2224
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2325
import org.springframework.boot.jdbc.DataSourceUnwrapper;
2426
import org.springframework.boot.jdbc.metadata.CommonsDbcp2DataSourcePoolMetadata;
2527
import org.springframework.boot.jdbc.metadata.DataSourcePoolMetadataProvider;
2628
import org.springframework.boot.jdbc.metadata.HikariDataSourcePoolMetadata;
29+
import org.springframework.boot.jdbc.metadata.OracleUcpDataSourcePoolMetadata;
2730
import org.springframework.boot.jdbc.metadata.TomcatDataSourcePoolMetadata;
2831
import org.springframework.context.annotation.Bean;
2932
import org.springframework.context.annotation.Configuration;
@@ -33,6 +36,7 @@
3336
* sources.
3437
*
3538
* @author Stephane Nicoll
39+
* @author Fabio Grassi
3640
* @since 1.2.0
3741
*/
3842
@Configuration(proxyBeanMethods = false)
@@ -90,4 +94,21 @@ DataSourcePoolMetadataProvider commonsDbcp2PoolDataSourceMetadataProvider() {
9094

9195
}
9296

97+
@Configuration(proxyBeanMethods = false)
98+
@ConditionalOnClass({ PoolDataSource.class, OracleConnection.class })
99+
static class OracleUcpPoolDataSourceMetadataProviderConfiguration {
100+
101+
@Bean
102+
DataSourcePoolMetadataProvider oracleUcpPoolDataSourceMetadataProvider() {
103+
return (dataSource) -> {
104+
PoolDataSource ucpDataSource = DataSourceUnwrapper.unwrap(dataSource, PoolDataSource.class);
105+
if (ucpDataSource != null) {
106+
return new OracleUcpDataSourcePoolMetadata(ucpDataSource);
107+
}
108+
return null;
109+
};
110+
}
111+
112+
}
113+
93114
}

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: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* Copyright 2012-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.jdbc;
18+
19+
import java.sql.Connection;
20+
21+
import javax.sql.DataSource;
22+
23+
import oracle.ucp.jdbc.PoolDataSource;
24+
import oracle.ucp.jdbc.PoolDataSourceImpl;
25+
import org.junit.jupiter.api.Test;
26+
27+
import org.springframework.boot.autoconfigure.AutoConfigurations;
28+
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
29+
30+
import static org.assertj.core.api.Assertions.assertThat;
31+
32+
/**
33+
* Tests for {@link DataSourceAutoConfiguration} with Oracle UCP.
34+
*
35+
* @author Fabio Grassi
36+
* @author Stephane Nicoll
37+
*/
38+
class OracleUcpDataSourceConfigurationTests {
39+
40+
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
41+
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
42+
.withPropertyValues("spring.datasource.initialization-mode=never",
43+
"spring.datasource.type=" + PoolDataSource.class.getName());
44+
45+
@Test
46+
void testDataSourceExists() {
47+
this.contextRunner.run((context) -> {
48+
assertThat(context.getBeansOfType(DataSource.class)).hasSize(1);
49+
assertThat(context.getBeansOfType(PoolDataSourceImpl.class)).hasSize(1);
50+
try (Connection connection = context.getBean(DataSource.class).getConnection()) {
51+
assertThat(connection.isValid(1000)).isTrue();
52+
}
53+
});
54+
}
55+
56+
@Test
57+
void testDataSourcePropertiesOverridden() {
58+
this.contextRunner.withPropertyValues("spring.datasource.oracleucp.url=jdbc:foo//bar/spam",
59+
"spring.datasource.oracleucp.max-idle-time=1234").run((context) -> {
60+
PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class);
61+
assertThat(ds.getURL()).isEqualTo("jdbc:foo//bar/spam");
62+
assertThat(ds.getMaxIdleTime()).isEqualTo(1234);
63+
});
64+
}
65+
66+
@Test
67+
void testDataSourceConnectionPropertiesOverridden() {
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+
});
73+
}
74+
75+
@Test
76+
void testDataSourceDefaultsPreserved() {
77+
this.contextRunner.run((context) -> {
78+
PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class);
79+
assertThat(ds.getInitialPoolSize()).isEqualTo(0);
80+
assertThat(ds.getMinPoolSize()).isEqualTo(0);
81+
assertThat(ds.getMaxPoolSize()).isEqualTo(Integer.MAX_VALUE);
82+
assertThat(ds.getInactiveConnectionTimeout()).isEqualTo(0);
83+
assertThat(ds.getConnectionWaitTimeout()).isEqualTo(3);
84+
assertThat(ds.getTimeToLiveConnectionTimeout()).isEqualTo(0);
85+
assertThat(ds.getAbandonedConnectionTimeout()).isEqualTo(0);
86+
assertThat(ds.getTimeoutCheckInterval()).isEqualTo(30);
87+
assertThat(ds.getFastConnectionFailoverEnabled()).isFalse();
88+
});
89+
}
90+
91+
@Test
92+
void nameIsAliasedToPoolName() {
93+
this.contextRunner.withPropertyValues("spring.datasource.name=myDS").run((context) -> {
94+
PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class);
95+
assertThat(ds.getConnectionPoolName()).isEqualTo("myDS");
96+
});
97+
}
98+
99+
@Test
100+
void poolNameTakesPrecedenceOverName() {
101+
this.contextRunner.withPropertyValues("spring.datasource.name=myDS",
102+
"spring.datasource.oracleucp.connection-pool-name=myOracleUcpDS").run((context) -> {
103+
PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class);
104+
assertThat(ds.getConnectionPoolName()).isEqualTo("myOracleUcpDS");
105+
});
106+
}
107+
108+
}

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/build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ dependencies {
2121
optional("com.atomikos:transactions-jta")
2222
optional("com.fasterxml.jackson.core:jackson-databind")
2323
optional("com.google.code.gson:gson")
24+
optional("com.oracle.database.jdbc:ucp")
2425
optional("com.samskivert:jmustache")
2526
optional("com.zaxxer:HikariCP")
2627
optional("io.netty:netty-tcnative-boringssl-static")
@@ -77,7 +78,7 @@ dependencies {
7778
testImplementation("com.ibm.db2:jcc")
7879
testImplementation("com.jayway.jsonpath:json-path")
7980
testImplementation("com.microsoft.sqlserver:mssql-jdbc")
80-
testImplementation("com.oracle.ojdbc:ojdbc8")
81+
testImplementation("com.oracle.database.jdbc:ojdbc8")
8182
testImplementation("com.squareup.okhttp3:okhttp")
8283
testImplementation("com.sun.xml.messaging.saaj:saaj-impl")
8384
testImplementation("io.projectreactor:reactor-test")

0 commit comments

Comments
 (0)