Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions spring-boot-project/spring-boot-autoconfigure/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ dependencies {
optional("org.codehaus.groovy:groovy-templates")
optional("com.github.ben-manes.caffeine:caffeine")
optional("com.github.mxab.thymeleaf.extras:thymeleaf-extras-data-attribute")
optional("com.oracle.database.jdbc:ucp")
optional("com.sendgrid:sendgrid-java")
optional("com.unboundid:unboundid-ldapsdk")
optional("com.zaxxer:HikariCP")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
* @author Phillip Webb
* @author Stephane Nicoll
* @author Kazuki Shimizu
* @author Fabio Grassi
* @since 1.0.0
*/
@Configuration(proxyBeanMethods = false)
Expand All @@ -69,8 +70,8 @@ protected static class EmbeddedDatabaseConfiguration {
@Conditional(PooledDataSourceCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.Generic.class,
DataSourceJmxConfiguration.class })
DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.Ucp.class,
DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class })
protected static class PooledDataSourceConfiguration {

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@

package org.springframework.boot.autoconfigure.jdbc;

import java.sql.SQLException;

import javax.sql.DataSource;

import com.zaxxer.hikari.HikariDataSource;
import oracle.ucp.jdbc.PoolDataSource;
import oracle.ucp.jdbc.PoolDataSourceImpl;

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
Expand All @@ -27,6 +31,7 @@
import org.springframework.boot.jdbc.DatabaseDriver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.util.StringUtils;

/**
Expand All @@ -35,6 +40,7 @@
* @author Dave Syer
* @author Phillip Webb
* @author Stephane Nicoll
* @author Fabio Grassi
*/
abstract class DataSourceConfiguration {

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

}

/**
* UCP DataSource configuration.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(PoolDataSource.class)
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "oracle.ucp.jdbc.PoolDataSource",
matchIfMissing = true)
static class Ucp {

@Bean
@ConfigurationProperties(prefix = "spring.datasource.ucp")
PoolDataSource dataSource(DataSourceProperties properties) {
PoolDataSource dataSource = createDataSource(properties, PoolDataSourceImpl.class);
if (StringUtils.hasText(properties.getName())) {
try {
dataSource.setConnectionPoolName(properties.getName());
}
catch (SQLException se) {
throw new InvalidDataAccessApiUsageException("Error setting property connectionPoolName", se);
}
}
return dataSource;
}

}

/**
* Generic DataSource configuration.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.springframework.boot.autoconfigure.jdbc.metadata;

import com.zaxxer.hikari.HikariDataSource;
import oracle.ucp.jdbc.PoolDataSource;
import org.apache.commons.dbcp2.BasicDataSource;

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
Expand All @@ -25,6 +26,7 @@
import org.springframework.boot.jdbc.metadata.DataSourcePoolMetadataProvider;
import org.springframework.boot.jdbc.metadata.HikariDataSourcePoolMetadata;
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.Configuration;

Expand All @@ -33,6 +35,7 @@
* sources.
*
* @author Stephane Nicoll
* @author Fabio Grassi
* @since 1.2.0
*/
@Configuration(proxyBeanMethods = false)
Expand Down Expand Up @@ -90,4 +93,21 @@ DataSourcePoolMetadataProvider commonsDbcp2PoolDataSourceMetadataProvider() {

}

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(PoolDataSource.class)
static class UcpPoolDataSourceMetadataProviderConfiguration {

@Bean
DataSourcePoolMetadataProvider UcpPoolDataSourceMetadataProvider() {
return (dataSource) -> {
PoolDataSource ucpDataSource = DataSourceUnwrapper.unwrap(dataSource, PoolDataSource.class);
if (ucpDataSource != null) {
return new UcpDataSourcePoolMetadata(ucpDataSource);
}
return null;
};
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* 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 static org.assertj.core.api.Assertions.assertThat;

import javax.sql.DataSource;

import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;

import oracle.ucp.jdbc.PoolDataSource;
import oracle.ucp.jdbc.PoolDataSourceImpl;

/**
* Tests for {@link DataSourceAutoConfiguration} with Oracle UCP.
*
* @author Fabio Grassi
*/
class UcpDataSourceConfigurationTests {

private static final String PREFIX = "spring.datasource.ucp.";

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

@Test
void testDataSourcePropertiesOverridden() {
this.contextRunner.withPropertyValues(PREFIX + "URL=jdbc:foo//bar/spam", PREFIX + "maxIdleTime=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(PREFIX + "connectionProperties.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 connectionPoolNameTakesPrecedenceOverName() {
this.contextRunner.withPropertyValues("spring.datasource.name=myDS", PREFIX + "connectionPoolName=myUcpDS")
.run((context) -> {
PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class);
assertThat(ds.getConnectionPoolName()).isEqualTo("myUcpDS");
});
}

}
3 changes: 2 additions & 1 deletion spring-boot-project/spring-boot/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ dependencies {
optional("com.atomikos:transactions-jta")
optional("com.fasterxml.jackson.core:jackson-databind")
optional("com.google.code.gson:gson")
optional("com.oracle.database.jdbc:ucp")
optional("com.samskivert:jmustache")
optional("com.zaxxer:HikariCP")
optional("io.netty:netty-tcnative-boringssl-static")
Expand Down Expand Up @@ -77,7 +78,7 @@ dependencies {
testImplementation("com.ibm.db2:jcc")
testImplementation("com.jayway.jsonpath:json-path")
testImplementation("com.microsoft.sqlserver:mssql-jdbc")
testImplementation("com.oracle.ojdbc:ojdbc8")
testImplementation("com.oracle.database.jdbc:ojdbc8")
testImplementation("com.squareup.okhttp3:okhttp")
testImplementation("com.sun.xml.messaging.saaj:saaj-impl")
testImplementation("io.projectreactor:reactor-test")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,38 +16,56 @@

package org.springframework.boot.jdbc;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import javax.sql.DataSource;

import org.springframework.beans.BeanUtils;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertyNameAliases;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.util.ClassUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

/**
* 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
* be selected (in that order with Hikari first). In the interest of a uniform interface,
* and so that there can be a fallback to an embedded database if one can be detected on
* the classpath, only a small set of common configuration properties are supported. To
* inject additional properties into the result you can downcast it, or use
* properties. If HikariCP, Tomcat, Commons DBCP or Oracle UCP are on the classpath one of
* them will be selected (in that order with Hikari first). In the interest of a uniform
* interface, and so that there can be a fallback to an embedded database if one can be
* detected on the classpath, only a small set of common configuration properties are
* supported. To inject additional properties into the result you can downcast it, or use
* {@code @ConfigurationProperties}.
*
* @param <T> type of DataSource produced by the builder
* @author Dave Syer
* @author Madhura Bhave
* @author Fabio Grassi
* @since 2.0.0
*/
public final class DataSourceBuilder<T extends DataSource> {

private static final String PROPERTY_URL = "url";

private static final String PROPERTY_DRIVER_CLASS_NAME = "driverClassName";

private static final String PROPERTY_USER_NAME = "username";

private static final String PROPERTY_PASSWORD = "password";

private static final String[] DATA_SOURCE_TYPE_NAMES = new String[] { "com.zaxxer.hikari.HikariDataSource",
"org.apache.tomcat.jdbc.pool.DataSource", "org.apache.commons.dbcp2.BasicDataSource" };
"org.apache.tomcat.jdbc.pool.DataSource", "org.apache.commons.dbcp2.BasicDataSource",
"oracle.ucp.jdbc.PoolDataSourceImpl" };

private static final MultiValueMap<String, String> PROPERTY_NAME_ALIASES;
static {
PROPERTY_NAME_ALIASES = new LinkedMultiValueMap<>(3);
PROPERTY_NAME_ALIASES.addAll(PROPERTY_DRIVER_CLASS_NAME,
Arrays.asList("driverClass", "connectionFactoryClassName"));
PROPERTY_NAME_ALIASES.addAll(PROPERTY_URL, Arrays.asList("jdbcUrl", "URL"));
PROPERTY_NAME_ALIASES.addAll(PROPERTY_USER_NAME, Arrays.asList("user"));
}

private Class<? extends DataSource> type;

Expand Down Expand Up @@ -77,21 +95,27 @@ public T build() {
}

private void maybeGetDriverClassName() {
if (!this.properties.containsKey("driverClassName") && this.properties.containsKey("url")) {
if (!this.properties.containsKey(PROPERTY_DRIVER_CLASS_NAME) && this.properties.containsKey(PROPERTY_URL)) {
String url = this.properties.get("url");
String driverClass = DatabaseDriver.fromJdbcUrl(url).getDriverClassName();
this.properties.put("driverClassName", driverClass);
this.properties.put(PROPERTY_DRIVER_CLASS_NAME, driverClass);
}
}

private void bind(DataSource result) {
ConfigurationPropertySource source = new MapConfigurationPropertySource(this.properties);
ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases();
aliases.addAliases("driver-class-name", "driver-class");
aliases.addAliases("url", "jdbc-url");
aliases.addAliases("username", "user");
Binder binder = new Binder(source.withAliases(aliases));
binder.bind(ConfigurationPropertyName.EMPTY, Bindable.ofInstance(result));
BeanWrapper beanWrapper = new BeanWrapperImpl(result);
Map<String, String> props = new HashMap<>(this.properties);
for (String origPropName : PROPERTY_NAME_ALIASES.keySet()) {
if (props.containsKey(origPropName) && !beanWrapper.isWritableProperty(origPropName)) {
for (String aliasCandidate : PROPERTY_NAME_ALIASES.get(origPropName)) {
if (beanWrapper.isWritableProperty(aliasCandidate)) {
props.put(aliasCandidate, props.remove(origPropName));
break;
}
}
}
}
beanWrapper.setPropertyValues(props);
}

@SuppressWarnings("unchecked")
Expand All @@ -101,22 +125,22 @@ public <D extends DataSource> DataSourceBuilder<D> type(Class<D> type) {
}

public DataSourceBuilder<T> url(String url) {
this.properties.put("url", url);
this.properties.put(PROPERTY_URL, url);
return this;
}

public DataSourceBuilder<T> driverClassName(String driverClassName) {
this.properties.put("driverClassName", driverClassName);
this.properties.put(PROPERTY_DRIVER_CLASS_NAME, driverClassName);
return this;
}

public DataSourceBuilder<T> username(String username) {
this.properties.put("username", username);
this.properties.put(PROPERTY_USER_NAME, username);
return this;
}

public DataSourceBuilder<T> password(String password) {
this.properties.put("password", password);
this.properties.put(PROPERTY_PASSWORD, password);
return this;
}

Expand Down
Loading