Skip to content

Commit a21ab39

Browse files
fabio-grassi-gbssnicoll
authored andcommitted
Add support for Oracle UCP
See gh-23403
1 parent a19a565 commit a21ab39

File tree

10 files changed

+355
-14
lines changed

10 files changed

+355
-14
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ dependencies {
6464
optional("org.codehaus.groovy:groovy-templates")
6565
optional("com.github.ben-manes.caffeine:caffeine")
6666
optional("com.github.mxab.thymeleaf.extras:thymeleaf-extras-data-attribute")
67+
optional("com.oracle.database.jdbc:ucp")
6768
optional("com.sendgrid:sendgrid-java")
6869
optional("com.unboundid:unboundid-ldapsdk")
6970
optional("com.zaxxer:HikariCP")

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
* @author Phillip Webb
4949
* @author Stephane Nicoll
5050
* @author Kazuki Shimizu
51+
* @author Fabio Grassi
5152
* @since 1.0.0
5253
*/
5354
@Configuration(proxyBeanMethods = false)
@@ -69,8 +70,8 @@ protected static class EmbeddedDatabaseConfiguration {
6970
@Conditional(PooledDataSourceCondition.class)
7071
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
7172
@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
72-
DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.Generic.class,
73-
DataSourceJmxConfiguration.class })
73+
DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.Ucp.class,
74+
DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class })
7475
protected static class PooledDataSourceConfiguration {
7576

7677
}

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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.ucp.jdbc.PoolDataSource;
25+
import oracle.ucp.jdbc.PoolDataSourceImpl;
2226

2327
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2428
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -27,6 +31,7 @@
2731
import org.springframework.boot.jdbc.DatabaseDriver;
2832
import org.springframework.context.annotation.Bean;
2933
import org.springframework.context.annotation.Configuration;
34+
import org.springframework.dao.InvalidDataAccessApiUsageException;
3035
import org.springframework.util.StringUtils;
3136

3237
/**
@@ -35,6 +40,7 @@
3540
* @author Dave Syer
3641
* @author Phillip Webb
3742
* @author Stephane Nicoll
43+
* @author Fabio Grassi
3844
*/
3945
abstract class DataSourceConfiguration {
4046

@@ -109,6 +115,33 @@ org.apache.commons.dbcp2.BasicDataSource dataSource(DataSourceProperties propert
109115

110116
}
111117

118+
/**
119+
* UCP DataSource configuration.
120+
*/
121+
@Configuration(proxyBeanMethods = false)
122+
@ConditionalOnClass(PoolDataSource.class)
123+
@ConditionalOnMissingBean(DataSource.class)
124+
@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "oracle.ucp.jdbc.PoolDataSource",
125+
matchIfMissing = true)
126+
static class Ucp {
127+
128+
@Bean
129+
@ConfigurationProperties(prefix = "spring.datasource.ucp")
130+
PoolDataSource dataSource(DataSourceProperties properties) {
131+
PoolDataSource dataSource = createDataSource(properties, PoolDataSourceImpl.class);
132+
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+
}
139+
}
140+
return dataSource;
141+
}
142+
143+
}
144+
112145
/**
113146
* Generic DataSource configuration.
114147
*/

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.boot.autoconfigure.jdbc.metadata;
1818

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

2223
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@@ -25,6 +26,7 @@
2526
import org.springframework.boot.jdbc.metadata.DataSourcePoolMetadataProvider;
2627
import org.springframework.boot.jdbc.metadata.HikariDataSourcePoolMetadata;
2728
import org.springframework.boot.jdbc.metadata.TomcatDataSourcePoolMetadata;
29+
import org.springframework.boot.jdbc.metadata.UcpDataSourcePoolMetadata;
2830
import org.springframework.context.annotation.Bean;
2931
import org.springframework.context.annotation.Configuration;
3032

@@ -33,6 +35,7 @@
3335
* sources.
3436
*
3537
* @author Stephane Nicoll
38+
* @author Fabio Grassi
3639
* @since 1.2.0
3740
*/
3841
@Configuration(proxyBeanMethods = false)
@@ -90,4 +93,21 @@ DataSourcePoolMetadataProvider commonsDbcp2PoolDataSourceMetadataProvider() {
9093

9194
}
9295

