Update to core r2dbc support

This commit adapts the auto-configuration for the new core r2dbc support
in Spring Framework and provides auto-configuration for
R2dbcEntityOperations.

Closes gh-22708
This commit is contained in:
Stephane Nicoll
2020-08-03 18:25:13 +02:00
parent 2b995324e4
commit 36fb86a110
18 changed files with 154 additions and 46 deletions

View File

@@ -20,8 +20,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import io.r2dbc.spi.ConnectionFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@@ -34,15 +32,14 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.r2dbc.convert.MappingR2dbcConverter;
import org.springframework.data.r2dbc.convert.R2dbcCustomConversions;
import org.springframework.data.r2dbc.core.DatabaseClient;
import org.springframework.data.r2dbc.core.DefaultReactiveDataAccessStrategy;
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy;
import org.springframework.data.r2dbc.dialect.DialectResolver;
import org.springframework.data.r2dbc.dialect.R2dbcDialect;
import org.springframework.data.r2dbc.mapping.R2dbcMappingContext;
import org.springframework.data.r2dbc.support.R2dbcExceptionSubclassTranslator;
import org.springframework.data.r2dbc.support.R2dbcExceptionTranslator;
import org.springframework.data.relational.core.mapping.NamingStrategy;
import org.springframework.r2dbc.core.DatabaseClient;
/**
* {@link EnableAutoConfiguration Auto-configuration} for {@link DatabaseClient}.
@@ -52,24 +49,21 @@ import org.springframework.data.relational.core.mapping.NamingStrategy;
* @since 2.3.0
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(DatabaseClient.class)
@ConditionalOnMissingBean(DatabaseClient.class)
@ConditionalOnSingleCandidate(ConnectionFactory.class)
@ConditionalOnClass({ DatabaseClient.class, R2dbcEntityTemplate.class })
@ConditionalOnSingleCandidate(DatabaseClient.class)
@AutoConfigureAfter(R2dbcAutoConfiguration.class)
public class R2dbcDataAutoConfiguration {
private final ConnectionFactory connectionFactory;
private final DatabaseClient databaseClient;
public R2dbcDataAutoConfiguration(ConnectionFactory connectionFactory) {
this.connectionFactory = connectionFactory;
public R2dbcDataAutoConfiguration(DatabaseClient databaseClient) {
this.databaseClient = databaseClient;
}
@Bean
@ConditionalOnMissingBean
public DatabaseClient r2dbcDatabaseClient(ReactiveDataAccessStrategy dataAccessStrategy,
R2dbcExceptionTranslator exceptionTranslator) {
return DatabaseClient.builder().connectionFactory(this.connectionFactory).dataAccessStrategy(dataAccessStrategy)
.exceptionTranslator(exceptionTranslator).build();
public R2dbcEntityTemplate r2dbcEntityTemplate(ReactiveDataAccessStrategy reactiveDataAccessStrategy) {
return new R2dbcEntityTemplate(this.databaseClient, reactiveDataAccessStrategy);
}
@Bean
@@ -87,13 +81,13 @@ public class R2dbcDataAutoConfiguration {
public ReactiveDataAccessStrategy reactiveDataAccessStrategy(R2dbcMappingContext mappingContext,
R2dbcCustomConversions r2dbcCustomConversions) {
MappingR2dbcConverter converter = new MappingR2dbcConverter(mappingContext, r2dbcCustomConversions);
return new DefaultReactiveDataAccessStrategy(DialectResolver.getDialect(this.connectionFactory), converter);
return new DefaultReactiveDataAccessStrategy(getDialect(), converter);
}
@Bean
@ConditionalOnMissingBean
public R2dbcCustomConversions r2dbcCustomConversions() {
R2dbcDialect dialect = DialectResolver.getDialect(this.connectionFactory);
R2dbcDialect dialect = getDialect();
List<Object> converters = new ArrayList<>(dialect.getConverters());
converters.addAll(R2dbcCustomConversions.STORE_CONVERTERS);
return new R2dbcCustomConversions(
@@ -101,10 +95,8 @@ public class R2dbcDataAutoConfiguration {
Collections.emptyList());
}
@Bean
@ConditionalOnMissingBean
public R2dbcExceptionTranslator r2dbcExceptionTranslator() {
return new R2dbcExceptionSubclassTranslator();
private R2dbcDialect getDialect() {
return DialectResolver.getDialect(this.databaseClient.getConnectionFactory());
}
}

View File

@@ -26,10 +26,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.r2dbc.core.DatabaseClient;
import org.springframework.data.r2dbc.repository.R2dbcRepository;
import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;
import org.springframework.data.r2dbc.repository.support.R2dbcRepositoryFactoryBean;
import org.springframework.r2dbc.core.DatabaseClient;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Spring Data R2DBC Repositories.

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2012-2020 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.r2dbc;
import io.r2dbc.spi.ConnectionFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.r2dbc.core.DatabaseClient;
/**
* Configuration of the R2DBC infrastructure based on a {@link ConnectionFactory}.
*
* @author Stephane Nicoll
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(DatabaseClient.class)
@ConditionalOnSingleCandidate(ConnectionFactory.class)
class ConnectionFactoryDependentConfiguration {
@Bean
@ConditionalOnMissingBean
DatabaseClient r2dbcDatabaseClient(ConnectionFactory connectionFactory) {
return DatabaseClient.builder().connectionFactory(connectionFactory).build();
}
}

View File

@@ -37,7 +37,8 @@ import org.springframework.context.annotation.Import;
@ConditionalOnClass(ConnectionFactory.class)
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
@EnableConfigurationProperties(R2dbcProperties.class)
@Import({ ConnectionFactoryConfigurations.Pool.class, ConnectionFactoryConfigurations.Generic.class })
@Import({ ConnectionFactoryConfigurations.Pool.class, ConnectionFactoryConfigurations.Generic.class,
ConnectionFactoryDependentConfiguration.class })
public class R2dbcAutoConfiguration {
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.data.r2dbc;
package org.springframework.boot.autoconfigure.r2dbc;
import io.r2dbc.spi.ConnectionFactory;
@@ -28,7 +28,7 @@ import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfigu
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.data.r2dbc.connectionfactory.R2dbcTransactionManager;
import org.springframework.r2dbc.connection.R2dbcTransactionManager;
import org.springframework.transaction.ReactiveTransactionManager;
/**

View File

@@ -57,7 +57,6 @@ org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfigura
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
@@ -106,6 +105,7 @@ org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration,\
org.springframework.boot.autoconfigure.r2dbc.R2dbcTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketRequesterAutoConfiguration,\
org.springframework.boot.autoconfigure.rsocket.RSocketServerAutoConfiguration,\

View File

@@ -21,7 +21,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.data.r2dbc.core.DatabaseClient;
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
import static org.assertj.core.api.Assertions.assertThat;
@@ -36,8 +36,8 @@ class R2dbcDataAutoConfigurationTests {
.withConfiguration(AutoConfigurations.of(R2dbcAutoConfiguration.class, R2dbcDataAutoConfiguration.class));
@Test
void databaseClientExists() {
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(DatabaseClient.class));
void r2dbcEntityTemplateIsConfigured() {
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(R2dbcEntityTemplate.class));
}
}

View File

@@ -23,19 +23,21 @@ import reactor.test.StepVerifier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage;
import org.springframework.boot.autoconfigure.data.empty.EmptyDataPackage;
import org.springframework.boot.autoconfigure.data.r2dbc.city.City;
import org.springframework.boot.autoconfigure.data.r2dbc.city.CityRepository;
import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.data.r2dbc.connectionfactory.init.ResourceDatabasePopulator;
import org.springframework.data.r2dbc.core.DatabaseClient;
import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;
import org.springframework.data.r2dbc.repository.config.R2dbcRepositoryConfigurationExtension;
import org.springframework.data.repository.Repository;
import org.springframework.r2dbc.connection.init.ResourceDatabasePopulator;
import org.springframework.r2dbc.core.DatabaseClient;
import static org.assertj.core.api.Assertions.assertThat;
@@ -58,6 +60,7 @@ class R2dbcRepositoriesAutoConfigurationTests {
@Test
void backsOffWithNoDatabaseClientOperations() {
this.contextRunner.withConfiguration(AutoConfigurations.of(R2dbcAutoConfiguration.class))
.withClassLoader(new FilteredClassLoader("org.springframework.r2dbc"))
.withUserConfiguration(TestConfiguration.class).run((context) -> {
assertThat(context).doesNotHaveBean(DatabaseClient.class);
assertThat(context).doesNotHaveBean(R2dbcRepositoryConfigurationExtension.class);
@@ -105,7 +108,7 @@ class R2dbcRepositoriesAutoConfigurationTests {
ResourceLoader resourceLoader = new DefaultResourceLoader();
Resource[] scripts = new Resource[] { resourceLoader.getResource("classpath:data-city-schema.sql"),
resourceLoader.getResource("classpath:city.sql") };
new ResourceDatabasePopulator(scripts).execute(connectionFactory).block();
new ResourceDatabasePopulator(scripts).populate(connectionFactory).block();
}
}
@@ -117,6 +120,7 @@ class R2dbcRepositoriesAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
@TestAutoConfigurationPackage(EmptyDataPackage.class)
static class EmptyConfiguration {
}

View File

@@ -39,6 +39,7 @@ import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.r2dbc.core.DatabaseClient;
import static org.assertj.core.api.Assertions.assertThat;
@@ -240,6 +241,24 @@ class R2dbcAutoConfigurationTests {
.doesNotHaveBean(DataSource.class));
}
@Test
void databaseClientIsConfigured() {
this.contextRunner.withPropertyValues("spring.r2dbc.url:r2dbc:h2:mem:///" + randomDatabaseName())
.run((context) -> {
assertThat(context).hasSingleBean(ConnectionFactory.class).hasSingleBean(DatabaseClient.class);
assertThat(context.getBean(DatabaseClient.class).getConnectionFactory())
.isSameAs(context.getBean(ConnectionFactory.class));
});
}
@Test
void databaseClientBacksOffIfSpringR2dbcIsNotAvailable() {
this.contextRunner.withClassLoader(new FilteredClassLoader("org.springframework.r2dbc"))
.withPropertyValues("spring.r2dbc.url:r2dbc:h2:mem:///" + randomDatabaseName())
.run((context) -> assertThat(context).hasSingleBean(ConnectionFactory.class)
.doesNotHaveBean(DatabaseClient.class));
}
private String randomDatabaseName() {
return "testdb-" + UUID.randomUUID();
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.data.r2dbc;
package org.springframework.boot.autoconfigure.r2dbc;
import io.r2dbc.spi.Connection;
import io.r2dbc.spi.ConnectionFactory;
@@ -27,7 +27,6 @@ import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfigu
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.r2dbc.connectionfactory.R2dbcTransactionManager;
import org.springframework.transaction.ReactiveTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.Transactional;
@@ -39,7 +38,7 @@ import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link R2dbcTransactionManager}.
* Tests for {@link R2dbcTransactionManagerAutoConfiguration}.
*
* @author Mark Paluch
* @author Oliver Drotbohm

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2012-2020 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.r2dbc;
import io.r2dbc.pool.ConnectionPool;
import io.r2dbc.spi.ConnectionFactory;
import org.springframework.boot.autoconfigure.r2dbc.SimpleConnectionFactoryProvider.SimpleTestConnectionFactory;
import org.springframework.r2dbc.core.binding.BindMarkersFactory;
import org.springframework.r2dbc.core.binding.BindMarkersFactoryResolver.BindMarkerFactoryProvider;
/**
* Simple {@link BindMarkerFactoryProvider} for {@link SimpleConnectionFactoryProvider}.
*
* @author Stephane Nicoll
*/
public class SimpleBindMarkerFactoryProvider implements BindMarkerFactoryProvider {
@Override
public BindMarkersFactory getBindMarkers(ConnectionFactory connectionFactory) {
if (unwrapIfNecessary(connectionFactory) instanceof SimpleTestConnectionFactory) {
return BindMarkersFactory.anonymous("?");
}
return null;
}
private ConnectionFactory unwrapIfNecessary(ConnectionFactory connectionFactory) {
if (connectionFactory instanceof ConnectionPool) {
return ((ConnectionPool) connectionFactory).unwrap();
}
return connectionFactory;
}
}

View File

@@ -0,0 +1 @@
org.springframework.r2dbc.core.binding.BindMarkersFactoryResolver$BindMarkerFactoryProvider=org.springframework.boot.autoconfigure.r2dbc.SimpleBindMarkerFactoryProvider