Derive a ConnectionFactoryBuilder from an existing ConnectionFactory

Closes gh-25788
This commit is contained in:
Andy Wilkinson
2021-03-24 20:20:59 +00:00
parent 870d9b191a
commit eb1200415d
7 changed files with 438 additions and 45 deletions

View File

@@ -29,6 +29,10 @@ import io.r2dbc.pool.ConnectionPool;
import io.r2dbc.pool.PoolMetrics;
import io.r2dbc.spi.ConnectionFactory;
import io.r2dbc.spi.Option;
import io.r2dbc.spi.Wrapped;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.assertj.core.api.InstanceOfAssertFactory;
import org.assertj.core.api.ObjectAssert;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanCreationException;
@@ -36,6 +40,7 @@ import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.r2dbc.SimpleConnectionFactoryProvider.SimpleTestConnectionFactory;
import org.springframework.boot.r2dbc.EmbeddedDatabaseConnection;
import org.springframework.boot.r2dbc.OptionsCapableConnectionFactory;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
@@ -58,8 +63,14 @@ class R2dbcAutoConfigurationTests {
@Test
void configureWithUrlCreateConnectionPoolByDefault() {
this.contextRunner.withPropertyValues("spring.r2dbc.url:r2dbc:h2:mem:///" + randomDatabaseName())
.run((context) -> assertThat(context).hasSingleBean(ConnectionFactory.class)
.hasSingleBean(ConnectionPool.class));
.run((context) -> {
assertThat(context).hasSingleBean(ConnectionFactory.class).hasSingleBean(ConnectionPool.class);
assertThat(context.getBean(ConnectionPool.class)).extracting(ConnectionPool::unwrap)
.satisfies((connectionFactory) -> assertThat(connectionFactory)
.asInstanceOf(type(OptionsCapableConnectionFactory.class))
.extracting(Wrapped<ConnectionFactory>::unwrap)
.isExactlyInstanceOf(H2ConnectionFactory.class));
});
}
@Test
@@ -113,7 +124,10 @@ class R2dbcAutoConfigurationTests {
this.contextRunner.withPropertyValues("spring.r2dbc.pool.enabled=false", "spring.r2dbc.url:r2dbc:h2:mem:///"
+ randomDatabaseName() + "?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE").run((context) -> {
assertThat(context).hasSingleBean(ConnectionFactory.class).doesNotHaveBean(ConnectionPool.class);
assertThat(context.getBean(ConnectionFactory.class)).isExactlyInstanceOf(H2ConnectionFactory.class);
assertThat(context.getBean(ConnectionFactory.class))
.asInstanceOf(type(OptionsCapableConnectionFactory.class))
.extracting(Wrapped<ConnectionFactory>::unwrap)
.isExactlyInstanceOf(H2ConnectionFactory.class);
});
}
@@ -122,8 +136,10 @@ class R2dbcAutoConfigurationTests {
this.contextRunner.with(hideConnectionPool()).withPropertyValues("spring.r2dbc.url:r2dbc:h2:mem:///"
+ randomDatabaseName() + "?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE").run((context) -> {
assertThat(context).hasSingleBean(ConnectionFactory.class);
ConnectionFactory bean = context.getBean(ConnectionFactory.class);
assertThat(bean).isExactlyInstanceOf(H2ConnectionFactory.class);
assertThat(context.getBean(ConnectionFactory.class))
.asInstanceOf(type(OptionsCapableConnectionFactory.class))
.extracting(Wrapped<ConnectionFactory>::unwrap)
.isExactlyInstanceOf(H2ConnectionFactory.class);
});
}
@@ -142,11 +158,10 @@ class R2dbcAutoConfigurationTests {
.withPropertyValues("spring.r2dbc.pool.enabled=false", "spring.r2dbc.url:r2dbc:simple://host/database")
.withUserConfiguration(CustomizerConfiguration.class).run((context) -> {
assertThat(context).hasSingleBean(ConnectionFactory.class).doesNotHaveBean(ConnectionPool.class);
ConnectionFactory bean = context.getBean(ConnectionFactory.class);
assertThat(bean).isExactlyInstanceOf(SimpleTestConnectionFactory.class);
SimpleTestConnectionFactory connectionFactory = (SimpleTestConnectionFactory) bean;
assertThat(connectionFactory.getOptions().getRequiredValue(Option.<Boolean>valueOf("customized")))
.isTrue();
ConnectionFactory connectionFactory = context.getBean(ConnectionFactory.class);
assertThat(connectionFactory).asInstanceOf(type(OptionsCapableConnectionFactory.class))
.extracting(OptionsCapableConnectionFactory::getOptions).satisfies((options) -> assertThat(
options.getRequiredValue(Option.<Boolean>valueOf("customized"))).isTrue());
});
}
@@ -155,11 +170,11 @@ class R2dbcAutoConfigurationTests {
this.contextRunner.withPropertyValues("spring.r2dbc.url:r2dbc:simple://host/database")
.withUserConfiguration(CustomizerConfiguration.class).run((context) -> {
assertThat(context).hasSingleBean(ConnectionFactory.class).hasSingleBean(ConnectionPool.class);
ConnectionFactory bean = context.getBean(ConnectionFactory.class);
SimpleTestConnectionFactory connectionFactory = (SimpleTestConnectionFactory) ((ConnectionPool) bean)
.unwrap();
assertThat(connectionFactory.getOptions().getRequiredValue(Option.<Boolean>valueOf("customized")))
.isTrue();
ConnectionFactory pool = context.getBean(ConnectionFactory.class);
ConnectionFactory connectionFactory = ((ConnectionPool) pool).unwrap();
assertThat(connectionFactory).asInstanceOf(type(OptionsCapableConnectionFactory.class))
.extracting(OptionsCapableConnectionFactory::getOptions).satisfies((options) -> assertThat(
options.getRequiredValue(Option.<Boolean>valueOf("customized"))).isTrue());
});
}
@@ -174,8 +189,10 @@ class R2dbcAutoConfigurationTests {
this.contextRunner.withPropertyValues("spring.r2dbc.pool.enabled=false", "spring.r2dbc.url:r2dbc:simple://foo")
.withClassLoader(new FilteredClassLoader("org.springframework.jdbc")).run((context) -> {
assertThat(context).hasSingleBean(ConnectionFactory.class);
ConnectionFactory connectionFactory = context.getBean(ConnectionFactory.class);
assertThat(connectionFactory).isInstanceOf(SimpleTestConnectionFactory.class);
assertThat(context.getBean(ConnectionFactory.class))
.asInstanceOf(type(OptionsCapableConnectionFactory.class))
.extracting(Wrapped<ConnectionFactory>::unwrap)
.isExactlyInstanceOf(SimpleTestConnectionFactory.class);
});
}
@@ -183,9 +200,12 @@ class R2dbcAutoConfigurationTests {
void configureWithoutPoolShouldApplyAdditionalProperties() {
this.contextRunner.withPropertyValues("spring.r2dbc.pool.enabled=false", "spring.r2dbc.url:r2dbc:simple://foo",
"spring.r2dbc.properties.test=value", "spring.r2dbc.properties.another=2").run((context) -> {
SimpleTestConnectionFactory connectionFactory = context.getBean(SimpleTestConnectionFactory.class);
assertThat(getRequiredOptionsValue(connectionFactory, "test")).isEqualTo("value");
assertThat(getRequiredOptionsValue(connectionFactory, "another")).isEqualTo("2");
ConnectionFactory connectionFactory = context.getBean(ConnectionFactory.class);
assertThat(connectionFactory).asInstanceOf(type(OptionsCapableConnectionFactory.class))
.extracting(OptionsCapableConnectionFactory::getOptions).satisfies((options) -> {
assertThat(options.getRequiredValue(Option.<String>valueOf("test"))).isEqualTo("value");
assertThat(options.getRequiredValue(Option.<String>valueOf("another"))).isEqualTo("2");
});
});
}
@@ -194,17 +214,15 @@ class R2dbcAutoConfigurationTests {
this.contextRunner.withPropertyValues("spring.r2dbc.url:r2dbc:simple://foo",
"spring.r2dbc.properties.test=value", "spring.r2dbc.properties.another=2").run((context) -> {
assertThat(context).hasSingleBean(ConnectionFactory.class).hasSingleBean(ConnectionPool.class);
SimpleTestConnectionFactory connectionFactory = (SimpleTestConnectionFactory) context
.getBean(ConnectionPool.class).unwrap();
assertThat(getRequiredOptionsValue(connectionFactory, "test")).isEqualTo("value");
assertThat(getRequiredOptionsValue(connectionFactory, "another")).isEqualTo("2");
ConnectionFactory connectionFactory = context.getBean(ConnectionPool.class).unwrap();
assertThat(connectionFactory).asInstanceOf(type(OptionsCapableConnectionFactory.class))
.extracting(OptionsCapableConnectionFactory::getOptions).satisfies((options) -> {
assertThat(options.getRequiredValue(Option.<String>valueOf("test"))).isEqualTo("value");
assertThat(options.getRequiredValue(Option.<String>valueOf("another"))).isEqualTo("2");
});
});
}
private Object getRequiredOptionsValue(SimpleTestConnectionFactory connectionFactory, String name) {
return connectionFactory.options.getRequiredValue(Option.valueOf(name));
}
@Test
void configureWithoutUrlShouldCreateEmbeddedConnectionPoolByDefault() {
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ConnectionFactory.class)
@@ -215,7 +233,9 @@ class R2dbcAutoConfigurationTests {
void configureWithoutUrlAndPollPoolDisabledCreateGenericConnectionFactory() {
this.contextRunner.withPropertyValues("spring.r2dbc.pool.enabled=false").run((context) -> {
assertThat(context).hasSingleBean(ConnectionFactory.class).doesNotHaveBean(ConnectionPool.class);
assertThat(context.getBean(ConnectionFactory.class)).isExactlyInstanceOf(H2ConnectionFactory.class);
assertThat(context.getBean(ConnectionFactory.class))
.asInstanceOf(type(OptionsCapableConnectionFactory.class))
.extracting(Wrapped<ConnectionFactory>::unwrap).isExactlyInstanceOf(H2ConnectionFactory.class);
});
}
@@ -260,6 +280,10 @@ class R2dbcAutoConfigurationTests {
.doesNotHaveBean(DatabaseClient.class));
}
private <T> InstanceOfAssertFactory<T, ObjectAssert<T>> type(Class<T> type) {
return InstanceOfAssertFactories.type(type);
}
private String randomDatabaseName() {
return "testdb-" + UUID.randomUUID();
}

View File

@@ -16,8 +16,8 @@
package org.springframework.boot.autoconfigure.r2dbc;
import io.r2dbc.pool.ConnectionPool;
import io.r2dbc.spi.ConnectionFactory;
import io.r2dbc.spi.Wrapped;
import org.springframework.boot.autoconfigure.r2dbc.SimpleConnectionFactoryProvider.SimpleTestConnectionFactory;
import org.springframework.r2dbc.core.binding.BindMarkersFactory;
@@ -38,9 +38,10 @@ public class SimpleBindMarkerFactoryProvider implements BindMarkerFactoryProvide
return null;
}
@SuppressWarnings("unchecked")
private ConnectionFactory unwrapIfNecessary(ConnectionFactory connectionFactory) {
if (connectionFactory instanceof ConnectionPool) {
return ((ConnectionPool) connectionFactory).unwrap();
if (connectionFactory instanceof Wrapped) {
return unwrapIfNecessary(((Wrapped<ConnectionFactory>) connectionFactory).unwrap());
}
return connectionFactory;
}

View File

@@ -25,15 +25,16 @@ import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono;
/**
* Simple driver to capture {@link ConnectionFactoryOptions}.
* Simple driver for testing.
*
* @author Mark Paluch
* @author Andy Wilkinson
*/
public class SimpleConnectionFactoryProvider implements ConnectionFactoryProvider {
@Override
public ConnectionFactory create(ConnectionFactoryOptions connectionFactoryOptions) {
return new SimpleTestConnectionFactory(connectionFactoryOptions);
return new SimpleTestConnectionFactory();
}
@Override
@@ -48,12 +49,6 @@ public class SimpleConnectionFactoryProvider implements ConnectionFactoryProvide
public static class SimpleTestConnectionFactory implements ConnectionFactory {
final ConnectionFactoryOptions options;
SimpleTestConnectionFactory(ConnectionFactoryOptions options) {
this.options = options;
}
@Override
public Publisher<? extends Connection> create() {
return Mono.error(new UnsupportedOperationException());
@@ -64,10 +59,6 @@ public class SimpleConnectionFactoryProvider implements ConnectionFactoryProvide
return SimpleConnectionFactoryProvider.class::getName;
}
public ConnectionFactoryOptions getOptions() {
return this.options;
}
}
}