Springboot 整合 r2dbc

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();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

执于代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值