Skip to content

Commit 61e9fe8

Browse files
wilkinsonamhalbritterphilwebb
committed
Add ConnectionDetail support to R2DBC auto-configuration
Update R2DBC auto-configuration so that `R2dbcConnectionDetails` beans may be optionally used to provide connection details. See gh-34657 Co-Authored-By: Mortitz Halbritter <[email protected]> Co-Authored-By: Phillip Webb <[email protected]>
1 parent d09ac00 commit 61e9fe8

File tree

7 files changed

+231
-59
lines changed

7 files changed

+231
-59
lines changed

spring-boot-project/spring-boot-autoconfigure/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ dependencies {
237237
testImplementation("org.mockito:mockito-core")
238238
testImplementation("org.mockito:mockito-junit-jupiter")
239239
testImplementation("org.postgresql:postgresql")
240+
testImplementation("org.postgresql:r2dbc-postgresql")
240241
testImplementation("org.skyscreamer:jsonassert")
241242
testImplementation("org.springframework:spring-test")
242243
testImplementation("org.springframework:spring-core-test")

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/ConnectionFactoryBeanCreationFailureAnalyzer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ private FailureAnalysis getFailureAnalysis(ConnectionFactoryBeanCreationExceptio
5353
private String getDescription(ConnectionFactoryBeanCreationException cause) {
5454
StringBuilder description = new StringBuilder();
5555
description.append("Failed to configure a ConnectionFactory: ");
56-
if (!StringUtils.hasText(cause.getProperties().getUrl())) {
56+
if (!StringUtils.hasText(cause.getUrl())) {
5757
description.append("'url' attribute is not specified and ");
5858
}
5959
description.append(String.format("no embedded database could be configured.%n"));

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/ConnectionFactoryConfigurations.java

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,18 @@
5151
* @author Mark Paluch
5252
* @author Stephane Nicoll
5353
* @author Rodolpho S. Couto
54+
* @author Moritz Halbritter
55+
* @author Andy Wilkinson
56+
* @author Phillip Webb
5457
*/
5558
abstract class ConnectionFactoryConfigurations {
5659

57-
protected static ConnectionFactory createConnectionFactory(R2dbcProperties properties, ClassLoader classLoader,
60+
protected static ConnectionFactory createConnectionFactory(R2dbcProperties properties,
61+
R2dbcConnectionDetails connectionDetails, ClassLoader classLoader,
5862
List<ConnectionFactoryOptionsBuilderCustomizer> optionsCustomizers) {
5963
try {
6064
return org.springframework.boot.r2dbc.ConnectionFactoryBuilder
61-
.withOptions(new ConnectionFactoryOptionsInitializer().initialize(properties,
65+
.withOptions(new ConnectionFactoryOptionsInitializer().initialize(properties, connectionDetails,
6266
() -> EmbeddedDatabaseConnection.get(classLoader)))
6367
.configure((options) -> {
6468
for (ConnectionFactoryOptionsBuilderCustomizer optionsCustomizer : optionsCustomizers) {
@@ -87,10 +91,12 @@ static class PoolConfiguration {
8791
static class PooledConnectionFactoryConfiguration {
8892

8993
@Bean(destroyMethod = "dispose")
90-
ConnectionPool connectionFactory(R2dbcProperties properties, ResourceLoader resourceLoader,
94+
ConnectionPool connectionFactory(R2dbcProperties properties,
95+
ObjectProvider<R2dbcConnectionDetails> connectionDetails, ResourceLoader resourceLoader,
9196
ObjectProvider<ConnectionFactoryOptionsBuilderCustomizer> customizers) {
9297
ConnectionFactory connectionFactory = createConnectionFactory(properties,
93-
resourceLoader.getClassLoader(), customizers.orderedStream().toList());
98+
connectionDetails.getIfAvailable(), resourceLoader.getClassLoader(),
99+
customizers.orderedStream().toList());
94100
R2dbcProperties.Pool pool = properties.getPool();
95101
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
96102
ConnectionPoolConfiguration.Builder builder = ConnectionPoolConfiguration.builder(connectionFactory);
@@ -116,10 +122,11 @@ ConnectionPool connectionFactory(R2dbcProperties properties, ResourceLoader reso
116122
static class GenericConfiguration {
117123

118124
@Bean
119-
ConnectionFactory connectionFactory(R2dbcProperties properties, ResourceLoader resourceLoader,
125+
ConnectionFactory connectionFactory(R2dbcProperties properties,
126+
ObjectProvider<R2dbcConnectionDetails> connectionDetails, ResourceLoader resourceLoader,
120127
ObjectProvider<ConnectionFactoryOptionsBuilderCustomizer> customizers) {
121-
return createConnectionFactory(properties, resourceLoader.getClassLoader(),
122-
customizers.orderedStream().toList());
128+
return createConnectionFactory(properties, connectionDetails.getIfAvailable(),
129+
resourceLoader.getClassLoader(), customizers.orderedStream().toList());
123130
}
124131

125132
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/ConnectionFactoryOptionsInitializer.java

Lines changed: 25 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2022 the original author or authors.
2+
* Copyright 2012-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,12 +16,10 @@
1616

1717
package org.springframework.boot.autoconfigure.r2dbc;
1818

19-
import java.util.function.Predicate;
2019
import java.util.function.Supplier;
2120

2221
import io.r2dbc.spi.ConnectionFactoryOptions;
2322
import io.r2dbc.spi.ConnectionFactoryOptions.Builder;
24-
import io.r2dbc.spi.Option;
2523

2624
import org.springframework.beans.factory.BeanCreationException;
2725
import org.springframework.boot.r2dbc.EmbeddedDatabaseConnection;
@@ -31,52 +29,41 @@
3129
* Initialize a {@link Builder} based on {@link R2dbcProperties}.
3230
*
3331
* @author Stephane Nicoll
32+
* @author Moritz Halbritter
33+
* @author Andy Wilkinson
34+
* @author Phillip Webb
3435
*/
3536
class ConnectionFactoryOptionsInitializer {
3637

3738
/**
3839
* Initialize a {@link Builder ConnectionFactoryOptions.Builder} using the specified
3940
* properties.
4041
* @param properties the properties to use to initialize the builder
42+
* @param connectionDetails the connection details to use to initialize the builder
4143
* @param embeddedDatabaseConnection the embedded connection to use as a fallback
4244
* @return an initialized builder
4345
* @throws ConnectionFactoryBeanCreationException if no suitable connection could be
4446
* determined
4547
*/
46-
ConnectionFactoryOptions.Builder initialize(R2dbcProperties properties,
48+
ConnectionFactoryOptions.Builder initialize(R2dbcProperties properties, R2dbcConnectionDetails connectionDetails,
4749
Supplier<EmbeddedDatabaseConnection> embeddedDatabaseConnection) {
48-
if (StringUtils.hasText(properties.getUrl())) {
49-
return initializeRegularOptions(properties);
50+
if (connectionDetails != null) {
51+
return connectionDetails.getConnectionFactoryOptions().mutate();
5052
}
5153
EmbeddedDatabaseConnection embeddedConnection = embeddedDatabaseConnection.get();
5254
if (embeddedConnection != EmbeddedDatabaseConnection.NONE) {
5355
return initializeEmbeddedOptions(properties, embeddedConnection);
5456
}
55-
throw connectionFactoryBeanCreationException("Failed to determine a suitable R2DBC Connection URL", properties,
57+
throw connectionFactoryBeanCreationException("Failed to determine a suitable R2DBC Connection URL", null,
5658
embeddedConnection);
5759
}
5860

59-
private ConnectionFactoryOptions.Builder initializeRegularOptions(R2dbcProperties properties) {
60-
ConnectionFactoryOptions urlOptions = ConnectionFactoryOptions.parse(properties.getUrl());
61-
Builder optionsBuilder = urlOptions.mutate();
62-
configureIf(optionsBuilder, urlOptions, ConnectionFactoryOptions.USER, properties::getUsername,
63-
StringUtils::hasText);
64-
configureIf(optionsBuilder, urlOptions, ConnectionFactoryOptions.PASSWORD, properties::getPassword,
65-
StringUtils::hasText);
66-
configureIf(optionsBuilder, urlOptions, ConnectionFactoryOptions.DATABASE,
67-
() -> determineDatabaseName(properties), StringUtils::hasText);
68-
if (properties.getProperties() != null) {
69-
properties.getProperties().forEach((key, value) -> optionsBuilder.option(Option.valueOf(key), value));
70-
}
71-
return optionsBuilder;
72-
}
73-
7461
private Builder initializeEmbeddedOptions(R2dbcProperties properties,
7562
EmbeddedDatabaseConnection embeddedDatabaseConnection) {
7663
String url = embeddedDatabaseConnection.getUrl(determineEmbeddedDatabaseName(properties));
7764
if (url == null) {
78-
throw connectionFactoryBeanCreationException("Failed to determine a suitable R2DBC Connection URL",
79-
properties, embeddedDatabaseConnection);
65+
throw connectionFactoryBeanCreationException("Failed to determine a suitable R2DBC Connection URL", url,
66+
embeddedDatabaseConnection);
8067
}
8168
Builder builder = ConnectionFactoryOptions.parse(url).mutate();
8269
String username = determineEmbeddedUsername(properties);
@@ -89,6 +76,11 @@ private Builder initializeEmbeddedOptions(R2dbcProperties properties,
8976
return builder;
9077
}
9178

79+
private String determineEmbeddedDatabaseName(R2dbcProperties properties) {
80+
String databaseName = determineDatabaseName(properties);
81+
return (databaseName != null) ? databaseName : "testdb";
82+
}
83+
9284
private String determineDatabaseName(R2dbcProperties properties) {
9385
if (properties.isGenerateUniqueName()) {
9486
return properties.determineUniqueName();
@@ -99,30 +91,14 @@ private String determineDatabaseName(R2dbcProperties properties) {
9991
return null;
10092
}
10193

102-
private String determineEmbeddedDatabaseName(R2dbcProperties properties) {
103-
String databaseName = determineDatabaseName(properties);
104-
return (databaseName != null) ? databaseName : "testdb";
105-
}
106-
10794
private String determineEmbeddedUsername(R2dbcProperties properties) {
10895
String username = ifHasText(properties.getUsername());
10996
return (username != null) ? username : "sa";
11097
}
11198

112-
private <T extends CharSequence> void configureIf(Builder optionsBuilder, ConnectionFactoryOptions originalOptions,
113-
Option<T> option, Supplier<T> valueSupplier, Predicate<T> setIf) {
114-
if (originalOptions.hasOption(option)) {
115-
return;
116-
}
117-
T value = valueSupplier.get();
118-
if (setIf.test(value)) {
119-
optionsBuilder.option(option, value);
120-
}
121-
}
122-
12399
private ConnectionFactoryBeanCreationException connectionFactoryBeanCreationException(String message,
124-
R2dbcProperties properties, EmbeddedDatabaseConnection embeddedDatabaseConnection) {
125-
return new ConnectionFactoryBeanCreationException(message, properties, embeddedDatabaseConnection);
100+
String r2dbcUrl, EmbeddedDatabaseConnection embeddedDatabaseConnection) {
101+
return new ConnectionFactoryBeanCreationException(message, r2dbcUrl, embeddedDatabaseConnection);
126102
}
127103

128104
private String ifHasText(String candidate) {
@@ -131,23 +107,23 @@ private String ifHasText(String candidate) {
131107

132108
static class ConnectionFactoryBeanCreationException extends BeanCreationException {
133109

134-
private final R2dbcProperties properties;
110+
private final String url;
135111

136112
private final EmbeddedDatabaseConnection embeddedDatabaseConnection;
137113

138-
ConnectionFactoryBeanCreationException(String message, R2dbcProperties properties,
114+
ConnectionFactoryBeanCreationException(String message, String url,
139115
EmbeddedDatabaseConnection embeddedDatabaseConnection) {
140116
super(message);
141-
this.properties = properties;
117+
this.url = url;
142118
this.embeddedDatabaseConnection = embeddedDatabaseConnection;
143119
}
144120

145-
EmbeddedDatabaseConnection getEmbeddedDatabaseConnection() {
146-
return this.embeddedDatabaseConnection;
121+
String getUrl() {
122+
return this.url;
147123
}
148124

149-
R2dbcProperties getProperties() {
150-
return this.properties;
125+
EmbeddedDatabaseConnection getEmbeddedDatabaseConnection() {
126+
return this.embeddedDatabaseConnection;
151127
}
152128

153129
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcAutoConfiguration.java

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2022 the original author or authors.
2+
* Copyright 2012-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,16 +16,26 @@
1616

1717
package org.springframework.boot.autoconfigure.r2dbc;
1818

19+
import java.util.function.Predicate;
20+
import java.util.function.Supplier;
21+
1922
import io.r2dbc.spi.ConnectionFactory;
23+
import io.r2dbc.spi.ConnectionFactoryOptions;
24+
import io.r2dbc.spi.ConnectionFactoryOptions.Builder;
25+
import io.r2dbc.spi.Option;
2026

2127
import org.springframework.boot.autoconfigure.AutoConfiguration;
2228
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
2329
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
30+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
31+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
2432
import org.springframework.boot.autoconfigure.condition.ConditionalOnResource;
2533
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
2634
import org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration;
2735
import org.springframework.boot.context.properties.EnableConfigurationProperties;
36+
import org.springframework.context.annotation.Bean;
2837
import org.springframework.context.annotation.Import;
38+
import org.springframework.util.StringUtils;
2939

3040
/**
3141
* {@link EnableAutoConfiguration Auto-configuration} for R2DBC.
@@ -42,4 +52,63 @@
4252
ConnectionFactoryConfigurations.GenericConfiguration.class, ConnectionFactoryDependentConfiguration.class })
4353
public class R2dbcAutoConfiguration {
4454

55+
@Bean
56+
@ConditionalOnMissingBean(R2dbcConnectionDetails.class)
57+
@ConditionalOnProperty("spring.r2dbc.url")
58+
PropertiesR2dbcConnectionDetails propertiesR2dbcConnectionDetails(R2dbcProperties properties) {
59+
return new PropertiesR2dbcConnectionDetails(properties);
60+
}
61+
62+
/**
63+
* Adapts {@link R2dbcProperties} to {@link R2dbcConnectionDetails}.
64+
*/
65+
static class PropertiesR2dbcConnectionDetails implements R2dbcConnectionDetails {
66+
67+
private final R2dbcProperties properties;
68+
69+
PropertiesR2dbcConnectionDetails(R2dbcProperties properties) {
70+
this.properties = properties;
71+
}
72+
73+
@Override
74+
public ConnectionFactoryOptions getConnectionFactoryOptions() {
75+
ConnectionFactoryOptions urlOptions = ConnectionFactoryOptions.parse(this.properties.getUrl());
76+
Builder optionsBuilder = urlOptions.mutate();
77+
configureIf(optionsBuilder, urlOptions, ConnectionFactoryOptions.USER, this.properties::getUsername,
78+
StringUtils::hasText);
79+
configureIf(optionsBuilder, urlOptions, ConnectionFactoryOptions.PASSWORD, this.properties::getPassword,
80+
StringUtils::hasText);
81+
configureIf(optionsBuilder, urlOptions, ConnectionFactoryOptions.DATABASE,
82+
() -> determineDatabaseName(this.properties), StringUtils::hasText);
83+
if (this.properties.getProperties() != null) {
84+
this.properties.getProperties()
85+
.forEach((key, value) -> optionsBuilder.option(Option.valueOf(key), value));
86+
}
87+
return optionsBuilder.build();
88+
}
89+
90+
private <T extends CharSequence> void configureIf(Builder optionsBuilder,
91+
ConnectionFactoryOptions originalOptions, Option<T> option, Supplier<T> valueSupplier,
92+
Predicate<T> setIf) {
93+
if (originalOptions.hasOption(option)) {
94+
return;
95+
}
96+
T value = valueSupplier.get();
97+
if (setIf.test(value)) {
98+
optionsBuilder.option(option, value);
99+
}
100+
}
101+
102+
private String determineDatabaseName(R2dbcProperties properties) {
103+
if (properties.isGenerateUniqueName()) {
104+
return properties.determineUniqueName();
105+
}
106+
if (StringUtils.hasLength(properties.getName())) {
107+
return properties.getName();
108+
}
109+
return null;
110+
}
111+
112+
}
113+
45114
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2012-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.r2dbc;
18+
19+
import io.r2dbc.spi.ConnectionFactoryOptions;
20+
21+
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
22+
23+
/**
24+
* Details required to establish a connection to an SQL service using R2DBC.
25+
*
26+
* @author Moritz Halbritter
27+
* @author Andy Wilkinson
28+
* @author Phillip Webb
29+
* @since 3.1.0
30+
*/
31+
public interface R2dbcConnectionDetails extends ConnectionDetails {
32+
33+
/**
34+
* Connection factory options for connecting to the database.
35+
* @return the connection factory options
36+
*/
37+
ConnectionFactoryOptions getConnectionFactoryOptions();
38+
39+
}

0 commit comments

Comments
 (0)