r2dbc:
init 源码:
public class R2dbcScriptDatabaseInitializer extends AbstractScriptDatabaseInitializer {
private final ConnectionFactory connectionFactory;
/**
* Creates a new {@code R2dbcScriptDatabaseInitializer} that will initialize the
* database recognized by the given {@code connectionFactory} using the given
* {@code settings}.
* @param connectionFactory connectionFactory for the database
* @param settings initialization settings
*/
public R2dbcScriptDatabaseInitializer(ConnectionFactory connectionFactory,
DatabaseInitializationSettings settings) {
super(settings);
this.connectionFactory = connectionFactory;
}
@Override
protected boolean isEmbeddedDatabase() {
return EmbeddedDatabaseConnection.isEmbedded(this.connectionFactory);
}
@Override
protected void runScripts(List<Resource> scripts, boolean continueOnError, String separator, Charset encoding) {
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
populator.setContinueOnError(continueOnError);
populator.setSeparator(separator);
if (encoding != null) {
populator.setSqlScriptEncoding(encoding.name());
}
for (Resource script : scripts) {
populator.addScript(script);
}
populator.populate(this.connectionFactory).block();
}
}
class R2dbcScriptDatabaseInitializerDetector extends AbstractBeansOfTypeDatabaseInitializerDetector {
@Override
protected Set<Class<?>> getDatabaseInitializerBeanTypes() {
return Collections.singleton(R2dbcScriptDatabaseInitializer.class);
}
}
R2DBCScirpt 的方法:
private static final OptionsCapableWrapper optionsCapableWrapper;
static {
if (ClassUtils.isPresent("io.r2dbc.pool.ConnectionPool", ConnectionFactoryBuilder.class.getClassLoader())) {
optionsCapableWrapper = new PoolingAwareOptionsCapableWrapper();
}
else {
optionsCapableWrapper = new OptionsCapableWrapper();
}
}
private static final String COLON = ":";
private final Builder optionsBuilder;
private ConnectionFactoryBuilder(Builder optionsBuilder) {
this.optionsBuilder = optionsBuilder;
}
/**
* Initialize a new {@link ConnectionFactoryBuilder} based on the specified R2DBC url.
* @param url the url to use
* @return a new builder initialized with the options exposed in the specified url
* @see EmbeddedDatabaseConnection#getUrl(String)
*/
public static ConnectionFactoryBuilder withUrl(String url) {
Assert.hasText(url, () -> "Url must not be null");
return withOptions(ConnectionFactoryOptions.parse(url).mutate());
}
/**
* Initialize a new {@link ConnectionFactoryBuilder} based on the specified
* {@link Builder options}.
* @param options the options to use to initialize the builder
* @return a new builder initialized with the settings defined in the given
* {@link Builder options}
*/
public static ConnectionFactoryBuilder withOptions(Builder options) {
return new ConnectionFactoryBuilder(options);
}
/**
* Initialize a new {@link ConnectionFactoryBuilder} derived from the options of the
* specified {@code connectionFactory}.
* @param connectionFactory the connection factory whose options are to be used to
* initialize the builder
* @return a new builder initialized with the options from the connection factory
* @since 2.5.1
*/
public static ConnectionFactoryBuilder derivedFrom(ConnectionFactory connectionFactory) {
ConnectionFactoryOptions options = extractOptionsIfPossible(connectionFactory);
if (options == null) {
throw new IllegalArgumentException(
"ConnectionFactoryOptions could not be extracted from " + connectionFactory);
}
return withOptions(options.mutate());
}
private static ConnectionFactoryOptions extractOptionsIfPossible(ConnectionFactory connectionFactory) {
OptionsCapableConnectionFactory optionsCapable = OptionsCapableConnectionFactory.unwrapFrom(connectionFactory);
if (optionsCapable != null) {
return optionsCapable.getOptions();
}
return null;
}
/**
* Configure additional options.
* @param options a {@link Consumer} to customize the options
* @return this for method chaining
*/
public ConnectionFactoryBuilder configure(Consumer<Builder> options) {
options.accept(this.optionsBuilder);
return this;
}
/**
* Configure the {@linkplain ConnectionFactoryOptions#USER username}.
* @param username the connection factory username
* @return this for method chaining
*/
public ConnectionFactoryBuilder username(String username) {
return configure((options) -> options.option(ConnectionFactoryOptions.USER, username));
}
/**
* Configure the {@linkplain ConnectionFactoryOptions#PASSWORD password}.
* @param password the connection factory password
* @return this for method chaining
*/
public ConnectionFactoryBuilder password(CharSequence password) {
return configure((options) -> options.option(ConnectionFactoryOptions.PASSWORD, password));
}
/**
* Configure the {@linkplain ConnectionFactoryOptions#HOST host name}.
* @param host the connection factory hostname
* @return this for method chaining
*/
public ConnectionFactoryBuilder hostname(String host) {
return configure((options) -> options.option(ConnectionFactoryOptions.HOST, host));
}
/**
* Configure the {@linkplain ConnectionFactoryOptions#PORT port}.
* @param port the connection factory port
* @return this for method chaining
*/
public ConnectionFactoryBuilder port(int port) {
return configure((options) -> options.option(ConnectionFactoryOptions.PORT, port));
}
/**
* Configure the {@linkplain ConnectionFactoryOptions#DATABASE database}.
* @param database the connection factory database
* @return this for method chaining
*/
public ConnectionFactoryBuilder database(String database) {
return configure((options) -> options.option(ConnectionFactoryOptions.DATABASE, database));
}
/**
* Build a {@link ConnectionFactory} based on the state of this builder.
* @return a connection factory
*/
public ConnectionFactory build() {
ConnectionFactoryOptions options = buildOptions();
return optionsCapableWrapper.buildAndWrap(options);
}
/**
* Build a {@link ConnectionFactoryOptions} based on the state of this builder.
* @return the options
*/
public ConnectionFactoryOptions buildOptions() {
return this.optionsBuilder.build();
}
private static class OptionsCapableWrapper {
ConnectionFactory buildAndWrap(ConnectionFactoryOptions options) {
ConnectionFactory connectionFactory = ConnectionFactories.get(options);
return new OptionsCapableConnectionFactory(options, connectionFactory);
}
}
static final class PoolingAwareOptionsCapableWrapper extends OptionsCapableWrapper {
private final PoolingConnectionFactoryProvider poolingProvider = new PoolingConnectionFactoryProvider();
@Override
ConnectionFactory buildAndWrap(ConnectionFactoryOptions options) {
if (!this.poolingProvider.supports(options)) {
return super.buildAndWrap(options);
}
ConnectionFactoryOptions delegateOptions = delegateFactoryOptions(options);
ConnectionFactory connectionFactory = super.buildAndWrap(delegateOptions);
ConnectionPoolConfiguration poolConfiguration = connectionPoolConfiguration(delegateOptions,
connectionFactory);
return new ConnectionPool(poolConfiguration);
}
private ConnectionFactoryOptions delegateFactoryOptions(ConnectionFactoryOptions options) {
String protocol = toString(options.getRequiredValue(ConnectionFactoryOptions.PROTOCOL));
if (protocol.trim().length() == 0) {
throw new IllegalArgumentException(String.format("Protocol %s is not valid.", protocol));
}
String[] protocols = protocol.split(COLON, 2);
String driverDelegate = protocols[0];
String protocolDelegate = (protocols.length != 2) ? "" : protocols[1];
return ConnectionFactoryOptions.builder().from(options)
.option(ConnectionFactoryOptions.DRIVER, driverDelegate)
.option(ConnectionFactoryOptions.PROTOCOL, protocolDelegate).build();
}
@SuppressWarnings("unchecked")
ConnectionPoolConfiguration connectionPoolConfiguration(ConnectionFactoryOptions options,
ConnectionFactory connectionFactory) {
ConnectionPoolConfiguration.Builder builder = ConnectionPoolConfiguration.builder(connectionFactory);
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(options.getValue(PoolingConnectionFactoryProvider.BACKGROUND_EVICTION_INTERVAL))
.as(this::toDuration).to(builder::backgroundEvictionInterval);
map.from(options.getValue(PoolingConnectionFactoryProvider.INITIAL_SIZE)).as(this::toInteger)
.to(builder::initialSize);
map.from(options.getValue(PoolingConnectionFactoryProvider.MAX_SIZE)).as(this::toInteger)
.to(builder::maxSize);
map.from(options.getValue(PoolingConnectionFactoryProvider.ACQUIRE_RETRY)).as(this::toInteger)
.to(builder::acquireRetry);
map.from(options.getValue(PoolingConnectionFactoryProvider.MAX_LIFE_TIME)).as(this::toDuration)
.to(builder::maxLifeTime);
map.from(options.getValue(PoolingConnectionFactoryProvider.MAX_ACQUIRE_TIME)).as(this::toDuration)
.to(builder::maxAcquireTime);
map.from(options.getValue(PoolingConnectionFactoryProvider.MAX_IDLE_TIME)).as(this::toDuration)
.to(builder::maxIdleTime);
map.from(options.getValue(PoolingConnectionFactoryProvider.MAX_CREATE_CONNECTION_TIME)).as(this::toDuration)
.to(builder::maxCreateConnectionTime);
map.from(options.getValue(PoolingConnectionFactoryProvider.POOL_NAME)).as(this::toString).to(builder::name);
map.from(options.getValue(PoolingConnectionFactoryProvider.PRE_RELEASE)).to((function) -> builder
.preRelease((Function<? super Connection, ? extends Publisher<Void>>) function));
map.from(options.getValue(PoolingConnectionFactoryProvider.POST_ALLOCATE)).to((function) -> builder
.postAllocate((Function<? super Connection, ? extends Publisher<Void>>) function));
map.from(options.getValue(PoolingConnectionFactoryProvider.REGISTER_JMX)).as(this::toBoolean)
.to(builder::registerJmx);
map.from(options.getValue(PoolingConnectionFactoryProvider.VALIDATION_QUERY)).as(this::toString)
.to(builder::validationQuery);
map.from(options.getValue(PoolingConnectionFactoryProvider.VALIDATION_DEPTH)).as(this::toValidationDepth)
.to(builder::validationDepth);
return builder.build();
}
private String toString(Object object) {
return toType(String.class, object, String::valueOf);
}
private Integer toInteger(Object object) {
return toType(Integer.class, object, Integer::valueOf);
}
private Duration toDuration(Object object) {
return toType(Duration.class, object, Duration::parse);
}
private Boolean toBoolean(Object object) {
return toType(Boolean.class, object, Boolean::valueOf);
}
private ValidationDepth toValidationDepth(Object object) {
return toType(ValidationDepth.class, object,
(string) -> ValidationDepth.valueOf(string.toUpperCase(Locale.ENGLISH)));
}
private <T> T toType(Class<T> type, Object object, Function<String, T> converter) {
if (type.isInstance(object)) {
return type.cast(object);
}
if (object instanceof String string) {
return converter.apply(string);
}
throw new IllegalArgumentException("Cannot convert '" + object + "' to " + type.getName());
}
}
}
public enum EmbeddedDatabaseConnection {
/**
* No Connection.
*/
NONE(null, null, (options) -> false),
/**
* H2 Database Connection.
*/
H2("io.r2dbc.h2.H2ConnectionFactoryProvider", "r2dbc:h2:mem:///%s?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE",
(options) -> options.getValue(ConnectionFactoryOptions.DRIVER).equals("h2")
&& options.getValue(ConnectionFactoryOptions.PROTOCOL).equals("mem"));
private final String driverClassName;
private final String url;
private Predicate<ConnectionFactoryOptions> embedded;
EmbeddedDatabaseConnection(String driverClassName, String url, Predicate<ConnectionFactoryOptions> embedded) {
this.driverClassName = driverClassName;
this.url = url;
this.embedded = embedded;
}
/**
* Returns the driver class name.
* @return the driver class name
*/
public String getDriverClassName() {
return this.driverClassName;
}
/**
* Returns the R2DBC URL for the connection using the specified {@code databaseName}.
* @param databaseName the name of the database
* @return the connection URL
*/
public String getUrl(String databaseName) {
Assert.hasText(databaseName, "DatabaseName must not be empty");
return (this.url != null) ? String.format(this.url, databaseName) : null;
}
/**
* Returns the most suitable {@link EmbeddedDatabaseConnection} for the given class
* loader.
* @param classLoader the class loader used to check for classes
* @return an {@link EmbeddedDatabaseConnection} or {@link #NONE}.
*/
public static EmbeddedDatabaseConnection get(ClassLoader classLoader) {
for (EmbeddedDatabaseConnection candidate : EmbeddedDatabaseConnection.values()) {
if (candidate != NONE && ClassUtils.isPresent(candidate.getDriverClassName(), classLoader)) {
return candidate;
}
}
return NONE;
}
/**
* Convenience method to determine if a given connection factory represents an
* embedded database type.
* @param connectionFactory the connection factory to interrogate
* @return true if the connection factory represents an embedded database
* @since 2.5.1
*/
public static boolean isEmbedded(ConnectionFactory connectionFactory) {
OptionsCapableConnectionFactory optionsCapable = OptionsCapableConnectionFactory.unwrapFrom(connectionFactory);
Assert.notNull(optionsCapable,
() -> "Cannot determine database's type as ConnectionFactory is not options-capable. To be "
+ "options-capable, a ConnectionFactory should be created with "
+ ConnectionFactoryBuilder.class.getName());
ConnectionFactoryOptions options = optionsCapable.getOptions();
for (EmbeddedDatabaseConnection candidate : values()) {
if (candidate.embedded.test(options)) {
return true;
}
}
return false;
}
}
public class OptionsCapableConnectionFactory implements Wrapped<ConnectionFactory>, ConnectionFactory {
private final ConnectionFactoryOptions options;
private final ConnectionFactory delegate;
/**
* Create a new {@code OptionsCapableConnectionFactory} that will provide access to
* the given {@code options} that were used to build the given {@code delegate}
* {@link ConnectionFactory}.
* @param options the options from which the connection factory was built
* @param delegate the delegate connection factory that was built with options
*/
public OptionsCapableConnectionFactory(ConnectionFactoryOptions options, ConnectionFactory delegate) {
this.options = options;
this.delegate = delegate;
}
public ConnectionFactoryOptions getOptions() {
return this.options;
}
@Override
public Publisher<? extends Connection> create() {
return this.delegate.create();
}
@Override
public ConnectionFactoryMetadata getMetadata() {
return this.delegate.getMetadata();
}
@Override
public ConnectionFactory unwrap() {
return this.delegate;
}
/**
* Returns, if possible, an {@code OptionsCapableConnectionFactory} by unwrapping the
* given {@code connectionFactory} as necessary. If the given
* {@code connectionFactory} does not wrap an {@code OptionsCapableConnectionFactory}
* and is not itself an {@code OptionsCapableConnectionFactory}, {@code null} is
* returned.
* @param connectionFactory the connection factory to unwrap
* @return the {@code OptionsCapableConnectionFactory} or {@code null}
* @since 2.5.1
*/
public static OptionsCapableConnectionFactory unwrapFrom(ConnectionFactory connectionFactory) {
if (connectionFactory instanceof OptionsCapableConnectionFactory) {
return (OptionsCapableConnectionFactory) connectionFactory;
}
if (connectionFactory instanceof Wrapped) {
Object unwrapped = ((Wrapped<?>) connectionFactory).unwrap();
if (unwrapped instanceof ConnectionFactory) {
return unwrapFrom((ConnectionFactory) unwrapped);
}
}
return null;
}
}
R2DBC 是 "Reactive Relational Database Connectivity"的简称。
R2DBC 是一个 API 规范的倡议,声明对于访问关系型数据库驱动实现了一些响应式的API。
R2DBC的诞生为了非阻塞的应用栈, 使用很少的线程可以处理大量的并发同时减少硬件资源。大量的应用还是使用的关系型数据库,然而很多 NoSQL 数据提供了响应式客户端,并不是所有的项目都适合迁移到 NoSQL。因此,R2DBC 应运而生。
R2DBC 特点
对于 R2DBC 的驱动实例,Spring 支持基于Java 的@Connfiguration的配置
R2dbcEntityTemplate 作为实体绑定的核心操作类
整合了Spring Conversion Service 丰富的对象映射
基于注解元数据映射关系
自动实现 Reposity 接口,包含支持自定义的查询方法
方法:
import com.example.springreactivedemo.model.Person;
import io.r2dbc.spi.ConnectionFactories;
import io.r2dbc.spi.ConnectionFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
import reactor.test.StepVerifier;
public class Test1 {
private static final Log log = LogFactory.getLog(Test1.class);
public static void main(String[] args) {
ConnectionFactory connectionFactory = ConnectionFactories.get("r2dbc:h2:mem:///test?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE");
R2dbcEntityTemplate template = new R2dbcEntityTemplate(connectionFactory);
template.getDatabaseClient().sql("CREATE TABLE person" +
"(id VARCHAR(255) PRIMARY KEY," +
"name VARCHAR(255)," +
"age INT)")
.fetch()
.rowsUpdated()
.as(StepVerifier::create)
.expectNextCount(1)
.verifyComplete();
template.insert(Person.class)
.using(new Person("joe", "Joe", 34))
.as(StepVerifier::create)
.expectNextCount(1)
.verifyComplete();
template.select(Person.class)
.first()
.doOnNext(it -> log.info(it))
.as(StepVerifier::create)
.expectNextCount(1)
.verifyComplete();
}
}