96+
@Configuration(proxyBeanMethods = false)
97+
@ConditionalOnClass(PoolDataSource.class)
98+
static class UcpPoolDataSourceMetadataProviderConfiguration {
99+
100+
@Bean
101+
DataSourcePoolMetadataProvider UcpPoolDataSourceMetadataProvider() {
102+
return (dataSource) -> {
103+
PoolDataSource ucpDataSource = DataSourceUnwrapper.unwrap(dataSource, PoolDataSource.class);
104+
if (ucpDataSource != null) {
105+
return new UcpDataSourcePoolMetadata(ucpDataSource);
106+
}
107+
return null;
108+
};
109+
}
110+
111+
}
112+
93113
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
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 static org.assertj.core.api.Assertions.assertThat;
20+
21+
import javax.sql.DataSource;
22+
23+
import org.junit.jupiter.api.Test;
24+
import org.springframework.boot.autoconfigure.AutoConfigurations;
25+
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
26+
27+
import oracle.ucp.jdbc.PoolDataSource;
28+
import oracle.ucp.jdbc.PoolDataSourceImpl;
29+
30+
/**
31+
* Tests for {@link DataSourceAutoConfiguration} with Oracle UCP.
32+
*
33+
* @author Fabio Grassi
34+
*/
35+
class UcpDataSourceConfigurationTests {
36+
37+
private static final String PREFIX = "spring.datasource.ucp.";
38+
39+
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
40+
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
41+
.withPropertyValues("spring.datasource.initialization-mode=never",
42+
"spring.datasource.type=" + PoolDataSource.class.getName());
43+
44+
@Test
45+
void testDataSourceExists() {
46+
this.contextRunner.run((context) -> {
47+
assertThat(context.getBeansOfType(DataSource.class)).hasSize(1);
48+
assertThat(context.getBeansOfType(PoolDataSourceImpl.class)).hasSize(1);
49+
});
50+
}
51+
52+
@Test
53+
void testDataSourcePropertiesOverridden() {
54+
this.contextRunner.withPropertyValues(PREFIX + "URL=jdbc:foo//bar/spam", PREFIX + "maxIdleTime=1234")
55+
.run((context) -> {
56+
PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class);
57+
assertThat(ds.getURL()).isEqualTo("jdbc:foo//bar/spam");
58+
assertThat(ds.getMaxIdleTime()).isEqualTo(1234);
59+
});
60+
}
61+
62+
@Test
63+
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+
});
69+
}
70+
71+
@Test
72+
void testDataSourceDefaultsPreserved() {
73+
this.contextRunner.run((context) -> {
74+
PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class);
75+
assertThat(ds.getInitialPoolSize()).isEqualTo(0);
76+
assertThat(ds.getMinPoolSize()).isEqualTo(0);
77+
assertThat(ds.getMaxPoolSize()).isEqualTo(Integer.MAX_VALUE);
78+
assertThat(ds.getInactiveConnectionTimeout()).isEqualTo(0);
79+
assertThat(ds.getConnectionWaitTimeout()).isEqualTo(3);
80+
assertThat(ds.getTimeToLiveConnectionTimeout()).isEqualTo(0);
81+
assertThat(ds.getAbandonedConnectionTimeout()).isEqualTo(0);
82+
assertThat(ds.getTimeoutCheckInterval()).isEqualTo(30);
83+
assertThat(ds.getFastConnectionFailoverEnabled()).isFalse();
84+
});
85+
}
86+
87+
@Test
88+
void nameIsAliasedToPoolName() {
89+
this.contextRunner.withPropertyValues("spring.datasource.name=myDS").run((context) -> {
90+
PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class);
91+
assertThat(ds.getConnectionPoolName()).isEqualTo("myDS");
92+
93+
});
94+
}
95+
96+
@Test
97+
void connectionPoolNameTakesPrecedenceOverName() {
98+
this.contextRunner.withPropertyValues("spring.datasource.name=myDS", PREFIX + "connectionPoolName=myUcpDS")
99+
.run((context) -> {
100+
PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class);
101+
assertThat(ds.getConnectionPoolName()).isEqualTo("myUcpDS");
102+
});
103+
}
104+
105+
}

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")

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

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,17 @@
3737

