Commit 36fb86a1 authored by Stephane Nicoll's avatar Stephane Nicoll

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
parent 2b995324
......@@ -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());
}
}
......@@ -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.
......
/*
* 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();
}
}
......@@ -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 {
}
......@@ -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;
/**
......
......@@ -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,\
......
......@@ -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));
}
}
......@@ -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 {
}
......
......@@ -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();
}
......
......@@ -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
......
/*
* 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;
}
}
org.springframework.r2dbc.core.binding.BindMarkersFactoryResolver$BindMarkerFactoryProvider=org.springframework.boot.autoconfigure.r2dbc.SimpleBindMarkerFactoryProvider
......@@ -1972,7 +1972,7 @@ You can tune that behavior by setting `spring.datasource.continue-on-error`.
=== Initialize a Database Using R2DBC
If you are using R2DBC, the regular `DataSource` auto-configuration backs off so none of the options described above can be used.
If you are using Spring Data R2DBC, you can initialize the database on startup using SQL scripts as shown in the following example:
You can initialize the database on startup using SQL scripts as shown in the following example:
[source,java,indent=0]
----
......
......@@ -4215,7 +4215,7 @@ If you want to make sure that each context has a separate embedded database, you
[[boot-features-r2dbc-using-database-client]]
==== Using DatabaseClient
Spring Data's `DatabaseClient` class is auto-configured, and you can `@Autowire` it directly into your own beans, as shown in the following example:
A `DatabaseClient` bean is auto-configured, and you can `@Autowire` it directly into your own beans, as shown in the following example:
[source,java,indent=0]
----
......
......@@ -23,7 +23,7 @@ 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.r2dbc.connection.init.ResourceDatabasePopulator;
/**
* Example configuration for initializing a database using R2DBC.
......@@ -41,7 +41,7 @@ public class R2dbcDatabaseInitializationExample {
ResourceLoader resourceLoader = new DefaultResourceLoader();
Resource[] scripts = new Resource[] { resourceLoader.getResource("classpath:schema.sql"),
resourceLoader.getResource("classpath:data.sql") };
new ResourceDatabasePopulator(scripts).execute(connectionFactory).block();
new ResourceDatabasePopulator(scripts).populate(connectionFactory).block();
}
}
......
......@@ -52,11 +52,11 @@ org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration
# AutoConfigureDataR2dbc auto-configuration imports
org.springframework.boot.test.autoconfigure.data.r2dbc.AutoConfigureDataR2dbc=\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration,\
org.springframework.boot.autoconfigure.r2dbc.R2dbcTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration
# AutoConfigureDataRedis auto-configuration imports
......
......@@ -26,8 +26,8 @@ import org.springframework.context.ApplicationContext;
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.r2dbc.connection.init.ResourceDatabasePopulator;
import org.springframework.r2dbc.core.DatabaseClient;
import static org.assertj.core.api.Assertions.assertThat;
......@@ -50,7 +50,7 @@ class DataR2dbcTestIntegrationTests {
@Test
void testDatabaseClient() {
this.databaseClient.execute("SELECT * FROM example").fetch().all().as(StepVerifier::create).verifyComplete();
this.databaseClient.sql("SELECT * FROM example").fetch().all().as(StepVerifier::create).verifyComplete();
}
@Test
......@@ -72,7 +72,7 @@ class DataR2dbcTestIntegrationTests {
ResourceLoader resourceLoader = new DefaultResourceLoader();
Resource[] scripts = new Resource[] { resourceLoader
.getResource("classpath:org/springframework/boot/test/autoconfigure/data/r2dbc/schema.sql") };
new ResourceDatabasePopulator(scripts).execute(connectionFactory).block();
new ResourceDatabasePopulator(scripts).populate(connectionFactory).block();
}
}
......
......@@ -24,7 +24,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.data.r2dbc.connectionfactory.init.ResourceDatabasePopulator;
import org.springframework.r2dbc.connection.init.ResourceDatabasePopulator;
@SpringBootApplication
public class SampleR2dbcApplication {
......@@ -37,7 +37,7 @@ public class SampleR2dbcApplication {
public ApplicationRunner initializeDatabase(ConnectionFactory connectionFactory, ResourceLoader resourceLoader) {
return (arguments) -> {
Resource[] scripts = new Resource[] { resourceLoader.getResource("classpath:database-init.sql") };
new ResourceDatabasePopulator(scripts).execute(connectionFactory).block();
new ResourceDatabasePopulator(scripts).populate(connectionFactory).block();
};
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment