diff --git a/spring-boot-project/spring-boot-autoconfigure/build.gradle b/spring-boot-project/spring-boot-autoconfigure/build.gradle index 7d4b6a15b9..684f771768 100644 --- a/spring-boot-project/spring-boot-autoconfigure/build.gradle +++ b/spring-boot-project/spring-boot-autoconfigure/build.gradle @@ -123,6 +123,7 @@ dependencies { optional("org.springframework.data:spring-data-ldap") optional("org.springframework.data:spring-data-mongodb") optional("org.springframework.data:spring-data-neo4j") + optional("org.springframework.data:spring-data-r2dbc") optional("org.springframework.data:spring-data-redis") optional("org.springframework.data:spring-data-solr") optional("org.springframework.hateoas:spring-hateoas") @@ -158,6 +159,7 @@ dependencies { testImplementation("com.jayway.jsonpath:json-path") testImplementation("com.squareup.okhttp3:mockwebserver") testImplementation("com.sun.xml.messaging.saaj:saaj-impl") + testImplementation("io.projectreactor:reactor-test") testImplementation("io.r2dbc:r2dbc-h2") testImplementation("jakarta.json:jakarta.json-api") testImplementation("jakarta.xml.ws:jakarta.xml.ws-api") diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/r2dbc/R2dbcDataAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/r2dbc/R2dbcDataAutoConfiguration.java new file mode 100644 index 0000000000..1f461921f0 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/r2dbc/R2dbcDataAutoConfiguration.java @@ -0,0 +1,110 @@ +/* + * 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.data.r2dbc; + +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; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; +import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration; +import org.springframework.context.annotation.Bean; +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.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; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for {@link DatabaseClient}. + * + * @author Mark Paluch + * @author Oliver Drotbohm + * @since 2.3.0 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass(DatabaseClient.class) +@ConditionalOnMissingBean(DatabaseClient.class) +@ConditionalOnSingleCandidate(ConnectionFactory.class) +@AutoConfigureAfter(R2dbcAutoConfiguration.class) +public class R2dbcDataAutoConfiguration { + + private final ConnectionFactory connectionFactory; + + public R2dbcDataAutoConfiguration(ConnectionFactory connectionFactory) { + this.connectionFactory = connectionFactory; + } + + @Bean + @ConditionalOnMissingBean + public DatabaseClient r2dbcDatabaseClient(ReactiveDataAccessStrategy dataAccessStrategy, + R2dbcExceptionTranslator exceptionTranslator) { + return DatabaseClient.builder().connectionFactory(this.connectionFactory).dataAccessStrategy(dataAccessStrategy) + .exceptionTranslator(exceptionTranslator).build(); + } + + @Bean + @ConditionalOnMissingBean + public R2dbcMappingContext r2dbcMappingContext(ObjectProvider namingStrategy, + R2dbcCustomConversions r2dbcCustomConversions) { + R2dbcMappingContext relationalMappingContext = new R2dbcMappingContext( + namingStrategy.getIfAvailable(() -> NamingStrategy.INSTANCE)); + relationalMappingContext.setSimpleTypeHolder(r2dbcCustomConversions.getSimpleTypeHolder()); + return relationalMappingContext; + } + + @Bean + @ConditionalOnMissingBean + public ReactiveDataAccessStrategy reactiveDataAccessStrategy(R2dbcMappingContext mappingContext, + R2dbcCustomConversions r2dbcCustomConversions) { + MappingR2dbcConverter converter = new MappingR2dbcConverter(mappingContext, r2dbcCustomConversions); + return new DefaultReactiveDataAccessStrategy(DialectResolver.getDialect(this.connectionFactory), converter); + } + + @Bean + @ConditionalOnMissingBean + public R2dbcCustomConversions r2dbcCustomConversions() { + R2dbcDialect dialect = DialectResolver.getDialect(this.connectionFactory); + List converters = new ArrayList<>(dialect.getConverters()); + converters.addAll(R2dbcCustomConversions.STORE_CONVERTERS); + return new R2dbcCustomConversions( + CustomConversions.StoreConversions.of(dialect.getSimpleTypeHolder(), converters), + Collections.emptyList()); + } + + @Bean + @ConditionalOnMissingBean + public R2dbcExceptionTranslator r2dbcExceptionTranslator() { + return new R2dbcExceptionSubclassTranslator(); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/r2dbc/R2dbcRepositoriesAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/r2dbc/R2dbcRepositoriesAutoConfiguration.java new file mode 100644 index 0000000000..46638e8f24 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/r2dbc/R2dbcRepositoriesAutoConfiguration.java @@ -0,0 +1,51 @@ +/* + * 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.data.r2dbc; + +import io.r2dbc.spi.ConnectionFactory; + +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +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; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for Spring Data R2DBC Repositories. + * + * @author Mark Paluch + * @see EnableR2dbcRepositories + * @since 2.3.0 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass({ ConnectionFactory.class, R2dbcRepository.class }) +@ConditionalOnBean(DatabaseClient.class) +@ConditionalOnProperty(prefix = "spring.data.r2dbc.repositories", name = "enabled", havingValue = "true", + matchIfMissing = true) +@ConditionalOnMissingBean(R2dbcRepositoryFactoryBean.class) +@Import(R2dbcRepositoriesAutoConfigureRegistrar.class) +@AutoConfigureAfter(R2dbcDataAutoConfiguration.class) +public class R2dbcRepositoriesAutoConfiguration { + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/r2dbc/R2dbcRepositoriesAutoConfigureRegistrar.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/r2dbc/R2dbcRepositoriesAutoConfigureRegistrar.java new file mode 100644 index 0000000000..955ee3d317 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/r2dbc/R2dbcRepositoriesAutoConfigureRegistrar.java @@ -0,0 +1,55 @@ +/* + * 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.data.r2dbc; + +import java.lang.annotation.Annotation; + +import org.springframework.boot.autoconfigure.data.AbstractRepositoryConfigurationSourceSupport; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories; +import org.springframework.data.r2dbc.repository.config.R2dbcRepositoryConfigurationExtension; +import org.springframework.data.repository.config.RepositoryConfigurationExtension; + +/** + * {@link ImportBeanDefinitionRegistrar} used to auto-configure Spring Data R2DBC + * Repositories. + * + * @author Mark Paluch + */ +class R2dbcRepositoriesAutoConfigureRegistrar extends AbstractRepositoryConfigurationSourceSupport { + + @Override + protected Class getAnnotation() { + return EnableR2dbcRepositories.class; + } + + @Override + protected Class getConfiguration() { + return EnableR2dbcRepositoriesConfiguration.class; + } + + @Override + protected RepositoryConfigurationExtension getRepositoryConfigurationExtension() { + return new R2dbcRepositoryConfigurationExtension(); + } + + @EnableR2dbcRepositories + private static class EnableR2dbcRepositoriesConfiguration { + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/r2dbc/R2dbcTransactionManagerAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/r2dbc/R2dbcTransactionManagerAutoConfiguration.java new file mode 100644 index 0000000000..0de28c6226 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/r2dbc/R2dbcTransactionManagerAutoConfiguration.java @@ -0,0 +1,53 @@ +/* + * 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.data.r2dbc; + +import io.r2dbc.spi.ConnectionFactory; + +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.AutoConfigureOrder; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; +import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration; +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.transaction.ReactiveTransactionManager; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for {@link R2dbcTransactionManager}. + * + * @author Mark Paluch + * @since 2.3.0 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass({ R2dbcTransactionManager.class, ReactiveTransactionManager.class }) +@ConditionalOnSingleCandidate(ConnectionFactory.class) +@AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE) +@AutoConfigureBefore(TransactionAutoConfiguration.class) +public class R2dbcTransactionManagerAutoConfiguration { + + @Bean + @ConditionalOnMissingBean(ReactiveTransactionManager.class) + public R2dbcTransactionManager connectionFactoryTransactionManager(ConnectionFactory connectionFactory) { + return new R2dbcTransactionManager(connectionFactory); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/r2dbc/package-info.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/r2dbc/package-info.java new file mode 100644 index 0000000000..7dd524cb2d --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/r2dbc/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/** + * Auto-Configuration for Spring Data R2DBC. + */ +package org.springframework.boot.autoconfigure.data.r2dbc; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index f196ba0749..0aafe1f38f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -418,6 +418,12 @@ "description": "Whether to enable Neo4j repositories.", "defaultValue": true }, + { + "name": "spring.data.r2dbc.repositories.enabled", + "type": "java.lang.Boolean", + "description": "Whether to enable R2DBC repositories.", + "defaultValue": true + }, { "name": "spring.data.redis.repositories.enabled", "type": "java.lang.Boolean", diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index feb87f0f9f..dfd9aeacfa 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -52,6 +52,9 @@ org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfigura org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\ 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,\ diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/jdbc/JdbcRepositoriesAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/jdbc/JdbcRepositoriesAutoConfigurationTests.java index e947b1e992..9fa77bb130 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/jdbc/JdbcRepositoriesAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/jdbc/JdbcRepositoriesAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * 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. @@ -112,7 +112,7 @@ class JdbcRepositoriesAutoConfigurationTests { private Function database() { return (runner) -> runner.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class)) - .withPropertyValues("spring.datasource.schema=classpath:data-jdbc-schema.sql", + .withPropertyValues("spring.datasource.schema=classpath:data-city-schema.sql", "spring.datasource.data=classpath:city.sql", "spring.datasource.generate-unique-name:true"); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/r2dbc/R2dbcDataAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/r2dbc/R2dbcDataAutoConfigurationTests.java new file mode 100644 index 0000000000..682ed667af --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/r2dbc/R2dbcDataAutoConfigurationTests.java @@ -0,0 +1,43 @@ +/* + * 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.data.r2dbc; + +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 static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link R2dbcDataAutoConfiguration}. + * + * @author Mark Paluch + */ +class R2dbcDataAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(R2dbcAutoConfiguration.class, R2dbcDataAutoConfiguration.class)); + + @Test + void databaseClientExists() { + this.contextRunner.run((context) -> assertThat(context).hasSingleBean(DatabaseClient.class)); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/r2dbc/R2dbcRepositoriesAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/r2dbc/R2dbcRepositoriesAutoConfigurationTests.java new file mode 100644 index 0000000000..1eb702dd5f --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/r2dbc/R2dbcRepositoriesAutoConfigurationTests.java @@ -0,0 +1,130 @@ +/* + * 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.data.r2dbc; + +import io.r2dbc.spi.ConnectionFactory; +import org.junit.jupiter.api.Test; +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.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.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 static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link R2dbcRepositoriesAutoConfiguration}. + * + * @author Mark Paluch + */ +class R2dbcRepositoriesAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(R2dbcRepositoriesAutoConfiguration.class)); + + @Test + void backsOffWithNoConnectionFactory() { + this.contextRunner.withUserConfiguration(TestConfiguration.class) + .run((context) -> assertThat(context).doesNotHaveBean(R2dbcRepositoryConfigurationExtension.class)); + } + + @Test + void backsOffWithNoDatabaseClientOperations() { + this.contextRunner.withConfiguration(AutoConfigurations.of(R2dbcAutoConfiguration.class)) + .withUserConfiguration(TestConfiguration.class).run((context) -> { + assertThat(context).doesNotHaveBean(DatabaseClient.class); + assertThat(context).doesNotHaveBean(R2dbcRepositoryConfigurationExtension.class); + }); + } + + @Test + void basicAutoConfiguration() { + this.contextRunner + .withConfiguration( + AutoConfigurations.of(R2dbcAutoConfiguration.class, R2dbcDataAutoConfiguration.class)) + .withUserConfiguration(DatabaseInitializationConfiguration.class, TestConfiguration.class) + .withPropertyValues("spring.r2dbc.generate-unique-name:true").run((context) -> { + assertThat(context).hasSingleBean(CityRepository.class); + context.getBean(CityRepository.class).findById(2000L).as(StepVerifier::create).expectNextCount(1) + .verifyComplete(); + }); + } + + @Test + void autoConfigurationWithNoRepositories() { + this.contextRunner.withConfiguration(AutoConfigurations.of(R2dbcAutoConfiguration.class)) + .withUserConfiguration(EmptyConfiguration.class) + .run((context) -> assertThat(context).doesNotHaveBean(Repository.class)); + } + + @Test + void honorsUsersEnableR2dbcRepositoriesConfiguration() { + this.contextRunner + .withConfiguration( + AutoConfigurations.of(R2dbcAutoConfiguration.class, R2dbcDataAutoConfiguration.class)) + .withUserConfiguration(DatabaseInitializationConfiguration.class, EnableRepositoriesConfiguration.class) + .withPropertyValues("spring.r2dbc.generate-unique-name:true").run((context) -> { + assertThat(context).hasSingleBean(CityRepository.class); + context.getBean(CityRepository.class).findById(2000L).as(StepVerifier::create).expectNextCount(1) + .verifyComplete(); + }); + } + + @Configuration(proxyBeanMethods = false) + static class DatabaseInitializationConfiguration { + + @Autowired + void initializeDatabase(ConnectionFactory connectionFactory) { + 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(); + } + + } + + @Configuration(proxyBeanMethods = false) + @TestAutoConfigurationPackage(City.class) + static class TestConfiguration { + + } + + @Configuration(proxyBeanMethods = false) + static class EmptyConfiguration { + + } + + @Configuration(proxyBeanMethods = false) + @EnableR2dbcRepositories(basePackageClasses = City.class) + static class EnableRepositoriesConfiguration { + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/r2dbc/R2dbcTransactionManagerAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/r2dbc/R2dbcTransactionManagerAutoConfigurationTests.java new file mode 100644 index 0000000000..b0fda814bd --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/r2dbc/R2dbcTransactionManagerAutoConfigurationTests.java @@ -0,0 +1,117 @@ +/* + * 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.data.r2dbc; + +import io.r2dbc.spi.Connection; +import io.r2dbc.spi.ConnectionFactory; +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration; +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; +import org.springframework.transaction.reactive.TransactionSynchronizationManager; +import org.springframework.transaction.reactive.TransactionalOperator; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link R2dbcTransactionManager}. + * + * @author Mark Paluch + * @author Oliver Drotbohm + */ +class R2dbcTransactionManagerAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration( + AutoConfigurations.of(R2dbcTransactionManagerAutoConfiguration.class, TransactionAutoConfiguration.class)); + + @Test + void noTransactionManager() { + this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(ReactiveTransactionManager.class)); + } + + @Test + void singleTransactionManager() { + this.contextRunner.withUserConfiguration(SingleConnectionFactoryConfiguration.class) + .run((context) -> assertThat(context).hasSingleBean(TransactionalOperator.class) + .hasSingleBean(ReactiveTransactionManager.class)); + } + + @Test + void transactionManagerEnabled() { + this.contextRunner.withUserConfiguration(SingleConnectionFactoryConfiguration.class, BaseConfiguration.class) + .run((context) -> { + TransactionalService bean = context.getBean(TransactionalService.class); + bean.isTransactionActive().as(StepVerifier::create).expectNext(true).verifyComplete(); + }); + } + + @Configuration(proxyBeanMethods = false) + static class SingleConnectionFactoryConfiguration { + + @Bean + ConnectionFactory connectionFactory() { + ConnectionFactory connectionFactory = mock(ConnectionFactory.class); + Connection connection = mock(Connection.class); + given(connectionFactory.create()).willAnswer((invocation) -> Mono.just(connection)); + given(connection.beginTransaction()).willReturn(Mono.empty()); + given(connection.commitTransaction()).willReturn(Mono.empty()); + given(connection.close()).willReturn(Mono.empty()); + return connectionFactory; + } + + } + + @Configuration(proxyBeanMethods = false) + @EnableTransactionManagement + static class BaseConfiguration { + + @Bean + TransactionalService transactionalService() { + return new TransactionalServiceImpl(); + } + + } + + interface TransactionalService { + + @Transactional + Mono isTransactionActive(); + + } + + static class TransactionalServiceImpl implements TransactionalService { + + @Override + public Mono isTransactionActive() { + return TransactionSynchronizationManager.forCurrentTransaction() + .map(TransactionSynchronizationManager::isActualTransactionActive); + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/r2dbc/city/City.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/r2dbc/city/City.java new file mode 100644 index 0000000000..3543139530 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/r2dbc/city/City.java @@ -0,0 +1,65 @@ +/* + * 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.data.r2dbc.city; + +import org.springframework.data.annotation.Id; +import org.springframework.data.relational.core.mapping.Table; + +@Table("CITY") +public class City { + + @Id + private Long id; + + private String name; + + private String state; + + private String country; + + private String map; + + protected City() { + } + + public City(String name, String country) { + this.name = name; + this.country = country; + } + + public String getName() { + return this.name; + } + + public String getState() { + return this.state; + } + + public String getCountry() { + return this.country; + } + + public String getMap() { + return this.map; + } + + @Override + public String toString() { + return getName() + "," + getState() + "," + getCountry(); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/r2dbc/city/CityRepository.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/r2dbc/city/CityRepository.java new file mode 100644 index 0000000000..adff0d8121 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/r2dbc/city/CityRepository.java @@ -0,0 +1,23 @@ +/* + * 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.data.r2dbc.city; + +import org.springframework.data.repository.reactive.ReactiveCrudRepository; + +public interface CityRepository extends ReactiveCrudRepository { + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/resources/data-jdbc-schema.sql b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/data-city-schema.sql similarity index 100% rename from spring-boot-project/spring-boot-autoconfigure/src/test/resources/data-jdbc-schema.sql rename to spring-boot-project/spring-boot-autoconfigure/src/test/resources/data-city-schema.sql diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index a7a8972d58..8ebd02159e 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -1465,6 +1465,7 @@ bom { "spring-boot-starter-data-ldap", "spring-boot-starter-data-mongodb", "spring-boot-starter-data-mongodb-reactive", + "spring-boot-starter-data-r2dbc", "spring-boot-starter-data-redis", "spring-boot-starter-data-redis-reactive", "spring-boot-starter-data-neo4j", diff --git a/spring-boot-project/spring-boot-starters/spring-boot-starter-data-r2dbc/build.gradle b/spring-boot-project/spring-boot-starters/spring-boot-starter-data-r2dbc/build.gradle new file mode 100644 index 0000000000..79d51a1261 --- /dev/null +++ b/spring-boot-project/spring-boot-starters/spring-boot-starter-data-r2dbc/build.gradle @@ -0,0 +1,13 @@ +plugins { + id "org.springframework.boot.starter" +} + +description = "Starter for using Spring Data R2DBC" + +dependencies { + api(platform(project(":spring-boot-project:spring-boot-dependencies"))) + api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter")) + api("org.springframework.data:spring-data-r2dbc") + api("io.r2dbc:r2dbc-spi") + api("io.r2dbc:r2dbc-pool") +}