Commit 19fbac7d authored by Stephane Nicoll's avatar Stephane Nicoll

Add auto-configuration for Spring Data R2DBC

This commit adds auto-configuration support for Spring Data R2DBC. If a
`ConnectionFactory` and Spring Data are available, scanning of reactive
repositories is enabled.

This commit also adds a starter to bring R2DBC and the necessary Spring
Data libraries.

See gh-19988
Co-authored-by: 's avatarMark Paluch <mpaluch@pivotal.io>
Co-authored-by: 's avatarOliver Drotbohm <odrotbohm@pivotal.io>
parent 5c174feb
...@@ -123,6 +123,7 @@ dependencies { ...@@ -123,6 +123,7 @@ dependencies {
optional("org.springframework.data:spring-data-ldap") optional("org.springframework.data:spring-data-ldap")
optional("org.springframework.data:spring-data-mongodb") optional("org.springframework.data:spring-data-mongodb")
optional("org.springframework.data:spring-data-neo4j") 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-redis")
optional("org.springframework.data:spring-data-solr") optional("org.springframework.data:spring-data-solr")
optional("org.springframework.hateoas:spring-hateoas") optional("org.springframework.hateoas:spring-hateoas")
...@@ -158,6 +159,7 @@ dependencies { ...@@ -158,6 +159,7 @@ dependencies {
testImplementation("com.jayway.jsonpath:json-path") testImplementation("com.jayway.jsonpath:json-path")
testImplementation("com.squareup.okhttp3:mockwebserver") testImplementation("com.squareup.okhttp3:mockwebserver")
testImplementation("com.sun.xml.messaging.saaj:saaj-impl") testImplementation("com.sun.xml.messaging.saaj:saaj-impl")
testImplementation("io.projectreactor:reactor-test")
testImplementation("io.r2dbc:r2dbc-h2") testImplementation("io.r2dbc:r2dbc-h2")
testImplementation("jakarta.json:jakarta.json-api") testImplementation("jakarta.json:jakarta.json-api")
testImplementation("jakarta.xml.ws:jakarta.xml.ws-api") testImplementation("jakarta.xml.ws:jakarta.xml.ws-api")
......
/*
* 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> 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<Object> 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();
}
}
/*
* 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 {
}
/*
* 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<? extends Annotation> getAnnotation() {
return EnableR2dbcRepositories.class;
}
@Override
protected Class<?> getConfiguration() {
return EnableR2dbcRepositoriesConfiguration.class;
}
@Override
protected RepositoryConfigurationExtension getRepositoryConfigurationExtension() {
return new R2dbcRepositoryConfigurationExtension();
}
@EnableR2dbcRepositories
private static class EnableR2dbcRepositoriesConfiguration {
}
}
/*
* 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);
}
}
/*
* 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;
...@@ -418,6 +418,12 @@ ...@@ -418,6 +418,12 @@
"description": "Whether to enable Neo4j repositories.", "description": "Whether to enable Neo4j repositories.",
"defaultValue": true "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", "name": "spring.data.redis.repositories.enabled",
"type": "java.lang.Boolean", "type": "java.lang.Boolean",
......
...@@ -52,6 +52,9 @@ org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfigura ...@@ -52,6 +52,9 @@ org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfigura
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\ 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.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\ org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
......
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -112,7 +112,7 @@ class JdbcRepositoriesAutoConfigurationTests { ...@@ -112,7 +112,7 @@ class JdbcRepositoriesAutoConfigurationTests {
private Function<ApplicationContextRunner, ApplicationContextRunner> database() { private Function<ApplicationContextRunner, ApplicationContextRunner> database() {
return (runner) -> runner.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class)) 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"); "spring.datasource.data=classpath:city.sql", "spring.datasource.generate-unique-name:true");
} }
......
/*
* 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));
}
}
/*
* 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 {
}
}
/*
* 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<Boolean> isTransactionActive();
}
static class TransactionalServiceImpl implements TransactionalService {
@Override
public Mono<Boolean> isTransactionActive() {
return TransactionSynchronizationManager.forCurrentTransaction()
.map(TransactionSynchronizationManager::isActualTransactionActive);
}
}
}
/*
* 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();
}
}
/*
* 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<City, Long> {
}
...@@ -1465,6 +1465,7 @@ bom { ...@@ -1465,6 +1465,7 @@ bom {
"spring-boot-starter-data-ldap", "spring-boot-starter-data-ldap",
"spring-boot-starter-data-mongodb", "spring-boot-starter-data-mongodb",
"spring-boot-starter-data-mongodb-reactive", "spring-boot-starter-data-mongodb-reactive",
"spring-boot-starter-data-r2dbc",
"spring-boot-starter-data-redis", "spring-boot-starter-data-redis",
"spring-boot-starter-data-redis-reactive", "spring-boot-starter-data-redis-reactive",
"spring-boot-starter-data-neo4j", "spring-boot-starter-data-neo4j",
......
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")
}
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