diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java index d6936471bd..512998bc03 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java @@ -30,6 +30,7 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 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.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -99,7 +100,8 @@ public class DataSourceAutoConfiguration { @Conditional(PooledDataSourceCondition.class) @ConditionalOnMissingBean({ DataSource.class, XADataSource.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 { } @@ -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. */ - static class PooledDataSourceCondition extends SpringBootCondition { + static class PooledDataSourceAvailableCondition extends SpringBootCondition { @Override public ConditionOutcome getMatchOutcome(ConditionContext context, diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration.java index 7f4490fcc4..ca43235119 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration.java @@ -21,6 +21,7 @@ import javax.sql.DataSource; import com.zaxxer.hikari.HikariDataSource; 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.DatabaseDriver; @@ -38,10 +39,7 @@ abstract class DataSourceConfiguration { @SuppressWarnings("unchecked") protected T createDataSource(DataSourceProperties properties, Class type) { - return (T) DataSourceBuilder.create(properties.getClassLoader()).type(type) - .driverClassName(properties.determineDriverClassName()) - .url(properties.determineUrl()).username(properties.determineUsername()) - .password(properties.determinePassword()).build(); + return (T) properties.initializeDataSourceBuilder().type(type).build(); } @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(); + } + } + } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java index 8f3419f9b3..ad5ca6bb92 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java @@ -164,6 +164,19 @@ public class DataSourceProperties .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() { return this.name; } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfigurationTests.java index 915da4743f..bf89963e21 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfigurationTests.java @@ -42,6 +42,7 @@ import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.datasource.SimpleDriverDataSource; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -184,18 +185,39 @@ public class DataSourceAutoConfigurationTests { 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 - public void explicitType() { + public void explicitTypeNoSupportedDataSource() { EnvironmentTestUtils.addEnvironment(this.context, "spring.datasource.driverClassName:org.hsqldb.jdbcDriver", "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, PropertyPlaceholderAutoConfiguration.class); this.context.refresh(); + assertThat(this.context.getBeansOfType(DataSource.class)).hasSize(1); DataSource bean = this.context.getBean(DataSource.class); assertThat(bean).isNotNull(); - assertThat(bean.getClass()).isEqualTo(HikariDataSource.class); + assertThat(bean.getClass()).isEqualTo(SimpleDriverDataSource.class); } @Test @@ -232,21 +254,7 @@ public class DataSourceAutoConfigurationTests { EnvironmentTestUtils.addEnvironment(this.context, "spring.datasource.driverClassName:org.hsqldb.jdbcDriver", "spring.datasource.url:jdbc:hsqldb:mem:testdb"); - this.context.setClassLoader( - 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.setClassLoader(new HidePackagesClassLoader(hiddenPackages)); this.context.register(DataSourceAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); 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); + } + + } + } diff --git a/spring-boot-docs/src/main/asciidoc/howto.adoc b/spring-boot-docs/src/main/asciidoc/howto.adoc index 16ab2c7c5c..c24965ec21 100644 --- a/spring-boot-docs/src/main/asciidoc/howto.adoc +++ b/spring-boot-docs/src/main/asciidoc/howto.adoc @@ -1642,15 +1642,14 @@ a| `log4j2.json` + [[howto-configure-a-datasource]] === Configure a 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 -to create one of the standard ones (if it is on the classpath), or you can just create -your own, and bind it to a set of `Environment` properties as explained in -<>, e.g. +As explained in +<> you +can easily bind it to a set of `Environment` properties: [source,java,indent=0,subs="verbatim,quotes,attributes"] ---- @Bean - @ConfigurationProperties(prefix="datasource.mine") + @ConfigurationProperties(prefix="datasource.fancy") public DataSource dataSource() { 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] ---- - datasource.mine.jdbcUrl=jdbc:h2:mem:mydb - datasource.mine.user=sa + datasource.fancy.jdbcUrl=jdbc:h2:mem:mydb + 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 ---- +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 _<>_ in the '`Spring Boot features`' section and the {sc-spring-boot-autoconfigure}/jdbc/DataSourceAutoConfiguration.{sc-ext}[`DataSourceAutoConfiguration`]