From 0be9fd91ab4572445cab1cfa12c1566458fb3cfc Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 6 Nov 2024 18:18:32 +0000 Subject: [PATCH] Separate type customization from additional DataSource configuration Closes gh-43054 --- .../src/docs/asciidoc/howto/data-access.adoc | 48 ++++++++++--------- .../src/docs/asciidoc/howto/testing.adoc | 2 +- .../MyCompleteDataSourcesConfiguration.java | 7 ++- .../MyDataSourcesConfiguration.java | 7 ++- .../testing/slicetests/MyConfiguration.java | 8 ++-- .../slicetests/MyDatasourceConfiguration.java | 8 ++-- .../MyCompleteDataSourcesConfiguration.kt | 7 ++- .../MyDataSourcesConfiguration.kt | 7 ++- .../MyDataSourcesConfigurationTests.java | 14 +++--- 9 files changed, 54 insertions(+), 54 deletions(-) diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/data-access.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/data-access.adoc index 8568eed283f0..7de6d8d6c857 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/data-access.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/data-access.adoc @@ -52,10 +52,15 @@ The following example shows how to define a JDBC data source by setting properti pool-size: 30 ---- -However, there is a catch. -Because the actual type of the connection pool is not exposed, no keys are generated in the metadata for your custom `DataSource` and no completion is available in your IDE (because the `DataSource` interface exposes no properties). -Also, if you happen to have Hikari on the classpath, this basic setup does not work, because Hikari has no `url` property (but does have a `jdbcUrl` property). -In that case, you must rewrite your configuration as follows: +However, there is a catch due to the method's `DataSource` return type. +This hides the actual type of the connection pool so no configuration property metadata is generated for your custom `DataSource` and no auto-completion is available in your IDE. +To address this problem, use the builder's `type(Class)` method to specify the type of `DataSource` to be built and update the method's return type. +For example, the following shows how to create a `HikariDataSource` with `DataSourceBuilder`: + +include::code:simple/MyDataSourceConfiguration[] + +Unfortunately, this basic setup does not work because Hikari has no `url` property. +Instead, it has a `jdbc-url` property which means that you must rewrite your configuration as follows: [source,yaml,indent=0,subs="verbatim",configblocks] ---- @@ -67,22 +72,15 @@ In that case, you must rewrite your configuration as follows: pool-size: 30 ---- -You can fix that by forcing the connection pool to use and return a dedicated implementation rather than `DataSource`. -You cannot change the implementation at runtime, but the list of options will be explicit. - -The following example shows how to create a `HikariDataSource` with `DataSourceBuilder`: - -include::code:simple/MyDataSourceConfiguration[] - -You can even go further by leveraging what `DataSourceProperties` does for you -- that is, by providing a default embedded database with a sensible username and password if no URL is provided. -You can easily initialize a `DataSourceBuilder` from the state of any `DataSourceProperties` object, so you could also inject the DataSource that Spring Boot creates automatically. -However, that would split your configuration into two namespaces: `url`, `username`, `password`, `type`, and `driver` on `spring.datasource` and the rest on your custom namespace (`app.datasource`). -To avoid that, you can redefine a custom `DataSourceProperties` on your custom namespace, as shown in the following example: +To address this problem, make use of `DataSourceProperties` which will handle the `url` to `jdbc-url` translation for you. +You can initialize a `DataSourceBuilder` from the state of any `DataSourceProperties` object using its `initializeDataSourceBuilder()` method. +You could inject the `DataSourceProperties` that Spring Boot creates automatically, however, that would split your configuration across `+spring.datasource.*+` and `+app.datasource.*+`. +To avoid this, define a custom `DataSourceProperties` with a custom configuration properties prefix, as shown in the following example: include::code:configurable/MyDataSourceConfiguration[] -This setup puts you _in sync_ with what Spring Boot does for you by default, except that a dedicated connection pool is chosen (in code) and its settings are exposed in the `app.datasource.configuration` sub namespace. -Because `DataSourceProperties` is taking care of the `url`/`jdbcUrl` translation for you, you can configure it as follows: +This setup is equivalent to what Spring Boot does for you by default, except that the pool's type is specified in code and its settings are exposed as `app.datasource.configuration.*` properties. +`DataSourceProperties` takes care of the `url` to `jdbc-url` translation, so you can configure it as follows: [source,yaml,indent=0,subs="verbatim",configblocks] ---- @@ -95,13 +93,16 @@ Because `DataSourceProperties` is taking care of the `url`/`jdbcUrl` translation maximum-pool-size: 30 ---- +Note that, as the custom configuration specifies in code that Hikari should be used, `app.datasource.type` will have no effect. + +As described in "`<>`", `DataSourceBuilder` supports several different connection pools. +To use a pool other than Hikari, add it to the classpath, use the `type(Class)` method to specify the pool class to use, and update the `@Bean` method's return type to match. +This will also provide you with configuration property metadata for the specific connection pool that you've chosen. + TIP: Spring Boot will expose Hikari-specific settings to `spring.datasource.hikari`. This example uses a more generic `configuration` sub namespace as the example does not support multiple datasource implementations. -NOTE: Because your custom configuration chooses to go with Hikari, `app.datasource.type` has no effect. -In practice, the builder is initialized with whatever value you might set there and then overridden by the call to `.type()`. - -See "`<>`" in the "`Spring Boot features`" section and the {spring-boot-autoconfigure-module-code}/jdbc/DataSourceAutoConfiguration.java[`DataSourceAutoConfiguration`] class for more details. +See "`<>`" and the {spring-boot-autoconfigure-module-code}/jdbc/DataSourceAutoConfiguration.java[`DataSourceAutoConfiguration`] class for more details. @@ -142,9 +143,12 @@ You can apply the same concept to the secondary `DataSource` as well, as shown i include::code:MyCompleteDataSourcesConfiguration[] -The preceding example configures two data sources on custom namespaces with the same logic as Spring Boot would use in auto-configuration. +The preceding example configures two data sources on custom configuration property namespaces with the same logic as Spring Boot would use in auto-configuration. Note that each `configuration` sub namespace provides advanced settings based on the chosen implementation. +As with <<#howto.data-access.configure-custom-datasource,configuring a single custom `DataSource`>>, the type of one or both of the `DataSource` beans can be customized using the `type(Class)` method on `DataSourceBuilder`. +See "`<>`" for details of the supported types. + [[howto.data-access.spring-data-repositories]] diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/testing.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/testing.adoc index 53fc7dd28c5c..e32bd7e31488 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/testing.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/testing.adoc @@ -33,7 +33,7 @@ include::{docs-java}/howto/testing/slicetests/MyConfiguration.java[] For a `@WebMvcTest` for an application with the above `@Configuration` class, you might expect to have the `SecurityFilterChain` bean in the application context so that you can test if your controller endpoints are secured properly. However, `MyConfiguration` is not picked up by @WebMvcTest's component scanning filter because it doesn't match any of the types specified by the filter. You can include the configuration explicitly by annotating the test class with `@Import(MyConfiguration.class)`. -This will load all the beans in `MyConfiguration` including the `BasicDataSource` bean which isn't required when testing the web tier. +This will load all the beans in `MyConfiguration` including the `HikariDataSource` bean which isn't required when testing the web tier. Splitting the configuration class into two will enable importing just the security configuration. [source,java,indent=0,subs="verbatim"] diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyCompleteDataSourcesConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyCompleteDataSourcesConfiguration.java index f25837de7274..320d01d367ea 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyCompleteDataSourcesConfiguration.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyCompleteDataSourcesConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 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. @@ -17,7 +17,6 @@ package org.springframework.boot.docs.howto.dataaccess.configuretwodatasources; import com.zaxxer.hikari.HikariDataSource; -import org.apache.commons.dbcp2.BasicDataSource; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; @@ -51,9 +50,9 @@ public DataSourceProperties secondDataSourceProperties() { @Bean @ConfigurationProperties("app.datasource.second.configuration") - public BasicDataSource secondDataSource( + public HikariDataSource secondDataSource( @Qualifier("secondDataSourceProperties") DataSourceProperties secondDataSourceProperties) { - return secondDataSourceProperties.initializeDataSourceBuilder().type(BasicDataSource.class).build(); + return secondDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build(); } } diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyDataSourcesConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyDataSourcesConfiguration.java index c88ba5a7274d..4bcc16243088 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyDataSourcesConfiguration.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyDataSourcesConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 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. @@ -17,7 +17,6 @@ package org.springframework.boot.docs.howto.dataaccess.configuretwodatasources; import com.zaxxer.hikari.HikariDataSource; -import org.apache.commons.dbcp2.BasicDataSource; import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -45,8 +44,8 @@ public HikariDataSource firstDataSource(DataSourceProperties firstDataSourceProp @Bean @ConfigurationProperties("app.datasource.second") - public BasicDataSource secondDataSource() { - return DataSourceBuilder.create().type(BasicDataSource.class).build(); + public HikariDataSource secondDataSource() { + return DataSourceBuilder.create().type(HikariDataSource.class).build(); } } diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/testing/slicetests/MyConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/testing/slicetests/MyConfiguration.java index 8bb2f8590316..4c92a61d5cf4 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/testing/slicetests/MyConfiguration.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/testing/slicetests/MyConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 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. @@ -16,7 +16,7 @@ package org.springframework.boot.docs.howto.testing.slicetests; -import org.apache.commons.dbcp2.BasicDataSource; +import com.zaxxer.hikari.HikariDataSource; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; @@ -36,8 +36,8 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti @Bean @ConfigurationProperties("app.datasource.second") - public BasicDataSource secondDataSource() { - return DataSourceBuilder.create().type(BasicDataSource.class).build(); + public HikariDataSource secondDataSource() { + return DataSourceBuilder.create().type(HikariDataSource.class).build(); } } diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/testing/slicetests/MyDatasourceConfiguration.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/testing/slicetests/MyDatasourceConfiguration.java index 70506c4ee427..8258bdfa7aeb 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/testing/slicetests/MyDatasourceConfiguration.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/howto/testing/slicetests/MyDatasourceConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 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. @@ -16,7 +16,7 @@ package org.springframework.boot.docs.howto.testing.slicetests; -import org.apache.commons.dbcp2.BasicDataSource; +import com.zaxxer.hikari.HikariDataSource; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; @@ -28,8 +28,8 @@ public class MyDatasourceConfiguration { @Bean @ConfigurationProperties("app.datasource.second") - public BasicDataSource secondDataSource() { - return DataSourceBuilder.create().type(BasicDataSource.class).build(); + public HikariDataSource secondDataSource() { + return DataSourceBuilder.create().type(HikariDataSource.class).build(); } } diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyCompleteDataSourcesConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyCompleteDataSourcesConfiguration.kt index 24d142717a08..f1a56aaed783 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyCompleteDataSourcesConfiguration.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyCompleteDataSourcesConfiguration.kt @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 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. @@ -17,7 +17,6 @@ package org.springframework.boot.docs.howto.dataaccess.configuretwodatasources import com.zaxxer.hikari.HikariDataSource -import org.apache.commons.dbcp2.BasicDataSource import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties import org.springframework.boot.context.properties.ConfigurationProperties import org.springframework.context.annotation.Bean @@ -49,8 +48,8 @@ class MyCompleteDataSourcesConfiguration { @Bean @ConfigurationProperties("app.datasource.second.configuration") - fun secondDataSource(secondDataSourceProperties: DataSourceProperties): BasicDataSource { - return secondDataSourceProperties.initializeDataSourceBuilder().type(BasicDataSource::class.java).build() + fun secondDataSource(secondDataSourceProperties: DataSourceProperties): HikariDataSource { + return secondDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource::class.java).build() } } diff --git a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyDataSourcesConfiguration.kt b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyDataSourcesConfiguration.kt index 2e47fb8b10cf..96e68325b962 100644 --- a/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyDataSourcesConfiguration.kt +++ b/spring-boot-project/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyDataSourcesConfiguration.kt @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 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. @@ -17,7 +17,6 @@ package org.springframework.boot.docs.howto.dataaccess.configuretwodatasources import com.zaxxer.hikari.HikariDataSource -import org.apache.commons.dbcp2.BasicDataSource import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties import org.springframework.boot.context.properties.ConfigurationProperties import org.springframework.boot.jdbc.DataSourceBuilder @@ -44,8 +43,8 @@ class MyDataSourcesConfiguration { @Bean @ConfigurationProperties("app.datasource.second") - fun secondDataSource(): BasicDataSource { - return DataSourceBuilder.create().type(BasicDataSource::class.java).build() + fun secondDataSource(): HikariDataSource { + return DataSourceBuilder.create().type(HikariDataSource::class.java).build() } } diff --git a/spring-boot-project/spring-boot-docs/src/test/java/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyDataSourcesConfigurationTests.java b/spring-boot-project/spring-boot-docs/src/test/java/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyDataSourcesConfigurationTests.java index 6034bae7659d..60db50bac182 100644 --- a/spring-boot-project/spring-boot-docs/src/test/java/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyDataSourcesConfigurationTests.java +++ b/spring-boot-project/spring-boot-docs/src/test/java/org/springframework/boot/docs/howto/dataaccess/configuretwodatasources/MyDataSourcesConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 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. @@ -20,7 +20,7 @@ import javax.sql.DataSource; -import org.apache.commons.dbcp2.BasicDataSource; +import com.zaxxer.hikari.HikariDataSource; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -38,8 +38,8 @@ * @author Stephane Nicoll */ @ExtendWith(SpringExtension.class) -@SpringBootTest(properties = { "app.datasource.second.url=jdbc:h2:mem:bar;DB_CLOSE_DELAY=-1", - "app.datasource.second.max-total=42" }) +@SpringBootTest(properties = { "app.datasource.second.jdbc-url=jdbc:h2:mem:bar;DB_CLOSE_DELAY=-1", + "app.datasource.second.maximum-pool-size=42" }) @Import(MyDataSourcesConfiguration.class) class MyDataSourcesConfigurationTests { @@ -52,9 +52,9 @@ void validateConfiguration() throws SQLException { DataSource dataSource = this.context.getBean(DataSource.class); assertThat(this.context.getBean("firstDataSource")).isSameAs(dataSource); assertThat(dataSource.getConnection().getMetaData().getURL()).startsWith("jdbc:h2:mem:"); - BasicDataSource secondDataSource = this.context.getBean("secondDataSource", BasicDataSource.class); - assertThat(secondDataSource.getUrl()).isEqualTo("jdbc:h2:mem:bar;DB_CLOSE_DELAY=-1"); - assertThat(secondDataSource.getMaxTotal()).isEqualTo(42); + HikariDataSource secondDataSource = this.context.getBean("secondDataSource", HikariDataSource.class); + assertThat(secondDataSource.getJdbcUrl()).isEqualTo("jdbc:h2:mem:bar;DB_CLOSE_DELAY=-1"); + assertThat(secondDataSource.getMaximumPoolSize()).isEqualTo(42); } }