Refactor DataSourceBuilder and add derivedFrom
Refactor `DataSourceBuilder` to use direct property mappers rather than the `Binder` and aliases. Supported DataSource types now include two-way mappers which allows us to both get and set properties in a uniform way. A new `derivedFrom` factory method has been added which allows a new `DataSource` to be derived from an existing one. This update is primarily to allow Flyway and Liquibase migrations to work against a `@Bean` configured DataSource rather than assuming that the primary DataSource was always created via auto-configuration. See gh-25643
This commit is contained in:
parent
6e92daa0a0
commit
85f1e2c9b6
|
@ -20,8 +20,10 @@ dependencies {
|
|||
optional("com.atomikos:transactions-jms")
|
||||
optional("com.atomikos:transactions-jta")
|
||||
optional("com.fasterxml.jackson.core:jackson-databind")
|
||||
optional("com.h2database:h2")
|
||||
optional("com.google.code.gson:gson")
|
||||
optional("com.oracle.database.jdbc:ucp")
|
||||
optional("com.oracle.database.jdbc:ojdbc8")
|
||||
optional("com.samskivert:jmustache")
|
||||
optional("com.zaxxer:HikariCP")
|
||||
optional("io.netty:netty-tcnative-boringssl-static")
|
||||
|
@ -58,6 +60,7 @@ dependencies {
|
|||
optional("org.jboss:jboss-transaction-spi")
|
||||
optional("org.jooq:jooq")
|
||||
optional("org.liquibase:liquibase-core")
|
||||
optional("org.postgresql:postgresql")
|
||||
optional("org.slf4j:jul-to-slf4j")
|
||||
optional("org.slf4j:slf4j-api")
|
||||
optional("org.springframework:spring-messaging")
|
||||
|
@ -76,11 +79,9 @@ dependencies {
|
|||
|
||||
testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
|
||||
testImplementation("com.google.appengine:appengine-api-1.0-sdk")
|
||||
testImplementation("com.h2database:h2")
|
||||
testImplementation("com.ibm.db2:jcc")
|
||||
testImplementation("com.jayway.jsonpath:json-path")
|
||||
testImplementation("com.microsoft.sqlserver:mssql-jdbc")
|
||||
testImplementation("com.oracle.database.jdbc:ojdbc8")
|
||||
testImplementation("com.squareup.okhttp3:okhttp")
|
||||
testImplementation("com.sun.xml.messaging.saaj:saaj-impl")
|
||||
testImplementation("io.projectreactor:reactor-test")
|
||||
|
@ -97,7 +98,6 @@ dependencies {
|
|||
testImplementation("org.mariadb.jdbc:mariadb-java-client")
|
||||
testImplementation("org.mockito:mockito-core")
|
||||
testImplementation("org.mockito:mockito-junit-jupiter")
|
||||
testImplementation("org.postgresql:postgresql")
|
||||
testImplementation("org.springframework:spring-context-support")
|
||||
testImplementation("org.springframework.data:spring-data-redis")
|
||||
testImplementation("org.springframework.data:spring-data-r2dbc")
|
||||
|
|
|
@ -16,241 +16,652 @@
|
|||
|
||||
package org.springframework.boot.jdbc;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.lang.reflect.Method;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import oracle.jdbc.datasource.OracleDataSource;
|
||||
import oracle.ucp.jdbc.PoolDataSource;
|
||||
import oracle.ucp.jdbc.PoolDataSourceImpl;
|
||||
import org.apache.commons.dbcp2.BasicDataSource;
|
||||
import org.h2.jdbcx.JdbcDataSource;
|
||||
import org.postgresql.ds.PGSimpleDataSource;
|
||||
|
||||
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.core.ResolvableType;
|
||||
import org.springframework.jdbc.datasource.SimpleDriverDataSource;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Convenience class for building a {@link DataSource} with common implementations and
|
||||
* 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
|
||||
* Convenience class for building a {@link DataSource}. Provides a limited subset of the
|
||||
* properties supported by a typical {@link DataSource} as well as detection logic to pick
|
||||
* the most suitable pooling {@link DataSource} implementation.
|
||||
* <p>
|
||||
* The following pooling {@link DataSource} implementations are supported by this builder.
|
||||
* When no {@link #type(Class) type} has been explicitly set, the first available pool
|
||||
* implementation will be picked:
|
||||
* <ul>
|
||||
* <li>Hikari ({@code com.zaxxer.hikari.HikariDataSource})</li>
|
||||
* <li>Tomcat JDBC Pool ({@code org.apache.tomcat.jdbc.pool.DataSource})</li>
|
||||
* <li>Apache DBCP2 ({@code org.apache.commons.dbcp2.BasicDataSource})</li>
|
||||
* <li>Oracle UCP ({@code oracle.ucp.jdbc.PoolDataSourceImpl})</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* The following non-pooling {@link DataSource} implementations can be used when
|
||||
* explicitly set as a {@link #type(Class) type}:
|
||||
* <ul>
|
||||
* <li>Spring's {@code SimpleDriverDataSource}
|
||||
* ({@code org.springframework.jdbc.datasource.SimpleDriverDataSource})</li>
|
||||
* <li>Oracle ({@code oracle.jdbc.datasource.OracleDataSource})</li>
|
||||
* <li>H2 ({@code org.h2.jdbcx.JdbcDataSource})</li>
|
||||
* <li>Postgres ({@code org.postgresql.ds.PGSimpleDataSource})</li>
|
||||
* <li>Any {@code DataSource} implementation with appropriately named methods</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* This class is commonly used in an {@code @Bean} method and often combined with
|
||||
* {@code @ConfigurationProperties}.
|
||||
*
|
||||
* @param <T> type of DataSource produced by the builder
|
||||
* @param <T> the {@link DataSource} type being built
|
||||
* @author Dave Syer
|
||||
* @author Madhura Bhave
|
||||
* @author Fabio Grassi
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
* @see #create()
|
||||
* @see #create(ClassLoader)
|
||||
* @see #derivedFrom(DataSource)
|
||||
*/
|
||||
public final class DataSourceBuilder<T extends DataSource> {
|
||||
|
||||
private Class<? extends DataSource> type;
|
||||
private final ClassLoader classLoader;
|
||||
|
||||
private final DataSourceSettingsResolver settingsResolver;
|
||||
private final Map<DataSourceProperty, String> values = new HashMap<>();
|
||||
|
||||
private final Map<String, String> properties = new HashMap<>();
|
||||
private Class<T> type;
|
||||
|
||||
public static DataSourceBuilder<?> create() {
|
||||
return new DataSourceBuilder<>(null);
|
||||
private final T deriveFrom;
|
||||
|
||||
private DataSourceBuilder(ClassLoader classLoader) {
|
||||
this.classLoader = classLoader;
|
||||
this.deriveFrom = null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private DataSourceBuilder(T deriveFrom) {
|
||||
Assert.notNull(deriveFrom, "DataSource must not be null");
|
||||
this.classLoader = deriveFrom.getClass().getClassLoader();
|
||||
this.type = (Class<T>) deriveFrom.getClass();
|
||||
this.deriveFrom = deriveFrom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link DataSource} type that should be built.
|
||||
* @param <D> the datasource type
|
||||
* @param type the datasource type
|
||||
* @return this builder
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <D extends DataSource> DataSourceBuilder<D> type(Class<D> type) {
|
||||
this.type = (Class<T>) type;
|
||||
return (DataSourceBuilder<D>) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the URL that should be used when building the datasource.
|
||||
* @param url the JDBC url
|
||||
* @return this builder
|
||||
*/
|
||||
public DataSourceBuilder<T> url(String url) {
|
||||
set(DataSourceProperty.URL, url);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the driver class name that should be used when building the datasource.
|
||||
* @param driverClassName the driver class name
|
||||
* @return this builder
|
||||
*/
|
||||
public DataSourceBuilder<T> driverClassName(String driverClassName) {
|
||||
set(DataSourceProperty.DRIVER_CLASS_NAME, driverClassName);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the username that should be used when building the datasource.
|
||||
* @param username the user name
|
||||
* @return this builder
|
||||
*/
|
||||
public DataSourceBuilder<T> username(String username) {
|
||||
set(DataSourceProperty.USERNAME, username);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the password that should be used when building the datasource.
|
||||
* @param password the password
|
||||
* @return this builder
|
||||
*/
|
||||
public DataSourceBuilder<T> password(String password) {
|
||||
set(DataSourceProperty.PASSWORD, password);
|
||||
return this;
|
||||
}
|
||||
|
||||
private void set(DataSourceProperty property, String value) {
|
||||
this.values.put(property, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a newly built {@link DataSource} instance.
|
||||
* @return the built datasource
|
||||
*/
|
||||
public T build() {
|
||||
DataSourceProperties<T> properties = DataSourceProperties.forType(this.classLoader, this.type);
|
||||
DataSourceProperties<T> derriveFromProperties = (this.deriveFrom != null)
|
||||
? DataSourceProperties.forType(this.classLoader, this.type) : null;
|
||||
Class<? extends T> instanceType = (this.type != null) ? this.type : properties.getDataSourceInstanceType();
|
||||
T dataSource = BeanUtils.instantiateClass(instanceType);
|
||||
Set<DataSourceProperty> applied = new HashSet<>();
|
||||
for (DataSourceProperty property : DataSourceProperty.values()) {
|
||||
if (this.values.containsKey(property)) {
|
||||
String value = this.values.get(property);
|
||||
properties.set(dataSource, property, value);
|
||||
applied.add(property);
|
||||
}
|
||||
else if (derriveFromProperties != null && properties.canSet(property)) {
|
||||
String value = derriveFromProperties.get(this.deriveFrom, property);
|
||||
if (value != null) {
|
||||
properties.set(dataSource, property, value);
|
||||
applied.add(property);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!applied.contains(DataSourceProperty.DRIVER_CLASS_NAME)
|
||||
&& properties.canSet(DataSourceProperty.DRIVER_CLASS_NAME)
|
||||
&& this.values.containsKey(DataSourceProperty.URL)) {
|
||||
String url = this.values.get(DataSourceProperty.URL);
|
||||
DatabaseDriver driver = DatabaseDriver.fromJdbcUrl(url);
|
||||
properties.set(dataSource, DataSourceProperty.DRIVER_CLASS_NAME, driver.getDriverClassName());
|
||||
}
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link DataSourceBuilder} instance.
|
||||
* @return a new datasource builder instance
|
||||
*/
|
||||
public static DataSourceBuilder<?> create() {
|
||||
return create(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link DataSourceBuilder} instance.
|
||||
* @param classLoader the classloader used to discover preferred settings
|
||||
* @return a new {@link DataSource} builder instance
|
||||
*/
|
||||
public static DataSourceBuilder<?> create(ClassLoader classLoader) {
|
||||
return new DataSourceBuilder<>(classLoader);
|
||||
}
|
||||
|
||||
private DataSourceBuilder(ClassLoader classLoader) {
|
||||
this.settingsResolver = new DataSourceSettingsResolver(classLoader);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public T build() {
|
||||
Class<? extends DataSource> type = getType();
|
||||
DataSource result = BeanUtils.instantiateClass(type);
|
||||
maybeGetDriverClassName();
|
||||
bind(result);
|
||||
return (T) result;
|
||||
}
|
||||
|
||||
private void maybeGetDriverClassName() {
|
||||
if (!this.properties.containsKey("driverClassName") && this.properties.containsKey("url")) {
|
||||
String url = this.properties.get("url");
|
||||
String driverClass = DatabaseDriver.fromJdbcUrl(url).getDriverClassName();
|
||||
this.properties.put("driverClassName", driverClass);
|
||||
}
|
||||
}
|
||||
|
||||
private void bind(DataSource result) {
|
||||
ConfigurationPropertySource source = new MapConfigurationPropertySource(this.properties);
|
||||
ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases();
|
||||
this.settingsResolver.registerAliases(result, aliases);
|
||||
Binder binder = new Binder(source.withAliases(aliases));
|
||||
binder.bind(ConfigurationPropertyName.EMPTY, Bindable.ofInstance(result));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <D extends DataSource> DataSourceBuilder<D> type(Class<D> type) {
|
||||
this.type = type;
|
||||
return (DataSourceBuilder<D>) this;
|
||||
}
|
||||
|
||||
public DataSourceBuilder<T> url(String url) {
|
||||
this.properties.put("url", url);
|
||||
return this;
|
||||
}
|
||||
|
||||
public DataSourceBuilder<T> driverClassName(String driverClassName) {
|
||||
this.properties.put("driverClassName", driverClassName);
|
||||
return this;
|
||||
}
|
||||
|
||||
public DataSourceBuilder<T> username(String username) {
|
||||
this.properties.put("username", username);
|
||||
return this;
|
||||
}
|
||||
|
||||
public DataSourceBuilder<T> password(String password) {
|
||||
this.properties.put("password", password);
|
||||
return this;
|
||||
}
|
||||
|
||||
public static Class<? extends DataSource> findType(ClassLoader classLoader) {
|
||||
DataSourceSettings preferredDataSourceSettings = new DataSourceSettingsResolver(classLoader)
|
||||
.getPreferredDataSourceSettings();
|
||||
return (preferredDataSourceSettings != null) ? preferredDataSourceSettings.getType() : null;
|
||||
}
|
||||
|
||||
private Class<? extends DataSource> getType() {
|
||||
if (this.type != null) {
|
||||
return this.type;
|
||||
}
|
||||
DataSourceSettings preferredDataSourceSettings = this.settingsResolver.getPreferredDataSourceSettings();
|
||||
if (preferredDataSourceSettings != null) {
|
||||
return preferredDataSourceSettings.getType();
|
||||
}
|
||||
throw new IllegalStateException("No supported DataSource type found");
|
||||
}
|
||||
|
||||
private static class DataSourceSettings {
|
||||
|
||||
private final Class<? extends DataSource> type;
|
||||
|
||||
private final Consumer<ConfigurationPropertyNameAliases> aliasesCustomizer;
|
||||
|
||||
DataSourceSettings(Class<? extends DataSource> type,
|
||||
Consumer<ConfigurationPropertyNameAliases> aliasesCustomizer) {
|
||||
this.type = type;
|
||||
this.aliasesCustomizer = aliasesCustomizer;
|
||||
}
|
||||
|
||||
DataSourceSettings(Class<? extends DataSource> type) {
|
||||
this(type, (aliases) -> {
|
||||
});
|
||||
}
|
||||
|
||||
Class<? extends DataSource> getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
void registerAliases(DataSource candidate, ConfigurationPropertyNameAliases aliases) {
|
||||
if (this.type != null && this.type.isInstance(candidate)) {
|
||||
this.aliasesCustomizer.accept(aliases);
|
||||
/**
|
||||
* Create a new {@link DataSourceBuilder} instance derived from the specified data
|
||||
* source. The returned builder can be used to build the same type of
|
||||
* {@link DataSource} with {@code username}, {@code password}, {@code url} and
|
||||
* {@code driverClassName} properties copied from the original when not specifically
|
||||
* set.
|
||||
* @param dataSource the source {@link DataSource}
|
||||
* @return a new {@link DataSource} builder
|
||||
* @since 2.5.0
|
||||
*/
|
||||
public static DataSourceBuilder<?> derivedFrom(DataSource dataSource) {
|
||||
if (dataSource instanceof EmbeddedDatabase) {
|
||||
try {
|
||||
dataSource = dataSource.unwrap(DataSource.class);
|
||||
}
|
||||
catch (SQLException ex) {
|
||||
throw new IllegalStateException("Unable to unwap embedded database", ex);
|
||||
}
|
||||
}
|
||||
|
||||
return new DataSourceBuilder<>(dataSource);
|
||||
}
|
||||
|
||||
private static class OracleDataSourceSettings extends DataSourceSettings {
|
||||
/**
|
||||
* Find the {@link DataSource} type preferred for the given classloader.
|
||||
* @param classLoader the classloader used to discover preferred settings
|
||||
* @return the preferred {@link DataSource} type
|
||||
*/
|
||||
public static Class<? extends DataSource> findType(ClassLoader classLoader) {
|
||||
MappedDataSourceProperties<?> mappings = MappedDataSourceProperties.forType(classLoader, null);
|
||||
return (mappings != null) ? mappings.getDataSourceInstanceType() : null;
|
||||
}
|
||||
|
||||
OracleDataSourceSettings(Class<? extends DataSource> type) {
|
||||
super(type, (aliases) -> aliases.addAliases("username", "user"));
|
||||
/**
|
||||
* An individual DataSource property supported by the builder.
|
||||
*/
|
||||
private enum DataSourceProperty {
|
||||
|
||||
URL("url"),
|
||||
|
||||
DRIVER_CLASS_NAME("driverClassName"),
|
||||
|
||||
USERNAME("username"),
|
||||
|
||||
PASSWORD("password");
|
||||
|
||||
private final String name;
|
||||
|
||||
DataSourceProperty(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends DataSource> getType() {
|
||||
return null; // Base interface
|
||||
public String toString() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
Method findSetter(Class<?> type) {
|
||||
return ReflectionUtils.findMethod(type, "set" + StringUtils.capitalize(this.name), String.class);
|
||||
}
|
||||
|
||||
Method findGetter(Class<?> type) {
|
||||
return ReflectionUtils.findMethod(type, "get" + StringUtils.capitalize(this.name), String.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class DataSourceSettingsResolver {
|
||||
private interface DataSourceProperties<T extends DataSource> {
|
||||
|
||||
private final DataSourceSettings preferredDataSourceSettings;
|
||||
Class<? extends T> getDataSourceInstanceType();
|
||||
|
||||
private final List<DataSourceSettings> allDataSourceSettings;
|
||||
boolean canSet(DataSourceProperty property);
|
||||
|
||||
DataSourceSettingsResolver(ClassLoader classLoader) {
|
||||
List<DataSourceSettings> supportedProviders = resolveAvailableDataSourceSettings(classLoader);
|
||||
this.preferredDataSourceSettings = (!supportedProviders.isEmpty()) ? supportedProviders.get(0) : null;
|
||||
this.allDataSourceSettings = new ArrayList<>(supportedProviders);
|
||||
addIfAvailable(this.allDataSourceSettings,
|
||||
create(classLoader, "org.springframework.jdbc.datasource.SimpleDriverDataSource",
|
||||
(type) -> new DataSourceSettings(type,
|
||||
(aliases) -> aliases.addAliases("driver-class-name", "driver-class"))));
|
||||
addIfAvailable(this.allDataSourceSettings,
|
||||
create(classLoader, "oracle.jdbc.datasource.OracleDataSource", OracleDataSourceSettings::new));
|
||||
addIfAvailable(this.allDataSourceSettings, create(classLoader, "org.h2.jdbcx.JdbcDataSource",
|
||||
(type) -> new DataSourceSettings(type, (aliases) -> aliases.addAliases("username", "user"))));
|
||||
addIfAvailable(this.allDataSourceSettings, create(classLoader, "org.postgresql.ds.PGSimpleDataSource",
|
||||
(type) -> new DataSourceSettings(type, (aliases) -> aliases.addAliases("username", "user"))));
|
||||
void set(T dataSource, DataSourceProperty property, String value);
|
||||
|
||||
String get(T dataSource, DataSourceProperty property);
|
||||
|
||||
static <T extends DataSource> DataSourceProperties<T> forType(ClassLoader classLoader, Class<T> type) {
|
||||
MappedDataSourceProperties<T> mapped = MappedDataSourceProperties.forType(classLoader, type);
|
||||
return (mapped != null) ? mapped : new ReflectionDataSourceProperties<>(type);
|
||||
}
|
||||
|
||||
private static List<DataSourceSettings> resolveAvailableDataSourceSettings(ClassLoader classLoader) {
|
||||
List<DataSourceSettings> providers = new ArrayList<>();
|
||||
addIfAvailable(providers, create(classLoader, "com.zaxxer.hikari.HikariDataSource",
|
||||
(type) -> new DataSourceSettings(type, (aliases) -> aliases.addAliases("url", "jdbc-url"))));
|
||||
addIfAvailable(providers,
|
||||
create(classLoader, "org.apache.tomcat.jdbc.pool.DataSource", DataSourceSettings::new));
|
||||
addIfAvailable(providers,
|
||||
create(classLoader, "org.apache.commons.dbcp2.BasicDataSource", DataSourceSettings::new));
|
||||
addIfAvailable(providers, create(classLoader, "oracle.ucp.jdbc.PoolDataSourceImpl", (type) -> {
|
||||
// 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;
|
||||
}
|
||||
|
||||
private static class MappedDataSourceProperties<T extends DataSource> implements DataSourceProperties<T> {
|
||||
|
||||
private final Map<DataSourceProperty, MappedDataSourceProperty<T, ?>> mappedProperties = new HashMap<>();
|
||||
|
||||
private final Class<T> dataSourceType;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
MappedDataSourceProperties() {
|
||||
this.dataSourceType = (Class<T>) ResolvableType.forClass(MappedDataSourceProperties.class, getClass())
|
||||
.resolveGeneric();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends T> getDataSourceInstanceType() {
|
||||
return this.dataSourceType;
|
||||
}
|
||||
|
||||
protected void add(DataSourceProperty property, Getter<T, String> getter, Setter<T, String> setter) {
|
||||
add(property, String.class, getter, setter);
|
||||
}
|
||||
|
||||
protected <V> void add(DataSourceProperty property, Class<V> type, Getter<T, V> getter, Setter<T, V> setter) {
|
||||
this.mappedProperties.put(property, new MappedDataSourceProperty<>(property, type, getter, setter));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canSet(DataSourceProperty property) {
|
||||
return this.mappedProperties.containsKey(property);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(T dataSource, DataSourceProperty property, String value) {
|
||||
MappedDataSourceProperty<T, ?> mappedProperty = getMapping(property);
|
||||
mappedProperty.set(dataSource, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String get(T dataSource, DataSourceProperty property) {
|
||||
MappedDataSourceProperty<T, ?> mappedProperty = getMapping(property);
|
||||
return mappedProperty.get(dataSource);
|
||||
}
|
||||
|
||||
private MappedDataSourceProperty<T, ?> getMapping(DataSourceProperty property) {
|
||||
MappedDataSourceProperty<T, ?> mappedProperty = this.mappedProperties.get(property);
|
||||
UnsupportedDataSourcePropertyException.throwIf(mappedProperty == null,
|
||||
() -> "No mapping found for " + property);
|
||||
return mappedProperty;
|
||||
}
|
||||
|
||||
static <T extends DataSource> MappedDataSourceProperties<T> forType(ClassLoader classLoader, Class<T> type) {
|
||||
MappedDataSourceProperties<T> pooled = lookupPooled(classLoader, type);
|
||||
if (type == null || pooled != null) {
|
||||
return pooled;
|
||||
}
|
||||
return lookupBasic(classLoader, type);
|
||||
}
|
||||
|
||||
private static <T extends DataSource> MappedDataSourceProperties<T> lookupPooled(ClassLoader classLoader,
|
||||
Class<T> type) {
|
||||
MappedDataSourceProperties<T> result = null;
|
||||
result = lookup(classLoader, type, result, "com.zaxxer.hikari.HikariDataSource",
|
||||
HikariDataSourceProperties::new);
|
||||
result = lookup(classLoader, type, result, "org.apache.tomcat.jdbc.pool.DataSource",
|
||||
TomcatPoolDataSourceProperties::new);
|
||||
result = lookup(classLoader, type, result, "org.apache.commons.dbcp2.BasicDataSource",
|
||||
MappedDbcp2DataSource::new);
|
||||
result = lookup(classLoader, type, result, "oracle.ucp.jdbc.PoolDataSourceImpl",
|
||||
OraclePoolDataSourceProperties::new, "oracle.jdbc.OracleConnection");
|
||||
return result;
|
||||
}
|
||||
|
||||
private static <T extends DataSource> MappedDataSourceProperties<T> lookupBasic(ClassLoader classLoader,
|
||||
Class<T> dataSourceType) {
|
||||
MappedDataSourceProperties<T> result = null;
|
||||
result = lookup(classLoader, dataSourceType, result,
|
||||
"org.springframework.jdbc.datasource.SimpleDriverDataSource",
|
||||
() -> new SimpleDataSourceProperties());
|
||||
result = lookup(classLoader, dataSourceType, result, "oracle.jdbc.datasource.OracleDataSource",
|
||||
OracleDataSourceProperties::new);
|
||||
result = lookup(classLoader, dataSourceType, result, "org.h2.jdbcx.JdbcDataSource",
|
||||
H2DataSourceProperties::new);
|
||||
result = lookup(classLoader, dataSourceType, result, "org.postgresql.ds.PGSimpleDataSource",
|
||||
PostgresDataSourceProperties::new);
|
||||
return result;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static DataSourceSettings create(ClassLoader classLoader, String target,
|
||||
Function<Class<? extends DataSource>, DataSourceSettings> factory) {
|
||||
if (ClassUtils.isPresent(target, classLoader)) {
|
||||
try {
|
||||
Class<? extends DataSource> type = (Class<? extends DataSource>) ClassUtils.forName(target,
|
||||
classLoader);
|
||||
return factory.apply(type);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Ignore
|
||||
}
|
||||
private static <T extends DataSource> MappedDataSourceProperties<T> lookup(ClassLoader classLoader,
|
||||
Class<T> dataSourceType, MappedDataSourceProperties<T> existing, String dataSourceClassName,
|
||||
Supplier<MappedDataSourceProperties<?>> propertyMappingsSupplier, String... requiredClassNames) {
|
||||
if (existing != null || !allPresent(classLoader, dataSourceClassName, requiredClassNames)) {
|
||||
return existing;
|
||||
}
|
||||
return null;
|
||||
MappedDataSourceProperties<?> propertyMappings = propertyMappingsSupplier.get();
|
||||
return (dataSourceType == null
|
||||
|| propertyMappings.getDataSourceInstanceType().isAssignableFrom(dataSourceType))
|
||||
? (MappedDataSourceProperties<T>) propertyMappings : null;
|
||||
}
|
||||
|
||||
private static void addIfAvailable(Collection<DataSourceSettings> list, DataSourceSettings dataSourceSettings) {
|
||||
if (dataSourceSettings != null) {
|
||||
list.add(dataSourceSettings);
|
||||
private static boolean allPresent(ClassLoader classLoader, String dataSourceClassName,
|
||||
String[] requiredClassNames) {
|
||||
boolean result = ClassUtils.isPresent(dataSourceClassName, classLoader);
|
||||
for (String requiredClassName : requiredClassNames) {
|
||||
result = result && ClassUtils.isPresent(requiredClassName, classLoader);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class MappedDataSourceProperty<T extends DataSource, V> {
|
||||
|
||||
private final DataSourceProperty property;
|
||||
|
||||
private final Class<V> type;
|
||||
|
||||
private final Getter<T, V> getter;
|
||||
|
||||
private final Setter<T, V> setter;
|
||||
|
||||
MappedDataSourceProperty(DataSourceProperty property, Class<V> type, Getter<T, V> getter, Setter<T, V> setter) {
|
||||
this.property = property;
|
||||
this.type = type;
|
||||
this.getter = getter;
|
||||
this.setter = setter;
|
||||
}
|
||||
|
||||
void set(T dataSource, String value) {
|
||||
try {
|
||||
UnsupportedDataSourcePropertyException.throwIf(this.setter == null,
|
||||
() -> "No setter mapped for '" + this.property + "' property");
|
||||
this.setter.set(dataSource, convertFromString(value));
|
||||
}
|
||||
catch (SQLException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
DataSourceSettings getPreferredDataSourceSettings() {
|
||||
return this.preferredDataSourceSettings;
|
||||
String get(T dataSource) {
|
||||
try {
|
||||
UnsupportedDataSourcePropertyException.throwIf(this.getter == null,
|
||||
() -> "No getter mapped for '" + this.property + "' property");
|
||||
return convertToString(this.getter.get(dataSource));
|
||||
}
|
||||
catch (SQLException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
void registerAliases(DataSource result, ConfigurationPropertyNameAliases aliases) {
|
||||
this.allDataSourceSettings.forEach((settings) -> settings.registerAliases(result, aliases));
|
||||
@SuppressWarnings("unchecked")
|
||||
private V convertFromString(String value) {
|
||||
if (String.class.equals(this.type)) {
|
||||
return (V) value;
|
||||
}
|
||||
if (Class.class.equals(this.type)) {
|
||||
return (V) ClassUtils.resolveClassName(value, null);
|
||||
}
|
||||
throw new IllegalStateException("Unsupported value type " + this.type);
|
||||
}
|
||||
|
||||
private String convertToString(V value) {
|
||||
if (String.class.equals(this.type)) {
|
||||
return (String) value;
|
||||
}
|
||||
if (Class.class.equals(this.type)) {
|
||||
return ((Class<?>) value).getName();
|
||||
}
|
||||
throw new IllegalStateException("Unsupported value type " + this.type);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class ReflectionDataSourceProperties<T extends DataSource> implements DataSourceProperties<T> {
|
||||
|
||||
private final Map<DataSourceProperty, Method> getters;
|
||||
|
||||
private final Map<DataSourceProperty, Method> setters;
|
||||
|
||||
private Class<T> dataSourceType;
|
||||
|
||||
ReflectionDataSourceProperties(Class<T> dataSourceType) {
|
||||
Assert.state(dataSourceType != null, "No supported DataSource type found");
|
||||
Map<DataSourceProperty, Method> getters = new HashMap<>();
|
||||
Map<DataSourceProperty, Method> setters = new HashMap<>();
|
||||
for (DataSourceProperty property : DataSourceProperty.values()) {
|
||||
putIfNotNull(getters, property, property.findGetter(dataSourceType));
|
||||
putIfNotNull(setters, property, property.findSetter(dataSourceType));
|
||||
}
|
||||
this.dataSourceType = dataSourceType;
|
||||
this.getters = Collections.unmodifiableMap(getters);
|
||||
this.setters = Collections.unmodifiableMap(setters);
|
||||
}
|
||||
|
||||
private void putIfNotNull(Map<DataSourceProperty, Method> map, DataSourceProperty property, Method method) {
|
||||
if (method != null) {
|
||||
map.put(property, method);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<T> getDataSourceInstanceType() {
|
||||
return this.dataSourceType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canSet(DataSourceProperty property) {
|
||||
return this.setters.containsKey(property);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(T dataSource, DataSourceProperty property, String value) {
|
||||
Method method = getMethod(property, this.setters);
|
||||
ReflectionUtils.invokeMethod(method, dataSource, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String get(T dataSource, DataSourceProperty property) {
|
||||
Method method = getMethod(property, this.getters);
|
||||
return (String) ReflectionUtils.invokeMethod(method, dataSource);
|
||||
}
|
||||
|
||||
private Method getMethod(DataSourceProperty property, Map<DataSourceProperty, Method> setters2) {
|
||||
Method method = setters2.get(property);
|
||||
UnsupportedDataSourcePropertyException.throwIf(method == null,
|
||||
() -> "Unable to find sutable method for " + property);
|
||||
ReflectionUtils.makeAccessible(method);
|
||||
return method;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface Getter<T, V> {
|
||||
|
||||
V get(T instance) throws SQLException;
|
||||
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface Setter<T, V> {
|
||||
|
||||
void set(T instance, V value) throws SQLException;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link MappedDataSource} for Hikari.
|
||||
*/
|
||||
private static class HikariDataSourceProperties extends MappedDataSourceProperties<HikariDataSource> {
|
||||
|
||||
HikariDataSourceProperties() {
|
||||
add(DataSourceProperty.URL, HikariDataSource::getJdbcUrl, HikariDataSource::setJdbcUrl);
|
||||
add(DataSourceProperty.DRIVER_CLASS_NAME, HikariDataSource::getDriverClassName,
|
||||
HikariDataSource::setDriverClassName);
|
||||
add(DataSourceProperty.USERNAME, HikariDataSource::getUsername, HikariDataSource::setUsername);
|
||||
add(DataSourceProperty.PASSWORD, HikariDataSource::getPassword, HikariDataSource::setPassword);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link MappedDataSource} for Tomcat Pool.
|
||||
*/
|
||||
private static class TomcatPoolDataSourceProperties
|
||||
extends MappedDataSourceProperties<org.apache.tomcat.jdbc.pool.DataSource> {
|
||||
|
||||
TomcatPoolDataSourceProperties() {
|
||||
add(DataSourceProperty.URL, org.apache.tomcat.jdbc.pool.DataSource::getUrl,
|
||||
org.apache.tomcat.jdbc.pool.DataSource::setUrl);
|
||||
add(DataSourceProperty.DRIVER_CLASS_NAME, org.apache.tomcat.jdbc.pool.DataSource::getDriverClassName,
|
||||
org.apache.tomcat.jdbc.pool.DataSource::setDriverClassName);
|
||||
add(DataSourceProperty.USERNAME, org.apache.tomcat.jdbc.pool.DataSource::getUsername,
|
||||
org.apache.tomcat.jdbc.pool.DataSource::setUsername);
|
||||
add(DataSourceProperty.PASSWORD, org.apache.tomcat.jdbc.pool.DataSource::getPassword,
|
||||
org.apache.tomcat.jdbc.pool.DataSource::setPassword);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link MappedDataSource} for DBCP2.
|
||||
*/
|
||||
private static class MappedDbcp2DataSource extends MappedDataSourceProperties<BasicDataSource> {
|
||||
|
||||
MappedDbcp2DataSource() {
|
||||
add(DataSourceProperty.URL, BasicDataSource::getUrl, BasicDataSource::setUrl);
|
||||
add(DataSourceProperty.DRIVER_CLASS_NAME, BasicDataSource::getDriverClassName,
|
||||
BasicDataSource::setDriverClassName);
|
||||
add(DataSourceProperty.USERNAME, BasicDataSource::getUsername, BasicDataSource::setUsername);
|
||||
add(DataSourceProperty.PASSWORD, BasicDataSource::getPassword, BasicDataSource::setPassword);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link MappedDataSource} for Oracle Pool.
|
||||
*/
|
||||
private static class OraclePoolDataSourceProperties extends MappedDataSourceProperties<PoolDataSource> {
|
||||
|
||||
@Override
|
||||
public Class<? extends PoolDataSource> getDataSourceInstanceType() {
|
||||
return PoolDataSourceImpl.class;
|
||||
}
|
||||
|
||||
OraclePoolDataSourceProperties() {
|
||||
add(DataSourceProperty.URL, PoolDataSource::getURL, PoolDataSource::setURL);
|
||||
add(DataSourceProperty.DRIVER_CLASS_NAME, PoolDataSource::getConnectionFactoryClassName,
|
||||
PoolDataSource::setConnectionFactoryClassName);
|
||||
add(DataSourceProperty.USERNAME, PoolDataSource::getUser, PoolDataSource::setUser);
|
||||
add(DataSourceProperty.PASSWORD, PoolDataSource::getPassword, PoolDataSource::setPassword);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link MappedDataSource} for Spring's {@link SimpleDriverDataSource}.
|
||||
*/
|
||||
private static class SimpleDataSourceProperties extends MappedDataSourceProperties<SimpleDriverDataSource> {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
SimpleDataSourceProperties() {
|
||||
add(DataSourceProperty.URL, SimpleDriverDataSource::getUrl, SimpleDriverDataSource::setUrl);
|
||||
add(DataSourceProperty.DRIVER_CLASS_NAME, Class.class, (dataSource) -> dataSource.getDriver().getClass(),
|
||||
(dataSource, driverClass) -> dataSource.setDriverClass(driverClass));
|
||||
add(DataSourceProperty.USERNAME, SimpleDriverDataSource::getUsername, SimpleDriverDataSource::setUsername);
|
||||
add(DataSourceProperty.PASSWORD, SimpleDriverDataSource::getPassword, SimpleDriverDataSource::setPassword);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link MappedDataSource} for Oracle.
|
||||
*/
|
||||
private static class OracleDataSourceProperties extends MappedDataSourceProperties<OracleDataSource> {
|
||||
|
||||
OracleDataSourceProperties() {
|
||||
add(DataSourceProperty.URL, OracleDataSource::getURL, OracleDataSource::setURL);
|
||||
add(DataSourceProperty.USERNAME, OracleDataSource::getUser, OracleDataSource::setUser);
|
||||
add(DataSourceProperty.PASSWORD, null, OracleDataSource::setPassword);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link MappedDataSource} for H2.
|
||||
*/
|
||||
private static class H2DataSourceProperties extends MappedDataSourceProperties<JdbcDataSource> {
|
||||
|
||||
H2DataSourceProperties() {
|
||||
add(DataSourceProperty.URL, JdbcDataSource::getUrl, JdbcDataSource::setUrl);
|
||||
add(DataSourceProperty.USERNAME, JdbcDataSource::getUser, JdbcDataSource::setUser);
|
||||
add(DataSourceProperty.PASSWORD, JdbcDataSource::getPassword, JdbcDataSource::setPassword);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link MappedDataSource} for Postgres.
|
||||
*/
|
||||
private static class PostgresDataSourceProperties extends MappedDataSourceProperties<PGSimpleDataSource> {
|
||||
|
||||
PostgresDataSourceProperties() {
|
||||
add(DataSourceProperty.URL, PGSimpleDataSource::getUrl, PGSimpleDataSource::setUrl);
|
||||
add(DataSourceProperty.USERNAME, PGSimpleDataSource::getUser, PGSimpleDataSource::setUser);
|
||||
add(DataSourceProperty.PASSWORD, PGSimpleDataSource::getPassword, PGSimpleDataSource::setPassword);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright 2012-2021 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.jdbc;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* {@link RuntimeException} thrown from {@link DataSourceBuilder} when an unsupported
|
||||
* property is used.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 2.5.0
|
||||
*/
|
||||
public class UnsupportedDataSourcePropertyException extends RuntimeException {
|
||||
|
||||
UnsupportedDataSourcePropertyException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
static void throwIf(boolean test, Supplier<String> message) {
|
||||
if (test) {
|
||||
throw new UnsupportedDataSourcePropertyException(message.get());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright 2012-2021 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.jdbc;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link DataSourceBuilder} when Hikari is not on the classpath.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@ClassPathExclusions("Hikari*.jar")
|
||||
class DataSourceBuilderNoHikariTests {
|
||||
|
||||
@Test
|
||||
void findTypeReturnsTomcatDataSource() {
|
||||
assertThat(DataSourceBuilder.findType(null)).isEqualTo(org.apache.tomcat.jdbc.pool.DataSource.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createAndBuildReturnsTomcatDataSource() {
|
||||
DataSource dataSource = DataSourceBuilder.create().build();
|
||||
assertThat(dataSource).isInstanceOf(org.apache.tomcat.jdbc.pool.DataSource.class);
|
||||
}
|
||||
|
||||
}
|
|
@ -20,12 +20,14 @@ import java.io.Closeable;
|
|||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import oracle.jdbc.internal.OpaqueString;
|
||||
import oracle.jdbc.pool.OracleDataSource;
|
||||
import oracle.ucp.jdbc.PoolDataSourceImpl;
|
||||
import org.apache.commons.dbcp2.BasicDataSource;
|
||||
|
@ -35,15 +37,21 @@ import org.junit.jupiter.api.AfterEach;
|
|||
import org.junit.jupiter.api.Test;
|
||||
import org.postgresql.ds.PGSimpleDataSource;
|
||||
|
||||
import org.springframework.jdbc.datasource.AbstractDataSource;
|
||||
import org.springframework.jdbc.datasource.SimpleDriverDataSource;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
/**
|
||||
* Tests for {@link DataSourceBuilder}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Fabio Grassi
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class DataSourceBuilderTests {
|
||||
|
||||
|
@ -57,7 +65,7 @@ class DataSourceBuilderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void defaultToHikari() {
|
||||
void buildWhenHikariAvailableReturnsHikariDataSource() {
|
||||
this.dataSource = DataSourceBuilder.create().url("jdbc:h2:test").build();
|
||||
assertThat(this.dataSource).isInstanceOf(HikariDataSource.class);
|
||||
HikariDataSource hikariDataSource = (HikariDataSource) this.dataSource;
|
||||
|
@ -65,14 +73,14 @@ class DataSourceBuilderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void defaultToTomcatIfHikariIsNotAvailable() {
|
||||
void buildWhenHikariNotAvailableReturnsTomcatDataSource() {
|
||||
this.dataSource = DataSourceBuilder.create(new HidePackagesClassLoader("com.zaxxer.hikari")).url("jdbc:h2:test")
|
||||
.build();
|
||||
assertThat(this.dataSource).isInstanceOf(org.apache.tomcat.jdbc.pool.DataSource.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultToCommonsDbcp2IfNeitherHikariNorTomcatIsNotAvailable() {
|
||||
void buildWhenHikariAndTomcatNotAvailableReturnsDbcp2DataSource() {
|
||||
this.dataSource = DataSourceBuilder
|
||||
.create(new HidePackagesClassLoader("com.zaxxer.hikari", "org.apache.tomcat.jdbc.pool"))
|
||||
.url("jdbc:h2:test").build();
|
||||
|
@ -80,20 +88,20 @@ class DataSourceBuilderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void defaultToOracleUcpAsLastResort() {
|
||||
void buildWhenHikariAndTomcatAndDbcpNotAvailableReturnsOracleUcpDataSource() {
|
||||
this.dataSource = DataSourceBuilder.create(new HidePackagesClassLoader("com.zaxxer.hikari",
|
||||
"org.apache.tomcat.jdbc.pool", "org.apache.commons.dbcp2")).url("jdbc:h2:test").build();
|
||||
assertThat(this.dataSource).isInstanceOf(PoolDataSourceImpl.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void specificTypeOfDataSource() {
|
||||
void buildWhenHikariTypeSpecifiedReturnsExpectedDataSource() {
|
||||
HikariDataSource hikariDataSource = DataSourceBuilder.create().type(HikariDataSource.class).build();
|
||||
assertThat(hikariDataSource).isInstanceOf(HikariDataSource.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void dataSourceCanBeCreatedWithSimpleDriverDataSource() {
|
||||
void buildWhenSimpleDriverTypeSpecifiedReturnsExpectedDataSource() {
|
||||
this.dataSource = DataSourceBuilder.create().url("jdbc:h2:test").type(SimpleDriverDataSource.class).build();
|
||||
assertThat(this.dataSource).isInstanceOf(SimpleDriverDataSource.class);
|
||||
SimpleDriverDataSource simpleDriverDataSource = (SimpleDriverDataSource) this.dataSource;
|
||||
|
@ -102,7 +110,7 @@ class DataSourceBuilderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void dataSourceCanBeCreatedWithOracleDataSource() throws SQLException {
|
||||
void buildWhenOracleTypeSpecifiedReturnsExpectedDataSource() throws SQLException {
|
||||
this.dataSource = DataSourceBuilder.create().url("jdbc:oracle:thin:@localhost:1521:xe")
|
||||
.type(OracleDataSource.class).username("test").build();
|
||||
assertThat(this.dataSource).isInstanceOf(OracleDataSource.class);
|
||||
|
@ -112,7 +120,7 @@ class DataSourceBuilderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void dataSourceCanBeCreatedWithOracleUcpDataSource() {
|
||||
void buildWhenOracleUcpTypeSpecifiedReturnsExpectedDataSource() {
|
||||
this.dataSource = DataSourceBuilder.create().driverClassName("org.hsqldb.jdbc.JDBCDriver")
|
||||
.type(PoolDataSourceImpl.class).username("test").build();
|
||||
assertThat(this.dataSource).isInstanceOf(PoolDataSourceImpl.class);
|
||||
|
@ -122,7 +130,7 @@ class DataSourceBuilderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void dataSourceCanBeCreatedWithH2JdbcDataSource() {
|
||||
void buildWhenH2TypeSpecifiedReturnsExpectedDataSource() {
|
||||
this.dataSource = DataSourceBuilder.create().url("jdbc:h2:test").type(JdbcDataSource.class).username("test")
|
||||
.build();
|
||||
assertThat(this.dataSource).isInstanceOf(JdbcDataSource.class);
|
||||
|
@ -131,7 +139,7 @@ class DataSourceBuilderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void dataSourceCanBeCreatedWithPGDataSource() {
|
||||
void buildWhenPostgressTypeSpecifiedReturnsExpectedDataSource() {
|
||||
this.dataSource = DataSourceBuilder.create().url("jdbc:postgresql://localhost/test")
|
||||
.type(PGSimpleDataSource.class).username("test").build();
|
||||
assertThat(this.dataSource).isInstanceOf(PGSimpleDataSource.class);
|
||||
|
@ -140,11 +148,17 @@ class DataSourceBuilderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void dataSourceAliasesAreOnlyAppliedToRelevantDataSource() {
|
||||
this.dataSource = DataSourceBuilder.create().url("jdbc:h2:test").type(TestDataSource.class).username("test")
|
||||
.build();
|
||||
assertThat(this.dataSource).isInstanceOf(TestDataSource.class);
|
||||
TestDataSource testDataSource = (TestDataSource) this.dataSource;
|
||||
void buildWhenMappedTypeSpecifiedAndNoSuitableMappingThrowsException() {
|
||||
assertThatExceptionOfType(UnsupportedDataSourcePropertyException.class).isThrownBy(
|
||||
() -> DataSourceBuilder.create().type(OracleDataSource.class).driverClassName("com.example").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildWhenCustomSubclassTypeSpecifiedReturnsDataSourceWithOnlyBasePropertiesSet() {
|
||||
this.dataSource = DataSourceBuilder.create().url("jdbc:h2:test").type(CustomTomcatDataSource.class)
|
||||
.username("test").build();
|
||||
assertThat(this.dataSource).isInstanceOf(CustomTomcatDataSource.class);
|
||||
CustomTomcatDataSource testDataSource = (CustomTomcatDataSource) this.dataSource;
|
||||
assertThat(testDataSource.getUrl()).isEqualTo("jdbc:h2:test");
|
||||
assertThat(testDataSource.getJdbcUrl()).isNull();
|
||||
assertThat(testDataSource.getUsername()).isEqualTo("test");
|
||||
|
@ -153,6 +167,85 @@ class DataSourceBuilderTests {
|
|||
assertThat(testDataSource.getDriverClass()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildWhenCustomTypeSpecifiedReturnsDataSourceWithPropertiesSetViaReflection() {
|
||||
this.dataSource = DataSourceBuilder.create().type(CustomDataSource.class).username("test").password("secret")
|
||||
.url("jdbc:h2:test").driverClassName("com.example").build();
|
||||
assertThat(this.dataSource).isInstanceOf(CustomDataSource.class);
|
||||
CustomDataSource testDataSource = (CustomDataSource) this.dataSource;
|
||||
assertThat(testDataSource.getUrl()).isEqualTo("jdbc:h2:test");
|
||||
assertThat(testDataSource.getUsername()).isEqualTo("test");
|
||||
assertThat(testDataSource.getPassword()).isEqualTo("secret");
|
||||
assertThat(testDataSource.getDriverClassName()).isEqualTo("com.example");
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildWhenCustomTypeSpecifiedAndNoSuitableSetterThrowsException() {
|
||||
assertThatExceptionOfType(UnsupportedDataSourcePropertyException.class).isThrownBy(() -> DataSourceBuilder
|
||||
.create().type(LimitedCustomDataSource.class).driverClassName("com.example").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildWhenDerivedWithNewUrlReturnsNewDataSource() {
|
||||
HikariDataSource dataSource = new HikariDataSource();
|
||||
dataSource.setUsername("test");
|
||||
dataSource.setPassword("secret");
|
||||
dataSource.setJdbcUrl("jdbc:h2:test");
|
||||
HikariDataSource built = (HikariDataSource) DataSourceBuilder.derivedFrom(dataSource).url("jdbc:h2:test2")
|
||||
.build();
|
||||
assertThat(built.getUsername()).isEqualTo("test");
|
||||
assertThat(built.getPassword()).isEqualTo("secret");
|
||||
assertThat(built.getJdbcUrl()).isEqualTo("jdbc:h2:test2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildWhenDerivedWithNewUsernameAndPasswordReturnsNewDataSource() {
|
||||
HikariDataSource dataSource = new HikariDataSource();
|
||||
dataSource.setUsername("test");
|
||||
dataSource.setPassword("secret");
|
||||
dataSource.setJdbcUrl("jdbc:h2:test");
|
||||
DataSourceBuilder<?> builder = DataSourceBuilder.derivedFrom(dataSource);
|
||||
HikariDataSource built = (HikariDataSource) builder.username("test2").password("secret2").build();
|
||||
assertThat(built.getUsername()).isEqualTo("test2");
|
||||
assertThat(built.getPassword()).isEqualTo("secret2");
|
||||
assertThat(built.getJdbcUrl()).isEqualTo("jdbc:h2:test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildWhenDerivedFromOracleDataSourceWithPasswordNotSetThrowsException() throws Exception {
|
||||
oracle.jdbc.datasource.impl.OracleDataSource dataSource = new oracle.jdbc.datasource.impl.OracleDataSource();
|
||||
dataSource.setUser("test");
|
||||
dataSource.setPassword("secret");
|
||||
dataSource.setURL("example.com");
|
||||
assertThatExceptionOfType(UnsupportedDataSourcePropertyException.class)
|
||||
.isThrownBy(() -> DataSourceBuilder.derivedFrom(dataSource).url("example.org").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildWhenDerivedFromOracleDataSourceWithPasswordSetReturnsDataSource() throws Exception {
|
||||
oracle.jdbc.datasource.impl.OracleDataSource dataSource = new oracle.jdbc.datasource.impl.OracleDataSource();
|
||||
dataSource.setUser("test");
|
||||
dataSource.setPassword("secret");
|
||||
dataSource.setURL("example.com");
|
||||
DataSourceBuilder<?> builder = DataSourceBuilder.derivedFrom(dataSource);
|
||||
oracle.jdbc.datasource.impl.OracleDataSource built = (oracle.jdbc.datasource.impl.OracleDataSource) builder
|
||||
.username("test2").password("secret2").build();
|
||||
assertThat(built.getUser()).isEqualTo("test2");
|
||||
assertThat(built).extracting("password").extracting((opaque) -> ((OpaqueString) opaque).get())
|
||||
.isEqualTo("secret2");
|
||||
assertThat(built.getURL()).isEqualTo("example.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildWhenDerivedFromEmbeddedDatabase() {
|
||||
EmbeddedDatabase database = new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.HSQL).build();
|
||||
SimpleDriverDataSource built = (SimpleDriverDataSource) DataSourceBuilder.derivedFrom(database).username("test")
|
||||
.password("secret").build();
|
||||
assertThat(built.getUsername()).isEqualTo("test");
|
||||
assertThat(built.getPassword()).isEqualTo("secret");
|
||||
assertThat(built.getUrl()).startsWith("jdbc:hsqldb:mem");
|
||||
}
|
||||
|
||||
final class HidePackagesClassLoader extends URLClassLoader {
|
||||
|
||||
private final String[] hiddenPackages;
|
||||
|
@ -172,7 +265,7 @@ class DataSourceBuilderTests {
|
|||
|
||||
}
|
||||
|
||||
public static class TestDataSource extends org.apache.tomcat.jdbc.pool.DataSource {
|
||||
static class CustomTomcatDataSource extends org.apache.tomcat.jdbc.pool.DataSource {
|
||||
|
||||
private String jdbcUrl;
|
||||
|
||||
|
@ -180,30 +273,88 @@ class DataSourceBuilderTests {
|
|||
|
||||
private String driverClass;
|
||||
|
||||
public String getJdbcUrl() {
|
||||
String getJdbcUrl() {
|
||||
return this.jdbcUrl;
|
||||
}
|
||||
|
||||
public void setJdbcUrl(String jdbcUrl) {
|
||||
void setJdbcUrl(String jdbcUrl) {
|
||||
this.jdbcUrl = jdbcUrl;
|
||||
}
|
||||
|
||||
public String getUser() {
|
||||
String getUser() {
|
||||
return this.user;
|
||||
}
|
||||
|
||||
public void setUser(String user) {
|
||||
void setUser(String user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public String getDriverClass() {
|
||||
String getDriverClass() {
|
||||
return this.driverClass;
|
||||
}
|
||||
|
||||
public void setDriverClass(String driverClass) {
|
||||
void setDriverClass(String driverClass) {
|
||||
this.driverClass = driverClass;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class LimitedCustomDataSource extends AbstractDataSource {
|
||||
|
||||
private String username;
|
||||
|
||||
private String password;
|
||||
|
||||
private String url;
|
||||
|
||||
@Override
|
||||
public Connection getConnection() throws SQLException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection getConnection(String username, String password) throws SQLException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
String getUsername() {
|
||||
return this.username;
|
||||
}
|
||||
|
||||
void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
String getPassword() {
|
||||
return this.password;
|
||||
}
|
||||
|
||||
void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
String getUrl() {
|
||||
return this.url;
|
||||
}
|
||||
|
||||
void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class CustomDataSource extends LimitedCustomDataSource {
|
||||
|
||||
private String driverClassName;
|
||||
|
||||
String getDriverClassName() {
|
||||
return this.driverClassName;
|
||||
}
|
||||
|
||||
void setDriverClassName(String driverClassName) {
|
||||
this.driverClassName = driverClassName;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue