Restore support for spring.datasource.type

This commit restores the support of "spring.datasource.type". If this
property is set to an unsupported `DataSource` implementation, we now
properly create it again, rather than ignoring it because its value did
not match any value we support.

Since Spring Boot 1.4 now redirects `DataSource` implementation settings
to dedicated namespaces, the documentation has been updated to explicitly
mention how one can create such arrangement for an unsupported
implementation.

As a convenience, `DataSourceProperties` can initialize a
`DataSourceBuilder` based on its internal state, making it very easy for
anyone to create a `DataSource` from a `DataSourceProperties` instance.

Closes gh-6695
pull/6721/head
Stephane Nicoll 8 years ago
parent a6ef3741ef
commit 54a3ce8827

@ -30,6 +30,7 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome; import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@ -99,7 +100,8 @@ public class DataSourceAutoConfiguration {
@Conditional(PooledDataSourceCondition.class) @Conditional(PooledDataSourceCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class }) @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import({ DataSourceConfiguration.Tomcat.class, DataSourceConfiguration.Hikari.class, @Import({ DataSourceConfiguration.Tomcat.class, DataSourceConfiguration.Hikari.class,
DataSourceConfiguration.Dbcp.class, DataSourceConfiguration.Dbcp2.class }) DataSourceConfiguration.Dbcp.class, DataSourceConfiguration.Dbcp2.class,
DataSourceConfiguration.Generic.class})
protected static class PooledDataSourceConfiguration { protected static class PooledDataSourceConfiguration {
} }
@ -126,10 +128,28 @@ public class DataSourceAutoConfiguration {
} }
/**
* {@link AnyNestedCondition} that checks that either {@code spring.datasource.type}
* is set or {@link PooledDataSourceAvailableCondition} applies.
*/
static class PooledDataSourceCondition extends AnyNestedCondition {
PooledDataSourceCondition() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
@ConditionalOnProperty(prefix = "spring.datasource", name = "type")
static class ExplicitType { }
@Conditional(PooledDataSourceAvailableCondition.class)
static class PooledDataSourceAvailable { }
}
/** /**
* {@link Condition} to test if a supported connection pool is available. * {@link Condition} to test if a supported connection pool is available.
*/ */
static class PooledDataSourceCondition extends SpringBootCondition { static class PooledDataSourceAvailableCondition extends SpringBootCondition {
@Override @Override
public ConditionOutcome getMatchOutcome(ConditionContext context, public ConditionOutcome getMatchOutcome(ConditionContext context,

@ -21,6 +21,7 @@ import javax.sql.DataSource;
import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.HikariDataSource;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 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.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DatabaseDriver; import org.springframework.boot.jdbc.DatabaseDriver;
@ -38,10 +39,7 @@ abstract class DataSourceConfiguration {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
protected <T> T createDataSource(DataSourceProperties properties, protected <T> T createDataSource(DataSourceProperties properties,
Class<? extends DataSource> type) { Class<? extends DataSource> type) {
return (T) DataSourceBuilder.create(properties.getClassLoader()).type(type) return (T) properties.initializeDataSourceBuilder().type(type).build();
.driverClassName(properties.determineDriverClassName())
.url(properties.determineUrl()).username(properties.determineUsername())
.password(properties.determinePassword()).build();
} }
@ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class) @ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class)
@ -111,4 +109,15 @@ abstract class DataSourceConfiguration {
} }
} }
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type")
static class Generic {
@Bean
public DataSource dataSource(
DataSourceProperties properties) {
return properties.initializeDataSourceBuilder().build();
}
}
} }

@ -164,6 +164,19 @@ public class DataSourceProperties
.get(this.classLoader); .get(this.classLoader);
} }
/**
* Initialize a {@link DataSourceBuilder} with the state of this instance.
* @return a {@link DataSourceBuilder} initialized with the customizations
* defined on this instance
*/
public DataSourceBuilder initializeDataSourceBuilder() {
return DataSourceBuilder.create(getClassLoader())
.type(getType())
.driverClassName(determineDriverClassName())
.url(determineUrl()).username(determineUsername())
.password(determinePassword());
}
public String getName() { public String getName() {
return this.name; return this.name;
} }

@ -42,6 +42,7 @@ import org.springframework.boot.test.util.EnvironmentTestUtils;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.SimpleDriverDataSource;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
@ -184,18 +185,39 @@ public class DataSourceAutoConfigurationTests {
assertThat(pool.getUsername()).isEqualTo("sa"); assertThat(pool.getUsername()).isEqualTo("sa");
} }
/**
* This test makes sure that if no supported data source is present, a datasource
* is still created if "spring.datasource.type" is present.
*/
@Test @Test
public void explicitType() { public void explicitTypeNoSupportedDataSource() {
EnvironmentTestUtils.addEnvironment(this.context, EnvironmentTestUtils.addEnvironment(this.context,
"spring.datasource.driverClassName:org.hsqldb.jdbcDriver", "spring.datasource.driverClassName:org.hsqldb.jdbcDriver",
"spring.datasource.url:jdbc:hsqldb:mem:testdb", "spring.datasource.url:jdbc:hsqldb:mem:testdb",
"spring.datasource.type:" + HikariDataSource.class.getName()); "spring.datasource.type:" + SimpleDriverDataSource.class.getName());
this.context.setClassLoader(new HidePackagesClassLoader(
"org.apache.tomcat", "com.zaxxer.hikari", "org.apache.commons.dbcp",
"org.apache.commons.dbcp2"));
testExplicitType();
}
@Test
public void explicitTypeSupportedDataSource() {
EnvironmentTestUtils.addEnvironment(this.context,
"spring.datasource.driverClassName:org.hsqldb.jdbcDriver",
"spring.datasource.url:jdbc:hsqldb:mem:testdb",
"spring.datasource.type:" + SimpleDriverDataSource.class.getName());
testExplicitType();
}
private void testExplicitType() {
this.context.register(DataSourceAutoConfiguration.class, this.context.register(DataSourceAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class); PropertyPlaceholderAutoConfiguration.class);
this.context.refresh(); this.context.refresh();
assertThat(this.context.getBeansOfType(DataSource.class)).hasSize(1);
DataSource bean = this.context.getBean(DataSource.class); DataSource bean = this.context.getBean(DataSource.class);
assertThat(bean).isNotNull(); assertThat(bean).isNotNull();
assertThat(bean.getClass()).isEqualTo(HikariDataSource.class); assertThat(bean.getClass()).isEqualTo(SimpleDriverDataSource.class);
} }
@Test @Test
@ -232,21 +254,7 @@ public class DataSourceAutoConfigurationTests {
EnvironmentTestUtils.addEnvironment(this.context, EnvironmentTestUtils.addEnvironment(this.context,
"spring.datasource.driverClassName:org.hsqldb.jdbcDriver", "spring.datasource.driverClassName:org.hsqldb.jdbcDriver",
"spring.datasource.url:jdbc:hsqldb:mem:testdb"); "spring.datasource.url:jdbc:hsqldb:mem:testdb");
this.context.setClassLoader( this.context.setClassLoader(new HidePackagesClassLoader(hiddenPackages));
new URLClassLoader(new URL[0], getClass().getClassLoader()) {
@Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
for (String hiddenPackage : hiddenPackages) {
if (name.startsWith(hiddenPackage)) {
throw new ClassNotFoundException();
}
}
return super.loadClass(name, resolve);
}
});
this.context.register(DataSourceAutoConfiguration.class, this.context.register(DataSourceAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class); PropertyPlaceholderAutoConfiguration.class);
this.context.refresh(); this.context.refresh();
@ -312,4 +320,27 @@ public class DataSourceAutoConfigurationTests {
} }
private static final class HidePackagesClassLoader extends URLClassLoader {
private final String[] hiddenPackages;
private HidePackagesClassLoader(String... hiddenPackages) {
super(new URL[0], DataSourceAutoConfigurationTests.class.getClassLoader());
this.hiddenPackages = hiddenPackages;
}
@Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
for (String hiddenPackage : this.hiddenPackages) {
if (name.startsWith(hiddenPackage)) {
throw new ClassNotFoundException();
}
}
return super.loadClass(name, resolve);
}
}
} }

@ -1642,15 +1642,14 @@ a| `log4j2.json` +
[[howto-configure-a-datasource]] [[howto-configure-a-datasource]]
=== Configure a DataSource === Configure a DataSource
To override the default settings just define a `@Bean` of your own of type `DataSource`. To override the default settings just define a `@Bean` of your own of type `DataSource`.
Spring Boot provides a utility builder class `DataSourceBuilder` that can be used As explained in
to create one of the standard ones (if it is on the classpath), or you can just create <<spring-boot-features.adoc#boot-features-external-config-3rd-party-configuration>> you
your own, and bind it to a set of `Environment` properties as explained in can easily bind it to a set of `Environment` properties:
<<spring-boot-features.adoc#boot-features-external-config-3rd-party-configuration>>, e.g.
[source,java,indent=0,subs="verbatim,quotes,attributes"] [source,java,indent=0,subs="verbatim,quotes,attributes"]
---- ----
@Bean @Bean
@ConfigurationProperties(prefix="datasource.mine") @ConfigurationProperties(prefix="datasource.fancy")
public DataSource dataSource() { public DataSource dataSource() {
return new FancyDataSource(); return new FancyDataSource();
} }
@ -1658,11 +1657,38 @@ your own, and bind it to a set of `Environment` properties as explained in
[source,properties,indent=0] [source,properties,indent=0]
---- ----
datasource.mine.jdbcUrl=jdbc:h2:mem:mydb datasource.fancy.jdbcUrl=jdbc:h2:mem:mydb
datasource.mine.user=sa datasource.fancy.username=sa
datasource.fancy.poolSize=30
----
Spring Boot also provides a utility builder class `DataSourceBuilder` that can be used
to create one of the standard data sources (if it is on the classpath), or you can just
create your own. If you want to reuse the customizations of `DataSourceProperties`, you
can easily initialize a `DataSourceBuilder` from it:
[source,java,indent=0,subs="verbatim,quotes,attributes"]
----
@Bean
@ConfigurationProperties(prefix="datasource.mine")
public DataSource dataSource(DataSourceProperties properties) {
return properties.initializeDataSourceBuilder()
// additional customizations
.build();
}
----
[source,properties,indent=0]
----
spring.datasource.url=jdbc:h2:mem:mydb
spring.datasource.username=sa
datasource.mine.poolSize=30 datasource.mine.poolSize=30
---- ----
In this scenario, you keep the standard properties exposed by Spring Boot with your
custom `DataSource` arrangement. By adding `@ConfigurationProperties`, you can also
expose additional implementation-specific settings in a dedicated namespace.
See _<<spring-boot-features.adoc#boot-features-configure-datasource>>_ in the See _<<spring-boot-features.adoc#boot-features-configure-datasource>>_ in the
'`Spring Boot features`' section and the '`Spring Boot features`' section and the
{sc-spring-boot-autoconfigure}/jdbc/DataSourceAutoConfiguration.{sc-ext}[`DataSourceAutoConfiguration`] {sc-spring-boot-autoconfigure}/jdbc/DataSourceAutoConfiguration.{sc-ext}[`DataSourceAutoConfiguration`]

Loading…
Cancel
Save