3838
/**
3939
* Convenience class for building a {@link DataSource} with common implementations and
40-
* properties. If HikariCP, Tomcat or Commons DBCP are on the classpath one of them will
41-
* be selected (in that order with Hikari first). In the interest of a uniform interface,
42-
* and so that there can be a fallback to an embedded database if one can be detected on
43-
* the classpath, only a small set of common configuration properties are supported. To
44-
* inject additional properties into the result you can downcast it, or use
40+
* properties. If HikariCP, Tomcat, Commons DBCP or Oracle UCP are on the classpath one of
41+
* them will be selected (in that order with Hikari first). In the interest of a uniform
42+
* interface, and so that there can be a fallback to an embedded database if one can be
43+
* detected on the classpath, only a small set of common configuration properties are
44+
* supported. To inject additional properties into the result you can downcast it, or use
4545
* {@code @ConfigurationProperties}.
4646
*
4747
* @param <T> type of DataSource produced by the builder
4848
* @author Dave Syer
4949
* @author Madhura Bhave
50+
* @author Fabio Grassi
5051
* @since 2.0.0
5152
*/
5253
public final class DataSourceBuilder<T extends DataSource> {
@@ -166,9 +167,9 @@ void registerAliases(DataSource candidate, ConfigurationPropertyNameAliases alia
166167

167168
}
168169

169-
private static class OracleDataSourceSettings extends DataSourceSettings {
170+
private static class OracleCommonDataSourceSettings extends DataSourceSettings {
170171

171-
OracleDataSourceSettings(Class<? extends DataSource> type) {
172+
OracleCommonDataSourceSettings(Class<? extends DataSource> type) {
172173
super(type, (aliases) -> aliases.addAliases("username", "user"));
173174
}
174175

@@ -194,7 +195,7 @@ private static class DataSourceSettingsResolver {
194195
(type) -> new DataSourceSettings(type,
195196
(aliases) -> aliases.addAliases("driver-class-name", "driver-class"))));
196197
addIfAvailable(this.allDataSourceSettings, create(classLoader,
197-
"oracle.jdbc.datasource.OracleCommonDataSource", OracleDataSourceSettings::new));
198+
"oracle.jdbc.datasource.OracleCommonDataSource", OracleCommonDataSourceSettings::new));
198199
}
199200

200201
private static List<DataSourceSettings> resolveAvailableDataSourceSettings(ClassLoader classLoader) {
@@ -205,6 +206,8 @@ private static List<DataSourceSettings> resolveAvailableDataSourceSettings(Class
205206
create(classLoader, "org.apache.tomcat.jdbc.pool.DataSource", DataSourceSettings::new));
206207
addIfAvailable(providers,
207208
create(classLoader, "org.apache.commons.dbcp2.BasicDataSource", DataSourceSettings::new));
209+
addIfAvailable(providers,
210+
create(classLoader, "oracle.ucp.jdbc.PoolDataSourceImpl", DataSourceSettings::new));
208211
return providers;
209212
}
210213

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright 2012-2019 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.jdbc.metadata;
18+
19+
import java.sql.SQLException;
20+
21+
import javax.sql.DataSource;
22+
23+
import org.springframework.dao.InvalidDataAccessApiUsageException;
24+
import org.springframework.util.StringUtils;
25+
26+
import oracle.ucp.jdbc.PoolDataSource;
27+
28+
/**
29+
* {@link DataSourcePoolMetadata} for a Oracle UCP {@link DataSource}.
30+
*
31+
* @author Fabio Grassi
32+
* @since 2.3.4
33+
*/
34+
public class UcpDataSourcePoolMetadata extends AbstractDataSourcePoolMetadata<PoolDataSource> {
35+
36+
public UcpDataSourcePoolMetadata(PoolDataSource dataSource) {
37+
super(dataSource);
38+
}
39+
40+
@Override
41+
public Integer getActive() {
42+
try {
43+
return getDataSource().getBorrowedConnectionsCount();
44+
}
45+
catch (SQLException se) {
46+
throw new InvalidDataAccessApiUsageException("Error while reading property borrowedConnectionsCount", se);
47+
}
48+
}
49+
50+
@Override
51+
public Integer getIdle() {
52+
try {
53+
return getDataSource().getAvailableConnectionsCount();
54+
}
55+
catch (SQLException se) {
56+
throw new InvalidDataAccessApiUsageException("Error while reading property availableConnectionsCount", se);
57+
}
58+
}
59+
60+
@Override
61+
public Integer getMax() {
62+
return getDataSource().getMaxPoolSize();
63+
}
64+
65+
@Override
66+
public Integer getMin() {
67+
return getDataSource().getMinPoolSize();
68+
}
69+
70+
@Override
71+
public String getValidationQuery() {
72+
return getDataSource().getSQLForValidateConnection();
73+
}
74+
75+
@Override
76+
public Boolean getDefaultAutoCommit() {
77+
String ac = getDataSource().getConnectionProperty("autoCommit");
78+
return StringUtils.hasText(ac) ? Boolean.valueOf(ac) : null;
79+
}
80+
81+
}

0 commit comments

Comments
 (0)