Skip to content

Commit

Permalink
Separate type customization from additional DataSource configuration
Browse files Browse the repository at this point in the history
Closes gh-43054
  • Loading branch information
wilkinsona committed Nov 7, 2024
1 parent 94f2fab commit 0be9fd9
Show file tree
Hide file tree
Showing 9 changed files with 54 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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]
----
Expand All @@ -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]
----
Expand All @@ -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 "`<<data#data.sql.datasource.connection-pool>>`", `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 "`<<data#data.sql.datasource>>`" in the "`Spring Boot features`" section and the {spring-boot-autoconfigure-module-code}/jdbc/DataSourceAutoConfiguration.java[`DataSourceAutoConfiguration`] class for more details.
See "`<<data#data.sql.datasource>>`" and the {spring-boot-autoconfigure-module-code}/jdbc/DataSourceAutoConfiguration.java[`DataSourceAutoConfiguration`] class for more details.



Expand Down Expand Up @@ -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 "`<<data#data.sql.datasource.connection-pool>>`" for details of the supported types.



[[howto.data-access.spring-data-repositories]]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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;
Expand Down Expand Up @@ -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();
}

}
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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;
Expand Down Expand Up @@ -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();
}

}
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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;
Expand All @@ -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();
}

}
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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;
Expand All @@ -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();
}

}
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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()
}

}
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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
Expand All @@ -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()
}

}
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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;

Expand All @@ -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 {

Expand All @@ -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);
}

}

0 comments on commit 0be9fd9

Please sign in to comment.