Add ConnectionDetail support to JDBC auto-configuration

Update JDBC auto-configuration so that `JdbcConnectionDetails` beans
may be optionally used to provide connection details.

See gh-34657

Co-Authored-By: Mortitz Halbritter <mkammerer@vmware.com>
Co-Authored-By: Phillip Webb <pwebb@vmware.com>
This commit is contained in:
Andy Wilkinson 2023-03-23 23:21:55 -07:00 committed by Phillip Webb
parent aa91f2b8b6
commit d09ac00824
23 changed files with 1198 additions and 102 deletions

View File

@ -236,6 +236,7 @@ dependencies {
testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation("org.mockito:mockito-core")
testImplementation("org.mockito:mockito-junit-jupiter")
testImplementation("org.postgresql:postgresql")
testImplementation("org.skyscreamer:jsonassert")
testImplementation("org.springframework:spring-test")
testImplementation("org.springframework:spring-core-test")

View File

@ -47,6 +47,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.FlywayAutoConfigurationRuntimeHints;
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.FlywayDataSourceCondition;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails;
import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationPropertiesBinding;
@ -86,6 +87,7 @@ import org.springframework.util.StringUtils;
* @author Semyon Danilov
* @author Chris Bono
* @author Moritz Halbritter
* @author Andy Wilkinson
* @since 1.1.0
*/
@AutoConfiguration(after = { DataSourceAutoConfiguration.class, JdbcTemplateAutoConfiguration.class,
@ -125,7 +127,7 @@ public class FlywayAutoConfiguration {
ObjectProvider<FlywayConfigurationCustomizer> fluentConfigurationCustomizers,
ObjectProvider<JavaMigration> javaMigrations, ObjectProvider<Callback> callbacks) {
return flyway(properties, resourceLoader, dataSource, flywayDataSource, fluentConfigurationCustomizers,
javaMigrations, callbacks, new ResourceProviderCustomizer());
javaMigrations, callbacks, new ResourceProviderCustomizer(), null);
}
@Bean
@ -133,9 +135,15 @@ public class FlywayAutoConfiguration {
@FlywayDataSource ObjectProvider<DataSource> flywayDataSource,
ObjectProvider<FlywayConfigurationCustomizer> fluentConfigurationCustomizers,
ObjectProvider<JavaMigration> javaMigrations, ObjectProvider<Callback> callbacks,
ResourceProviderCustomizer resourceProviderCustomizer) {
ResourceProviderCustomizer resourceProviderCustomizer,
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
FluentConfiguration configuration = new FluentConfiguration(resourceLoader.getClassLoader());
configureDataSource(configuration, properties, flywayDataSource.getIfAvailable(), dataSource.getIfUnique());
JdbcConnectionDetails connectionDetails = (connectionDetailsProvider != null)
? connectionDetailsProvider.getIfAvailable() : null;
connectionDetails = (connectionDetails != null) ? connectionDetails
: new FlywayPropertiesJdbcConnectionDetails(properties);
configureDataSource(configuration, flywayDataSource.getIfAvailable(), dataSource.getIfUnique(),
connectionDetails);
configureProperties(configuration, properties);
configureCallbacks(configuration, callbacks.orderedStream().toList());
configureJavaMigrations(configuration, javaMigrations.orderedStream().toList());
@ -144,38 +152,41 @@ public class FlywayAutoConfiguration {
return configuration.load();
}
private void configureDataSource(FluentConfiguration configuration, FlywayProperties properties,
DataSource flywayDataSource, DataSource dataSource) {
DataSource migrationDataSource = getMigrationDataSource(properties, flywayDataSource, dataSource);
private void configureDataSource(FluentConfiguration configuration, DataSource flywayDataSource,
DataSource dataSource, JdbcConnectionDetails connectionDetails) {
DataSource migrationDataSource = getMigrationDataSource(flywayDataSource, dataSource, connectionDetails);
configuration.dataSource(migrationDataSource);
}
private DataSource getMigrationDataSource(FlywayProperties properties, DataSource flywayDataSource,
DataSource dataSource) {
private DataSource getMigrationDataSource(DataSource flywayDataSource, DataSource dataSource,
JdbcConnectionDetails connectionDetails) {
if (flywayDataSource != null) {
return flywayDataSource;
}
if (properties.getUrl() != null) {
String url = connectionDetails.getJdbcUrl();
if (url != null) {
DataSourceBuilder<?> builder = DataSourceBuilder.create().type(SimpleDriverDataSource.class);
builder.url(properties.getUrl());
applyCommonBuilderProperties(properties, builder);
builder.url(url);
applyConnectionDetails(connectionDetails, builder);
return builder.build();
}
if (properties.getUser() != null && dataSource != null) {
String user = connectionDetails.getUsername();
if (user != null && dataSource != null) {
DataSourceBuilder<?> builder = DataSourceBuilder.derivedFrom(dataSource)
.type(SimpleDriverDataSource.class);
applyCommonBuilderProperties(properties, builder);
applyConnectionDetails(connectionDetails, builder);
return builder.build();
}
Assert.state(dataSource != null, "Flyway migration DataSource missing");
return dataSource;
}
private void applyCommonBuilderProperties(FlywayProperties properties, DataSourceBuilder<?> builder) {
builder.username(properties.getUser());
builder.password(properties.getPassword());
if (StringUtils.hasText(properties.getDriverClassName())) {
builder.driverClassName(properties.getDriverClassName());
private void applyConnectionDetails(JdbcConnectionDetails connectionDetails, DataSourceBuilder<?> builder) {
builder.username(connectionDetails.getUsername());
builder.password(connectionDetails.getPassword());
String driverClassName = connectionDetails.getDriverClassName();
if (StringUtils.hasText(driverClassName)) {
builder.driverClassName(driverClassName);
}
}
@ -373,6 +384,11 @@ public class FlywayAutoConfiguration {
}
@ConditionalOnBean(JdbcConnectionDetails.class)
private static final class JdbcConnectionDetailsCondition {
}
@ConditionalOnProperty(prefix = "spring.flyway", name = "url")
private static final class FlywayUrlCondition {
@ -389,4 +405,37 @@ public class FlywayAutoConfiguration {
}
/**
* Adapts {@link FlywayProperties} to {@link JdbcConnectionDetails}.
*/
private static final class FlywayPropertiesJdbcConnectionDetails implements JdbcConnectionDetails {
private final FlywayProperties properties;
private FlywayPropertiesJdbcConnectionDetails(FlywayProperties properties) {
this.properties = properties;
}
@Override
public String getUsername() {
return this.properties.getUser();
}
@Override
public String getPassword() {
return this.properties.getPassword();
}
@Override
public String getJdbcUrl() {
return this.properties.getUrl();
}
@Override
public String getDriverClassName() {
return this.properties.getDriverClassName();
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2023 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.
@ -24,10 +24,13 @@ import com.zaxxer.hikari.HikariDataSource;
import oracle.jdbc.OracleConnection;
import oracle.ucp.jdbc.PoolDataSourceImpl;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.jdbc.DatabaseDriver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -40,6 +43,8 @@ import org.springframework.util.StringUtils;
* @author Phillip Webb
* @author Stephane Nicoll
* @author Fabio Grassi
* @author Moritz Halbritter
* @author Andy Wilkinson
*/
abstract class DataSourceConfiguration {
@ -48,6 +53,17 @@ abstract class DataSourceConfiguration {
return (T) properties.initializeDataSourceBuilder().type(type).build();
}
@SuppressWarnings("unchecked")
protected static <T> T createDataSource(JdbcConnectionDetails connectionDetails, Class<? extends DataSource> type,
ClassLoader classLoader) {
return (T) DataSourceBuilder.create(classLoader)
.url(connectionDetails.getJdbcUrl())
.username(connectionDetails.getUsername())
.password(connectionDetails.getPassword())
.type(type)
.build();
}
/**
* Tomcat Pool DataSource configuration.
*/
@ -58,13 +74,26 @@ abstract class DataSourceConfiguration {
matchIfMissing = true)
static class Tomcat {
@Bean
@ConditionalOnBean(JdbcConnectionDetails.class)
static TomcatJdbcConnectionDetailsBeanPostProcessor tomcatJdbcConnectionDetailsBeanPostProcessor(
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
return new TomcatJdbcConnectionDetailsBeanPostProcessor(connectionDetailsProvider);
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.tomcat")
org.apache.tomcat.jdbc.pool.DataSource dataSource(DataSourceProperties properties) {
org.apache.tomcat.jdbc.pool.DataSource dataSource = createDataSource(properties,
org.apache.tomcat.jdbc.pool.DataSource.class);
DatabaseDriver databaseDriver = DatabaseDriver.fromJdbcUrl(properties.determineUrl());
String validationQuery = databaseDriver.getValidationQuery();
org.apache.tomcat.jdbc.pool.DataSource dataSource(DataSourceProperties properties,
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
JdbcConnectionDetails connectionDetails = connectionDetailsProvider.getIfAvailable();
Class<? extends DataSource> dataSourceType = org.apache.tomcat.jdbc.pool.DataSource.class;
org.apache.tomcat.jdbc.pool.DataSource dataSource = (connectionDetails != null)
? createDataSource(connectionDetails, dataSourceType, properties.getClassLoader())
: createDataSource(properties, dataSourceType);
String validationQuery;
String url = (connectionDetails != null) ? connectionDetails.getJdbcUrl() : properties.determineUrl();
DatabaseDriver databaseDriver = DatabaseDriver.fromJdbcUrl(url);
validationQuery = databaseDriver.getValidationQuery();
if (validationQuery != null) {
dataSource.setTestOnBorrow(true);
dataSource.setValidationQuery(validationQuery);
@ -84,10 +113,21 @@ abstract class DataSourceConfiguration {
matchIfMissing = true)
static class Hikari {
@Bean
@ConditionalOnBean(JdbcConnectionDetails.class)
static HikariJdbcConnectionDetailsBeanPostProcessor jdbcConnectionDetailsHikariBeanPostProcessor(
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
return new HikariJdbcConnectionDetailsBeanPostProcessor(connectionDetailsProvider);
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari")
HikariDataSource dataSource(DataSourceProperties properties) {
HikariDataSource dataSource = createDataSource(properties, HikariDataSource.class);
HikariDataSource dataSource(DataSourceProperties properties,
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
JdbcConnectionDetails connectionDetails = connectionDetailsProvider.getIfAvailable();
HikariDataSource dataSource = (connectionDetails != null)
? createDataSource(connectionDetails, HikariDataSource.class, properties.getClassLoader())
: createDataSource(properties, HikariDataSource.class);
if (StringUtils.hasText(properties.getName())) {
dataSource.setPoolName(properties.getName());
}
@ -106,10 +146,22 @@ abstract class DataSourceConfiguration {
matchIfMissing = true)
static class Dbcp2 {
@Bean
@ConditionalOnBean(JdbcConnectionDetails.class)
static Dbcp2JdbcConnectionDetailsBeanPostProcessor dbcp2JdbcConnectionDetailsBeanPostProcessor(
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
return new Dbcp2JdbcConnectionDetailsBeanPostProcessor(connectionDetailsProvider);
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.dbcp2")
org.apache.commons.dbcp2.BasicDataSource dataSource(DataSourceProperties properties) {
return createDataSource(properties, org.apache.commons.dbcp2.BasicDataSource.class);
org.apache.commons.dbcp2.BasicDataSource dataSource(DataSourceProperties properties,
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
JdbcConnectionDetails connectionDetails = connectionDetailsProvider.getIfAvailable();
Class<? extends DataSource> dataSourceType = org.apache.commons.dbcp2.BasicDataSource.class;
return (connectionDetails != null)
? createDataSource(connectionDetails, dataSourceType, properties.getClassLoader())
: createDataSource(properties, dataSourceType);
}
}
@ -124,10 +176,21 @@ abstract class DataSourceConfiguration {
matchIfMissing = true)
static class OracleUcp {
@Bean
@ConditionalOnBean(JdbcConnectionDetails.class)
static OracleUcpJdbcConnectionDetailsBeanPostProcessor oracleUcpJdbcConnectionDetailsBeanPostProcessor(
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
return new OracleUcpJdbcConnectionDetailsBeanPostProcessor(connectionDetailsProvider);
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.oracleucp")
PoolDataSourceImpl dataSource(DataSourceProperties properties) throws SQLException {
PoolDataSourceImpl dataSource = createDataSource(properties, PoolDataSourceImpl.class);
PoolDataSourceImpl dataSource(DataSourceProperties properties,
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) throws SQLException {
JdbcConnectionDetails connectionDetails = connectionDetailsProvider.getIfAvailable();
PoolDataSourceImpl dataSource = (connectionDetails != null)
? createDataSource(connectionDetails, PoolDataSourceImpl.class, properties.getClassLoader())
: createDataSource(properties, PoolDataSourceImpl.class);
dataSource.setValidateConnectionOnBorrow(true);
if (StringUtils.hasText(properties.getName())) {
dataSource.setConnectionPoolName(properties.getName());
@ -146,7 +209,17 @@ abstract class DataSourceConfiguration {
static class Generic {
@Bean
DataSource dataSource(DataSourceProperties properties) {
DataSource dataSource(DataSourceProperties properties,
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
JdbcConnectionDetails connectionDetails = connectionDetailsProvider.getIfAvailable();
if (connectionDetails != null) {
return DataSourceBuilder.create(properties.getClassLoader())
.url(connectionDetails.getJdbcUrl())
.username(connectionDetails.getUsername())
.password(connectionDetails.getPassword())
.type(properties.getType())
.build();
}
return properties.initializeDataSourceBuilder().build();
}

View File

@ -0,0 +1,46 @@
/*
* Copyright 2012-2023 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 org.apache.commons.dbcp2.BasicDataSource;
import org.springframework.beans.factory.ObjectProvider;
/**
* Post-processes beans of type {@link BasicDataSource} and name 'dataSource' to apply the
* values from {@link JdbcConnectionDetails}.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class Dbcp2JdbcConnectionDetailsBeanPostProcessor extends JdbcConnectionDetailsBeanPostProcessor<BasicDataSource> {
Dbcp2JdbcConnectionDetailsBeanPostProcessor(ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
super(BasicDataSource.class, connectionDetailsProvider);
}
@Override
protected Object processDataSource(BasicDataSource dataSource, JdbcConnectionDetails connectionDetails) {
dataSource.setUrl(connectionDetails.getJdbcUrl());
dataSource.setUsername(connectionDetails.getUsername());
dataSource.setPassword(connectionDetails.getPassword());
dataSource.setDriverClassName(connectionDetails.getDriverClassName());
return dataSource;
}
}

View File

@ -0,0 +1,46 @@
/*
* Copyright 2012-2023 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 com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.ObjectProvider;
/**
* Post-processes beans of type {@link HikariDataSource} and name 'dataSource' to apply
* the values from {@link JdbcConnectionDetails}.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class HikariJdbcConnectionDetailsBeanPostProcessor extends JdbcConnectionDetailsBeanPostProcessor<HikariDataSource> {
HikariJdbcConnectionDetailsBeanPostProcessor(ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
super(HikariDataSource.class, connectionDetailsProvider);
}
@Override
protected Object processDataSource(HikariDataSource dataSource, JdbcConnectionDetails connectionDetails) {
dataSource.setJdbcUrl(connectionDetails.getJdbcUrl());
dataSource.setUsername(connectionDetails.getUsername());
dataSource.setPassword(connectionDetails.getPassword());
dataSource.setDriverClassName(connectionDetails.getDriverClassName());
return dataSource;
}
}

View File

@ -0,0 +1,74 @@
/*
* Copyright 2012-2023 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 org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
import org.springframework.boot.jdbc.DatabaseDriver;
/**
* Details required to establish a connection to an SQL service using JDBC.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
* @since 3.1.0
*/
public interface JdbcConnectionDetails extends ConnectionDetails {
/**
* Hostname for the database.
* @return the username for the database
*/
String getUsername();
/**
* Password for the database.
* @return the password for the database
*/
String getPassword();
/**
* JDBC url for the database.
* @return the JDBC url for the database
*/
String getJdbcUrl();
/**
* The name of the JDBC driver class. Defaults to the class name of the driver
* specified in the JDBC URL.
* @return the JDBC driver class name
* @see #getJdbcUrl()
* @see DatabaseDriver#fromJdbcUrl(String)
* @see DatabaseDriver#getDriverClassName()
*/
default String getDriverClassName() {
return DatabaseDriver.fromJdbcUrl(getJdbcUrl()).getDriverClassName();
}
/**
* Returns the name of the XA DataSource class. Defaults to the class name from the
* driver specified in the JDBC URL.
* @return the XA DataSource class name
* @see #getJdbcUrl()
* @see DatabaseDriver#fromJdbcUrl(String)
* @see DatabaseDriver#getXaDataSourceClassName()
*/
default String getXaDataSourceClassName() {
return DatabaseDriver.fromJdbcUrl(getJdbcUrl()).getXaDataSourceClassName();
}
}

View File

@ -0,0 +1,64 @@
/*
* Copyright 2012-2023 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 org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
/**
* Abstract base class for DataSource bean post processors which apply values from
* {@link JdbcConnectionDetails}. Acts on beans named 'dataSource' of type {@code T}.
*
* @param <T> type of the datasource
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
abstract class JdbcConnectionDetailsBeanPostProcessor<T> implements BeanPostProcessor, PriorityOrdered {
private final Class<T> dataSourceClass;
private final ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider;
JdbcConnectionDetailsBeanPostProcessor(Class<T> dataSourceClass,
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
this.dataSourceClass = dataSourceClass;
this.connectionDetailsProvider = connectionDetailsProvider;
}
@Override
@SuppressWarnings("unchecked")
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (this.dataSourceClass.isAssignableFrom(bean.getClass()) && "dataSource".equals(beanName)) {
JdbcConnectionDetails connectionDetails = this.connectionDetailsProvider.getObject();
return processDataSource((T) bean, connectionDetails);
}
return bean;
}
protected abstract Object processDataSource(T dataSource, JdbcConnectionDetails connectionDetails);
@Override
public int getOrder() {
// Runs after ConfigurationPropertiesBindingPostProcessor
return Ordered.HIGHEST_PRECEDENCE + 2;
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright 2012-2023 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 java.sql.SQLException;
import oracle.ucp.jdbc.PoolDataSourceImpl;
import org.springframework.beans.factory.ObjectProvider;
/**
* Post-processes beans of type {@link PoolDataSourceImpl} and name 'dataSource' to apply
* the values from {@link JdbcConnectionDetails}.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class OracleUcpJdbcConnectionDetailsBeanPostProcessor
extends JdbcConnectionDetailsBeanPostProcessor<PoolDataSourceImpl> {
OracleUcpJdbcConnectionDetailsBeanPostProcessor(ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
super(PoolDataSourceImpl.class, connectionDetailsProvider);
}
@Override
protected Object processDataSource(PoolDataSourceImpl dataSource, JdbcConnectionDetails connectionDetails) {
try {
dataSource.setURL(connectionDetails.getJdbcUrl());
dataSource.setUser(connectionDetails.getUsername());
dataSource.setPassword(connectionDetails.getPassword());
dataSource.setConnectionFactoryClassName(connectionDetails.getDriverClassName());
return dataSource;
}
catch (SQLException ex) {
throw new RuntimeException("Failed to set URL / user / password of datasource", ex);
}
}
}

View File

@ -0,0 +1,46 @@
/*
* Copyright 2012-2023 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 org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.beans.factory.ObjectProvider;
/**
* Post-processes beans of type {@link DataSource} and name 'dataSource' to apply the
* values from {@link JdbcConnectionDetails}.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class TomcatJdbcConnectionDetailsBeanPostProcessor extends JdbcConnectionDetailsBeanPostProcessor<DataSource> {
TomcatJdbcConnectionDetailsBeanPostProcessor(ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
super(DataSource.class, connectionDetailsProvider);
}
@Override
protected Object processDataSource(DataSource dataSource, JdbcConnectionDetails connectionDetails) {
dataSource.setUrl(connectionDetails.getJdbcUrl());
dataSource.setUsername(connectionDetails.getUsername());
dataSource.setPassword(connectionDetails.getPassword());
dataSource.setDriverClassName(connectionDetails.getDriverClassName());
return dataSource;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 the original author or authors.
* Copyright 2012-2023 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.
@ -40,7 +40,6 @@ import org.springframework.boot.context.properties.source.ConfigurationPropertyN
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.boot.jdbc.DatabaseDriver;
import org.springframework.boot.jdbc.XADataSourceWrapper;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
@ -54,6 +53,8 @@ import org.springframework.util.StringUtils;
* @author Phillip Webb
* @author Josh Long
* @author Madhura Bhave
* @author Moritz Halbritter
* @author Andy Wilkinson
* @since 1.2.0
*/
@AutoConfiguration(before = DataSourceAutoConfiguration.class)
@ -67,8 +68,10 @@ public class XADataSourceAutoConfiguration implements BeanClassLoaderAware {
@Bean
public DataSource dataSource(XADataSourceWrapper wrapper, DataSourceProperties properties,
ObjectProvider<XADataSource> xaDataSource) throws Exception {
return wrapper.wrapDataSource(xaDataSource.getIfAvailable(() -> createXaDataSource(properties)));
ObjectProvider<JdbcConnectionDetails> connectionDetails, ObjectProvider<XADataSource> xaDataSource)
throws Exception {
return wrapper.wrapDataSource(xaDataSource.getIfAvailable(() -> createXaDataSource(properties,
connectionDetails.getIfAvailable(() -> new PropertiesJdbcConnectionDetails(properties)))));
}
@Override
@ -76,14 +79,11 @@ public class XADataSourceAutoConfiguration implements BeanClassLoaderAware {
this.classLoader = classLoader;
}
private XADataSource createXaDataSource(DataSourceProperties properties) {
String className = properties.getXa().getDataSourceClassName();
if (!StringUtils.hasLength(className)) {
className = DatabaseDriver.fromJdbcUrl(properties.determineUrl()).getXaDataSourceClassName();
}
private XADataSource createXaDataSource(DataSourceProperties properties, JdbcConnectionDetails connectionDetails) {
String className = connectionDetails.getXaDataSourceClassName();
Assert.state(StringUtils.hasLength(className), "No XA DataSource class name specified");
XADataSource dataSource = createXaDataSourceInstance(className);
bindXaProperties(dataSource, properties);
bindXaProperties(dataSource, properties, connectionDetails);
return dataSource;
}
@ -99,18 +99,19 @@ public class XADataSourceAutoConfiguration implements BeanClassLoaderAware {
}
}
private void bindXaProperties(XADataSource target, DataSourceProperties dataSourceProperties) {
Binder binder = new Binder(getBinderSource(dataSourceProperties));
private void bindXaProperties(XADataSource target, DataSourceProperties dataSourceProperties,
JdbcConnectionDetails connectionDetails) {
Binder binder = new Binder(getBinderSource(dataSourceProperties, connectionDetails));
binder.bind(ConfigurationPropertyName.EMPTY, Bindable.ofInstance(target));
}
private ConfigurationPropertySource getBinderSource(DataSourceProperties dataSourceProperties) {
Map<Object, Object> properties = new HashMap<>();
properties.putAll(dataSourceProperties.getXa().getProperties());
properties.computeIfAbsent("user", (key) -> dataSourceProperties.determineUsername());
properties.computeIfAbsent("password", (key) -> dataSourceProperties.determinePassword());
private ConfigurationPropertySource getBinderSource(DataSourceProperties dataSourceProperties,
JdbcConnectionDetails connectionDetails) {
Map<Object, Object> properties = new HashMap<>(dataSourceProperties.getXa().getProperties());
properties.computeIfAbsent("user", (key) -> connectionDetails.getUsername());
properties.computeIfAbsent("password", (key) -> connectionDetails.getPassword());
try {
properties.computeIfAbsent("url", (key) -> dataSourceProperties.determineUrl());
properties.computeIfAbsent("url", (key) -> connectionDetails.getJdbcUrl());
}
catch (DataSourceBeanCreationException ex) {
// Continue as not all XA DataSource's require a URL
@ -121,4 +122,45 @@ public class XADataSourceAutoConfiguration implements BeanClassLoaderAware {
return source.withAliases(aliases);
}
/**
* Adapts {@link DataSourceProperties} to {@link JdbcConnectionDetails}.
*/
private static class PropertiesJdbcConnectionDetails implements JdbcConnectionDetails {
private final DataSourceProperties properties;
PropertiesJdbcConnectionDetails(DataSourceProperties properties) {
this.properties = properties;
}
@Override
public String getUsername() {
return this.properties.determineUsername();
}
@Override
public String getPassword() {
return this.properties.determinePassword();
}
@Override
public String getJdbcUrl() {
return this.properties.determineUrl();
}
@Override
public String getDriverClassName() {
return (this.properties.getDriverClassName() != null) ? this.properties.getDriverClassName()
: JdbcConnectionDetails.super.getDriverClassName();
}
@Override
public String getXaDataSourceClassName() {
return (this.properties.getXa().getDataSourceClassName() != null)
? this.properties.getXa().getDataSourceClassName()
: JdbcConnectionDetails.super.getXaDataSourceClassName();
}
}
}

View File

@ -32,6 +32,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration.LiquibaseAutoConfigurationRuntimeHints;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration.LiquibaseDataSourceCondition;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
@ -61,6 +62,7 @@ import org.springframework.util.StringUtils;
* @author András Deák
* @author Ferenc Gratzer
* @author Evgeniy Cheban
* @author Moritz Halbritter
* @since 1.1.0
*/
@AutoConfiguration(after = { DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class })
@ -83,38 +85,34 @@ public class LiquibaseAutoConfiguration {
@EnableConfigurationProperties(LiquibaseProperties.class)
public static class LiquibaseConfiguration {
private final LiquibaseProperties properties;
public LiquibaseConfiguration(LiquibaseProperties properties) {
this.properties = properties;
}
@Bean
public SpringLiquibase liquibase(ObjectProvider<DataSource> dataSource,
@LiquibaseDataSource ObjectProvider<DataSource> liquibaseDataSource) {
@LiquibaseDataSource ObjectProvider<DataSource> liquibaseDataSource, LiquibaseProperties properties,
ObjectProvider<JdbcConnectionDetails> connectionDetails) {
SpringLiquibase liquibase = createSpringLiquibase(liquibaseDataSource.getIfAvailable(),
dataSource.getIfUnique());
liquibase.setChangeLog(this.properties.getChangeLog());
liquibase.setClearCheckSums(this.properties.isClearChecksums());
liquibase.setContexts(this.properties.getContexts());
liquibase.setDefaultSchema(this.properties.getDefaultSchema());
liquibase.setLiquibaseSchema(this.properties.getLiquibaseSchema());
liquibase.setLiquibaseTablespace(this.properties.getLiquibaseTablespace());
liquibase.setDatabaseChangeLogTable(this.properties.getDatabaseChangeLogTable());
liquibase.setDatabaseChangeLogLockTable(this.properties.getDatabaseChangeLogLockTable());
liquibase.setDropFirst(this.properties.isDropFirst());
liquibase.setShouldRun(this.properties.isEnabled());
liquibase.setLabelFilter(this.properties.getLabelFilter());
liquibase.setChangeLogParameters(this.properties.getParameters());
liquibase.setRollbackFile(this.properties.getRollbackFile());
liquibase.setTestRollbackOnUpdate(this.properties.isTestRollbackOnUpdate());
liquibase.setTag(this.properties.getTag());
dataSource.getIfUnique(),
connectionDetails.getIfAvailable(() -> new LiquibasePropertiesJdbcConnectionDetails(properties)));
liquibase.setChangeLog(properties.getChangeLog());
liquibase.setClearCheckSums(properties.isClearChecksums());
liquibase.setContexts(properties.getContexts());
liquibase.setDefaultSchema(properties.getDefaultSchema());
liquibase.setLiquibaseSchema(properties.getLiquibaseSchema());
liquibase.setLiquibaseTablespace(properties.getLiquibaseTablespace());
liquibase.setDatabaseChangeLogTable(properties.getDatabaseChangeLogTable());
liquibase.setDatabaseChangeLogLockTable(properties.getDatabaseChangeLogLockTable());
liquibase.setDropFirst(properties.isDropFirst());
liquibase.setShouldRun(properties.isEnabled());
liquibase.setLabelFilter(properties.getLabelFilter());
liquibase.setChangeLogParameters(properties.getParameters());
liquibase.setRollbackFile(properties.getRollbackFile());
liquibase.setTestRollbackOnUpdate(properties.isTestRollbackOnUpdate());
liquibase.setTag(properties.getTag());
return liquibase;
}
private SpringLiquibase createSpringLiquibase(DataSource liquibaseDataSource, DataSource dataSource) {
LiquibaseProperties properties = this.properties;
DataSource migrationDataSource = getMigrationDataSource(liquibaseDataSource, dataSource, properties);
private SpringLiquibase createSpringLiquibase(DataSource liquibaseDataSource, DataSource dataSource,
JdbcConnectionDetails connectionDetails) {
DataSource migrationDataSource = getMigrationDataSource(liquibaseDataSource, dataSource, connectionDetails);
SpringLiquibase liquibase = (migrationDataSource == liquibaseDataSource
|| migrationDataSource == dataSource) ? new SpringLiquibase()
: new DataSourceClosingSpringLiquibase();
@ -123,31 +121,34 @@ public class LiquibaseAutoConfiguration {
}
private DataSource getMigrationDataSource(DataSource liquibaseDataSource, DataSource dataSource,
LiquibaseProperties properties) {
JdbcConnectionDetails connectionDetails) {
if (liquibaseDataSource != null) {
return liquibaseDataSource;
}
if (properties.getUrl() != null) {
String url = connectionDetails.getJdbcUrl();
if (url != null) {
DataSourceBuilder<?> builder = DataSourceBuilder.create().type(SimpleDriverDataSource.class);
builder.url(properties.getUrl());
applyCommonBuilderProperties(properties, builder);
builder.url(url);
applyConnectionDetails(connectionDetails, builder);
return builder.build();
}
if (properties.getUser() != null && dataSource != null) {
String user = connectionDetails.getUsername();
if (user != null && dataSource != null) {
DataSourceBuilder<?> builder = DataSourceBuilder.derivedFrom(dataSource)
.type(SimpleDriverDataSource.class);
applyCommonBuilderProperties(properties, builder);
applyConnectionDetails(connectionDetails, builder);
return builder.build();
}
Assert.state(dataSource != null, "Liquibase migration DataSource missing");
return dataSource;
}
private void applyCommonBuilderProperties(LiquibaseProperties properties, DataSourceBuilder<?> builder) {
builder.username(properties.getUser());
builder.password(properties.getPassword());
if (StringUtils.hasText(properties.getDriverClassName())) {
builder.driverClassName(properties.getDriverClassName());
private void applyConnectionDetails(JdbcConnectionDetails connectionDetails, DataSourceBuilder<?> builder) {
builder.username(connectionDetails.getUsername());
builder.password(connectionDetails.getPassword());
String driverClassName = connectionDetails.getDriverClassName();
if (StringUtils.hasText(driverClassName)) {
builder.driverClassName(driverClassName);
}
}
@ -164,6 +165,11 @@ public class LiquibaseAutoConfiguration {
}
@ConditionalOnBean(JdbcConnectionDetails.class)
private static final class JdbcConnectionDetailsCondition {
}
@ConditionalOnProperty(prefix = "spring.liquibase", name = "url")
private static final class LiquibaseUrlCondition {
@ -180,4 +186,37 @@ public class LiquibaseAutoConfiguration {
}
/**
* Adapts {@link LiquibaseProperties} to {@link JdbcConnectionDetails}.
*/
private static final class LiquibasePropertiesJdbcConnectionDetails implements JdbcConnectionDetails {
private final LiquibaseProperties properties;
private LiquibasePropertiesJdbcConnectionDetails(LiquibaseProperties properties) {
this.properties = properties;
}
@Override
public String getUsername() {
return this.properties.getUser();
}
@Override
public String getPassword() {
return this.properties.getPassword();
}
@Override
public String getJdbcUrl() {
return this.properties.getUrl();
}
@Override
public String getDriverClassName() {
return this.properties.getDriverClassName();
}
}
}

View File

@ -49,6 +49,7 @@ import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.FlywayAutoConfigurationRuntimeHints;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.jdbc.SchemaManagement;
@ -120,6 +121,16 @@ class FlywayAutoConfigurationTests {
});
}
@Test
void createsDataSourceWithNoDataSourceBeanAndJdbcConnectionDetails() {
this.contextRunner
.withUserConfiguration(JdbcConnectionDetailsConfiguration.class, MockFlywayMigrationStrategy.class)
.run((context) -> {
assertThat(context).hasSingleBean(Flyway.class);
assertThat(context.getBean(Flyway.class).getConfiguration().getDataSource()).isNotNull();
});
}
@Test
void backsOffWithFlywayUrlAndNoSpringJdbc() {
this.contextRunner.withPropertyValues("spring.flyway.url:jdbc:hsqldb:mem:" + UUID.randomUUID())
@ -137,6 +148,28 @@ class FlywayAutoConfigurationTests {
});
}
@Test
void createDataSourceWithJdbcConnectionDetails() {
this.contextRunner
.withUserConfiguration(EmbeddedDataSourceConfiguration.class, JdbcConnectionDetailsConfiguration.class,
MockFlywayMigrationStrategy.class)
.withPropertyValues("spring.flyway.url=jdbc:hsqldb:mem:flywaytest", "spring.flyway.user=some-user",
"spring.flyway.password=some-password",
"spring.flyway.driver-class-name=org.hsqldb.jdbc.JDBCDriver")
.run((context) -> {
assertThat(context).hasSingleBean(Flyway.class);
Flyway flyway = context.getBean(Flyway.class);
DataSource dataSource = flyway.getConfiguration().getDataSource();
assertThat(dataSource).isInstanceOf(SimpleDriverDataSource.class);
SimpleDriverDataSource simpleDriverDataSource = (SimpleDriverDataSource) dataSource;
assertThat(simpleDriverDataSource.getUrl())
.isEqualTo("jdbc:postgresql://database.example.com:12345/database-1");
assertThat(simpleDriverDataSource.getUsername()).isEqualTo("user-1");
assertThat(simpleDriverDataSource.getPassword()).isEqualTo("secret-1");
assertThat(simpleDriverDataSource.getDriver()).isInstanceOf(org.postgresql.Driver.class);
});
}
@Test
void createDataSourceWithUser() {
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
@ -200,6 +233,19 @@ class FlywayAutoConfigurationTests {
});
}
@Test
void flywayDataSourceIsUsedWhenJdbcConnectionDetailsIsAvailable() {
this.contextRunner
.withUserConfiguration(FlywayDataSourceConfiguration.class, EmbeddedDataSourceConfiguration.class,
JdbcConnectionDetailsConfiguration.class)
.run((context) -> {
assertThat(context).hasSingleBean(JdbcConnectionDetails.class);
assertThat(context).hasSingleBean(Flyway.class);
assertThat(context.getBean(Flyway.class).getConfiguration().getDataSource())
.isEqualTo(context.getBean("flywayDataSource"));
});
}
@Test
void flywayDataSourceWithoutDataSourceAutoConfiguration() {
this.contextRunner.withUserConfiguration(FlywayDataSourceConfiguration.class).run((context) -> {
@ -1036,4 +1082,31 @@ class FlywayAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
static class JdbcConnectionDetailsConfiguration {
@Bean
JdbcConnectionDetails jdbcConnectionDetails() {
return new JdbcConnectionDetails() {
@Override
public String getJdbcUrl() {
return "jdbc:postgresql://database.example.com:12345/database-1";
}
@Override
public String getUsername() {
return "user-1";
}
@Override
public String getPassword() {
return "secret-1";
}
};
}
}
}

View File

@ -36,6 +36,7 @@ import com.zaxxer.hikari.HikariDataSource;
import io.r2dbc.spi.ConnectionFactory;
import oracle.ucp.jdbc.PoolDataSourceImpl;
import org.apache.commons.dbcp2.BasicDataSource;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanCreationException;
@ -60,6 +61,9 @@ import static org.mockito.Mockito.mock;
*
* @author Dave Syer
* @author Stephane Nicoll
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class DataSourceAutoConfigurationTests {
@ -244,6 +248,41 @@ class DataSourceAutoConfigurationTests {
.run((context) -> assertThat(context).doesNotHaveBean(DataSourceScriptDatabaseInitializer.class));
}
@Test
void dbcp2UsesJdbcConnectionDetailsIfAvailable() {
ApplicationContextRunner runner = new ApplicationContextRunner()
.withPropertyValues("spring.datasource.type=org.apache.commons.dbcp2.BasicDataSource",
"spring.datasource.dbcp2.url=jdbc:broken", "spring.datasource.dbcp2.username=alice",
"spring.datasource.dbcp2.password=secret")
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class));
runner.withUserConfiguration(JdbcConnectionDetailsConfiguration.class).run((context) -> {
DataSource dataSource = context.getBean(DataSource.class);
assertThat(dataSource).asInstanceOf(InstanceOfAssertFactories.type(BasicDataSource.class))
.satisfies((dbcp2) -> {
assertThat(dbcp2.getUsername()).isEqualTo("user-1");
assertThat(dbcp2.getPassword()).isEqualTo("password-1");
assertThat(dbcp2.getDriverClassName()).isEqualTo("org.postgresql.Driver");
assertThat(dbcp2.getUrl()).isEqualTo("jdbc:postgresql://postgres.example.com:12345/database-1");
});
});
}
@Test
void genericUsesJdbcConnectionDetailsIfAvailable() {
ApplicationContextRunner runner = new ApplicationContextRunner()
.withPropertyValues("spring.datasource.type=" + TestDataSource.class.getName())
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class));
runner.withUserConfiguration(JdbcConnectionDetailsConfiguration.class).run((context) -> {
DataSource dataSource = context.getBean(DataSource.class);
assertThat(dataSource).isInstanceOf(TestDataSource.class);
TestDataSource source = (TestDataSource) dataSource;
assertThat(source.getUsername()).isEqualTo("user-1");
assertThat(source.getPassword()).isEqualTo("password-1");
assertThat(source.getDriver().getClass().getName()).isEqualTo("org.postgresql.Driver");
assertThat(source.getUrl()).isEqualTo("jdbc:postgresql://postgres.example.com:12345/database-1");
});
}
private static Function<ApplicationContextRunner, ApplicationContextRunner> hideConnectionPools() {
return (runner) -> runner.withClassLoader(new FilteredClassLoader("org.apache.tomcat", "com.zaxxer.hikari",
"org.apache.commons.dbcp2", "oracle.ucp.jdbc", "com.mchange"));
@ -259,6 +298,16 @@ class DataSourceAutoConfigurationTests {
});
}
@Configuration(proxyBeanMethods = false)
static class JdbcConnectionDetailsConfiguration {
@Bean
JdbcConnectionDetails sqlJdbcConnectionDetails() {
return new TestJdbcConnectionDetails();
}
}
@Configuration(proxyBeanMethods = false)
static class TestDataSourceConfiguration {

View File

@ -0,0 +1,50 @@
/*
* Copyright 2012-2023 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 org.apache.commons.dbcp2.BasicDataSource;
import org.junit.jupiter.api.Test;
import org.springframework.boot.jdbc.DatabaseDriver;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link Dbcp2JdbcConnectionDetailsBeanPostProcessor}.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class Dbcp2JdbcConnectionDetailsBeanPostProcessorTests {
@Test
void setUsernamePasswordAndUrl() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setUrl("will-be-overwritten");
dataSource.setUsername("will-be-overwritten");
dataSource.setPassword("will-be-overwritten");
dataSource.setDriverClassName("will-be-overwritten");
new Dbcp2JdbcConnectionDetailsBeanPostProcessor(null).processDataSource(dataSource,
new TestJdbcConnectionDetails());
assertThat(dataSource.getUrl()).isEqualTo("jdbc:postgresql://postgres.example.com:12345/database-1");
assertThat(dataSource.getUsername()).isEqualTo("user-1");
assertThat(dataSource.getPassword()).isEqualTo("password-1");
assertThat(dataSource.getDriverClassName()).isEqualTo(DatabaseDriver.POSTGRESQL.getDriverClassName());
}
}

View File

@ -19,10 +19,13 @@ package org.springframework.boot.autoconfigure.jdbc;
import javax.sql.DataSource;
import com.zaxxer.hikari.HikariDataSource;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
@ -31,9 +34,14 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Dave Syer
* @author Stephane Nicoll
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class HikariDataSourceConfigurationTests {
private static final String PREFIX = "spring.datasource.hikari.";
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
.withPropertyValues("spring.datasource.type=" + HikariDataSource.class.getName());
@ -49,8 +57,7 @@ class HikariDataSourceConfigurationTests {
@Test
void testDataSourcePropertiesOverridden() {
this.contextRunner
.withPropertyValues("spring.datasource.hikari.jdbc-url=jdbc:foo//bar/spam",
"spring.datasource.hikari.max-lifetime=1234")
.withPropertyValues(PREFIX + "jdbc-url=jdbc:foo//bar/spam", "spring.datasource.hikari.max-lifetime=1234")
.run((context) -> {
HikariDataSource ds = context.getBean(HikariDataSource.class);
assertThat(ds.getJdbcUrl()).isEqualTo("jdbc:foo//bar/spam");
@ -61,8 +68,7 @@ class HikariDataSourceConfigurationTests {
@Test
void testDataSourceGenericPropertiesOverridden() {
this.contextRunner
.withPropertyValues(
"spring.datasource.hikari.data-source-properties.dataSourceClassName=org.h2.JDBCDataSource")
.withPropertyValues(PREFIX + "data-source-properties.dataSourceClassName=org.h2.JDBCDataSource")
.run((context) -> {
HikariDataSource ds = context.getBean(HikariDataSource.class);
assertThat(ds.getDataSourceProperties().getProperty("dataSourceClassName"))
@ -90,12 +96,38 @@ class HikariDataSourceConfigurationTests {
@Test
void poolNameTakesPrecedenceOverName() {
this.contextRunner
.withPropertyValues("spring.datasource.name=myDS", "spring.datasource.hikari.pool-name=myHikariDS")
this.contextRunner.withPropertyValues("spring.datasource.name=myDS", PREFIX + "pool-name=myHikariDS")
.run((context) -> {
HikariDataSource ds = context.getBean(HikariDataSource.class);
assertThat(ds.getPoolName()).isEqualTo("myHikariDS");
});
}
@Test
void usesConnectionDetailsIfAvailable() {
this.contextRunner.withUserConfiguration(ConnectionDetailsConfiguration.class)
.withPropertyValues(PREFIX + "url=jdbc:broken", PREFIX + "username=alice", PREFIX + "password=secret")
.run((context) -> {
DataSource dataSource = context.getBean(DataSource.class);
assertThat(dataSource).asInstanceOf(InstanceOfAssertFactories.type(HikariDataSource.class))
.satisfies((hikari) -> {
assertThat(hikari.getUsername()).isEqualTo("user-1");
assertThat(hikari.getPassword()).isEqualTo("password-1");
assertThat(hikari.getDriverClassName()).isEqualTo("org.postgresql.Driver");
assertThat(hikari.getJdbcUrl())
.isEqualTo("jdbc:postgresql://postgres.example.com:12345/database-1");
});
});
}
@Configuration(proxyBeanMethods = false)
static class ConnectionDetailsConfiguration {
@Bean
JdbcConnectionDetails sqlConnectionDetails() {
return new TestJdbcConnectionDetails();
}
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright 2012-2023 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 com.zaxxer.hikari.HikariDataSource;
import org.junit.jupiter.api.Test;
import org.springframework.boot.jdbc.DatabaseDriver;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link HikariJdbcConnectionDetailsBeanPostProcessor}.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class HikariJdbcConnectionDetailsBeanPostProcessorTests {
@Test
void setUsernamePasswordAndUrl() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("will-be-overwritten");
dataSource.setUsername("will-be-overwritten");
dataSource.setPassword("will-be-overwritten");
dataSource.setDriverClassName(DatabaseDriver.H2.getDriverClassName());
new HikariJdbcConnectionDetailsBeanPostProcessor(null).processDataSource(dataSource,
new TestJdbcConnectionDetails());
assertThat(dataSource.getJdbcUrl()).isEqualTo("jdbc:postgresql://postgres.example.com:12345/database-1");
assertThat(dataSource.getUsername()).isEqualTo("user-1");
assertThat(dataSource.getPassword()).isEqualTo("password-1");
assertThat(dataSource.getDriverClassName()).isEqualTo(DatabaseDriver.POSTGRESQL.getDriverClassName());
}
}

View File

@ -22,10 +22,13 @@ import javax.sql.DataSource;
import oracle.ucp.jdbc.PoolDataSource;
import oracle.ucp.jdbc.PoolDataSourceImpl;
import oracle.ucp.util.OpaqueString;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
@ -34,9 +37,14 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Fabio Grassi
* @author Stephane Nicoll
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class OracleUcpDataSourceConfigurationTests {
private static final String PREFIX = "spring.datasource.oracleucp.";
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
.withPropertyValues("spring.datasource.type=" + PoolDataSource.class.getName());
@ -54,9 +62,7 @@ class OracleUcpDataSourceConfigurationTests {
@Test
void testDataSourcePropertiesOverridden() {
this.contextRunner
.withPropertyValues("spring.datasource.oracleucp.url=jdbc:foo//bar/spam",
"spring.datasource.oracleucp.max-idle-time=1234")
this.contextRunner.withPropertyValues(PREFIX + "url=jdbc:foo//bar/spam", PREFIX + "max-idle-time=1234")
.run((context) -> {
PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class);
assertThat(ds.getURL()).isEqualTo("jdbc:foo//bar/spam");
@ -66,11 +72,10 @@ class OracleUcpDataSourceConfigurationTests {
@Test
void testDataSourceConnectionPropertiesOverridden() {
this.contextRunner.withPropertyValues("spring.datasource.oracleucp.connection-properties.autoCommit=false")
.run((context) -> {
PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class);
assertThat(ds.getConnectionProperty("autoCommit")).isEqualTo("false");
});
this.contextRunner.withPropertyValues(PREFIX + "connection-properties.autoCommit=false").run((context) -> {
PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class);
assertThat(ds.getConnectionProperty("autoCommit")).isEqualTo("false");
});
}
@Test
@ -100,12 +105,38 @@ class OracleUcpDataSourceConfigurationTests {
@Test
void poolNameTakesPrecedenceOverName() {
this.contextRunner
.withPropertyValues("spring.datasource.name=myDS",
"spring.datasource.oracleucp.connection-pool-name=myOracleUcpDS")
.withPropertyValues("spring.datasource.name=myDS", PREFIX + "connection-pool-name=myOracleUcpDS")
.run((context) -> {
PoolDataSourceImpl ds = context.getBean(PoolDataSourceImpl.class);
assertThat(ds.getConnectionPoolName()).isEqualTo("myOracleUcpDS");
});
}
@Test
void usesJdbcConnectionDetailsIfAvailable() {
this.contextRunner.withUserConfiguration(ConnectionDetailsConfiguration.class)
.withPropertyValues(PREFIX + "url=jdbc:broken", PREFIX + "username=alice", PREFIX + "password=secret")
.run((context) -> {
DataSource dataSource = context.getBean(DataSource.class);
assertThat(dataSource).isInstanceOf(PoolDataSourceImpl.class);
PoolDataSourceImpl oracleUcp = (PoolDataSourceImpl) dataSource;
assertThat(oracleUcp.getUser()).isEqualTo("user-1");
assertThat(oracleUcp).extracting("password")
.extracting((o) -> ((OpaqueString) o).get())
.isEqualTo("password-1");
assertThat(oracleUcp.getConnectionFactoryClassName()).isEqualTo("org.postgresql.Driver");
assertThat(oracleUcp.getURL()).isEqualTo("jdbc:postgresql://postgres.example.com:12345/database-1");
});
}
@Configuration(proxyBeanMethods = false)
static class ConnectionDetailsConfiguration {
@Bean
JdbcConnectionDetails jdbcConnectionDetails() {
return new TestJdbcConnectionDetails();
}
}
}

View File

@ -0,0 +1,56 @@
/*
* Copyright 2012-2023 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 java.sql.SQLException;
import oracle.ucp.jdbc.PoolDataSourceImpl;
import oracle.ucp.util.OpaqueString;
import org.junit.jupiter.api.Test;
import org.springframework.boot.jdbc.DatabaseDriver;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link OracleUcpJdbcConnectionDetailsBeanPostProcessor}.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class OracleUcpJdbcConnectionDetailsBeanPostProcessorTests {
@Test
void setUsernamePasswordAndUrl() throws SQLException {
PoolDataSourceImpl dataSource = new PoolDataSourceImpl();
dataSource.setURL("will-be-overwritten");
dataSource.setUser("will-be-overwritten");
dataSource.setPassword("will-be-overwritten");
dataSource.setConnectionFactoryClassName("will-be-overwritten");
new OracleUcpJdbcConnectionDetailsBeanPostProcessor(null).processDataSource(dataSource,
new TestJdbcConnectionDetails());
assertThat(dataSource.getURL()).isEqualTo("jdbc:postgresql://postgres.example.com:12345/database-1");
assertThat(dataSource.getUser()).isEqualTo("user-1");
assertThat(dataSource).extracting("password")
.extracting((password) -> ((OpaqueString) password).get())
.isEqualTo("password-1");
assertThat(dataSource.getConnectionFactoryClassName())
.isEqualTo(DatabaseDriver.POSTGRESQL.getDriverClassName());
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright 2012-2023 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;
/**
* {@link JdbcConnectionDetails} used in tests.
*
* @author Moritz Halbritter
*/
class TestJdbcConnectionDetails implements JdbcConnectionDetails {
@Override
public String getJdbcUrl() {
return "jdbc:postgresql://postgres.example.com:12345/database-1";
}
@Override
public String getUsername() {
return "user-1";
}
@Override
public String getPassword() {
return "password-1";
}
}

View File

@ -24,9 +24,11 @@ import org.apache.tomcat.jdbc.pool.interceptor.SlowQueryReport;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
@ -41,6 +43,9 @@ import static org.junit.jupiter.api.Assertions.fail;
*
* @author Dave Syer
* @author Stephane Nicoll
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class TomcatDataSourceConfigurationTests {
@ -48,6 +53,10 @@ class TomcatDataSourceConfigurationTests {
private final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
.withPropertyValues("spring.datasource.type=" + org.apache.tomcat.jdbc.pool.DataSource.class.getName());
@BeforeEach
void init() {
TestPropertyValues.of(PREFIX + "initialize:false").applyTo(this.context);
@ -106,6 +115,32 @@ class TomcatDataSourceConfigurationTests {
assertThat(ds.getValidationInterval()).isEqualTo(3000L);
}
@Test
void usesJdbcConnectionDetailsIfAvailable() {
this.contextRunner.withUserConfiguration(ConnectionDetailsConfiguration.class)
.withPropertyValues(PREFIX + "url=jdbc:broken", PREFIX + "username=alice", PREFIX + "password=secret")
.run((context) -> {
DataSource dataSource = context.getBean(DataSource.class);
assertThat(dataSource).isInstanceOf(org.apache.tomcat.jdbc.pool.DataSource.class);
org.apache.tomcat.jdbc.pool.DataSource tomcat = (org.apache.tomcat.jdbc.pool.DataSource) dataSource;
assertThat(tomcat.getPoolProperties().getUsername()).isEqualTo("user-1");
assertThat(tomcat.getPoolProperties().getPassword()).isEqualTo("password-1");
assertThat(tomcat.getPoolProperties().getDriverClassName()).isEqualTo("org.postgresql.Driver");
assertThat(tomcat.getPoolProperties().getUrl())
.isEqualTo("jdbc:postgresql://postgres.example.com:12345/database-1");
});
}
@Configuration(proxyBeanMethods = false)
static class ConnectionDetailsConfiguration {
@Bean
JdbcConnectionDetails jdbcConnectionDetails() {
return new TestJdbcConnectionDetails();
}
}
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@EnableMBeanExport

View File

@ -0,0 +1,51 @@
/*
* Copyright 2012-2023 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 org.apache.tomcat.jdbc.pool.DataSource;
import org.junit.jupiter.api.Test;
import org.springframework.boot.jdbc.DatabaseDriver;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link TomcatJdbcConnectionDetailsBeanPostProcessor}.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class TomcatJdbcConnectionDetailsBeanPostProcessorTests {
@Test
void setUsernamePasswordAndUrl() {
DataSource dataSource = new DataSource();
dataSource.setUrl("will-be-overwritten");
dataSource.setUsername("will-be-overwritten");
dataSource.setPassword("will-be-overwritten");
dataSource.setDriverClassName("will-be-overwritten");
new TomcatJdbcConnectionDetailsBeanPostProcessor(null).processDataSource(dataSource,
new TestJdbcConnectionDetails());
assertThat(dataSource.getUrl()).isEqualTo("jdbc:postgresql://postgres.example.com:12345/database-1");
assertThat(dataSource.getUsername()).isEqualTo("user-1");
assertThat(dataSource.getPoolProperties().getPassword()).isEqualTo("password-1");
assertThat(dataSource.getPoolProperties().getDriverClassName())
.isEqualTo(DatabaseDriver.POSTGRESQL.getDriverClassName());
}
}

View File

@ -22,6 +22,7 @@ import javax.sql.XADataSource;
import com.ibm.db2.jcc.DB2XADataSource;
import org.hsqldb.jdbc.pool.JDBCXADataSource;
import org.junit.jupiter.api.Test;
import org.postgresql.xa.PGXADataSource;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.jdbc.XADataSourceWrapper;
@ -40,6 +41,8 @@ import static org.mockito.Mockito.mock;
* Tests for {@link XADataSourceAutoConfiguration}.
*
* @author Phillip Webb
* @author Moritz Halbritter
* @author Andy Wilkinson
*/
class XADataSourceAutoConfigurationTests {
@ -90,6 +93,20 @@ class XADataSourceAutoConfigurationTests {
assertThat(dataSource.getLoginTimeout()).isEqualTo(123);
}
@Test
void shouldUseConnectionDetailsIfAvailable() {
new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(XADataSourceAutoConfiguration.class))
.withUserConfiguration(FromProperties.class, JdbcConnectionDetailsConfiguration.class)
.run((context) -> {
MockXADataSourceWrapper wrapper = context.getBean(MockXADataSourceWrapper.class);
PGXADataSource dataSource = (PGXADataSource) wrapper.getXaDataSource();
assertThat(dataSource).isNotNull();
assertThat(dataSource.getUrl()).startsWith("jdbc:postgresql://postgres.example.com:12345/database-1");
assertThat(dataSource.getUser()).isEqualTo("user-1");
assertThat(dataSource.getPassword()).isEqualTo("password-1");
});
}
private ApplicationContext createContext(Class<?> configuration, String... env) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
TestPropertyValues.of(env).applyTo(context);
@ -98,6 +115,16 @@ class XADataSourceAutoConfigurationTests {
return context;
}
@Configuration(proxyBeanMethods = false)
static class JdbcConnectionDetailsConfiguration {
@Bean
JdbcConnectionDetails jdbcConnectionDetails() {
return new TestJdbcConnectionDetails();
}
}
@Configuration(proxyBeanMethods = false)
static class WrapExisting {

View File

@ -28,6 +28,7 @@ import java.util.function.Consumer;
import javax.sql.DataSource;
import com.zaxxer.hikari.HikariDataSource;
import liquibase.integration.spring.SpringLiquibase;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ -39,6 +40,7 @@ import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails;
import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration;
import org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration.LiquibaseAutoConfigurationRuntimeHints;
@ -75,6 +77,8 @@ import static org.assertj.core.api.Assertions.contentOf;
* @author Andrii Hrytsiuk
* @author Ferenc Gratzer
* @author Evgeniy Cheban
* @author Moritz Halbritter
* @author Phillip Webb
*/
@ExtendWith(OutputCaptureExtension.class)
class LiquibaseAutoConfigurationTests {
@ -97,6 +101,18 @@ class LiquibaseAutoConfigurationTests {
}));
}
@Test
void createsDataSourceWithNoDataSourceBeanAndJdbcConnectionDetails() {
this.contextRunner.withSystemProperties("shouldRun=false")
.withUserConfiguration(JdbcConnectionDetailsConfiguration.class)
.run(assertLiquibase((liquibase) -> {
SimpleDriverDataSource dataSource = (SimpleDriverDataSource) liquibase.getDataSource();
assertThat(dataSource.getUrl()).isEqualTo("jdbc:postgresql://database.example.com:12345/database-1");
assertThat(dataSource.getUsername()).isEqualTo("user-1");
assertThat(dataSource.getPassword()).isEqualTo("secret-1");
}));
}
@Test
void backsOffWithLiquibaseUrlAndNoSpringJdbc() {
this.contextRunner.withPropertyValues("spring.liquibase.url:jdbc:hsqldb:mem:" + UUID.randomUUID())
@ -116,6 +132,30 @@ class LiquibaseAutoConfigurationTests {
}));
}
@Test
void jdbcConnectionDetailsAreUsedIfAvailable() {
this.contextRunner.withSystemProperties("shouldRun=false")
.withUserConfiguration(EmbeddedDataSourceConfiguration.class, JdbcConnectionDetailsConfiguration.class)
.run(assertLiquibase((liquibase) -> {
SimpleDriverDataSource dataSource = (SimpleDriverDataSource) liquibase.getDataSource();
assertThat(dataSource.getUrl()).isEqualTo("jdbc:postgresql://database.example.com:12345/database-1");
assertThat(dataSource.getUsername()).isEqualTo("user-1");
assertThat(dataSource.getPassword()).isEqualTo("secret-1");
}));
}
@Test
void liquibaseDataSourceIsUsedOverJdbcConnectionDetails() {
this.contextRunner
.withUserConfiguration(LiquibaseDataSourceConfiguration.class, JdbcConnectionDetailsConfiguration.class)
.run(assertLiquibase((liquibase) -> {
HikariDataSource dataSource = (HikariDataSource) liquibase.getDataSource();
assertThat(dataSource.getJdbcUrl()).startsWith("jdbc:hsqldb:mem:liquibasetest");
assertThat(dataSource.getUsername()).isEqualTo("sa");
assertThat(dataSource.getPassword()).isNull();
}));
}
@Test
void changelogXml() {
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class)
@ -509,6 +549,33 @@ class LiquibaseAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
static class JdbcConnectionDetailsConfiguration {
@Bean
JdbcConnectionDetails jdbcConnectionDetails() {
return new JdbcConnectionDetails() {
@Override
public String getJdbcUrl() {
return "jdbc:postgresql://database.example.com:12345/database-1";
}
@Override
public String getUsername() {
return "user-1";
}
@Override
public String getPassword() {
return "secret-1";
}
};
}
}
static class CustomH2Driver extends org.h2.Driver {
}