Get ahead
VMware offers training and certification to turbo-charge your progress.
Learn moreOn behalf of the team and everyone that contributed, I am pleased to announce that the first release candidate of Spring Data R2DBC 1.0 has been released and is available from our milestone repository. This release contains 60 issues and pull requests. It upgrades its baseline to R2DBC 0.8 RC1 and Spring Data Moore GA.
The most notable features are:
ConnectionFactory
routing through AbstractRoutingConnectionFactory
.ResourceDatabasePopulator
and ScriptUtils
.TransactionDefinition
.ConnectionFactory
RoutingFor each operation, DatabaseClient
obtains a Connection
from the ConnectionFactory
that is used to create DatabaseClient
.
The general idea is that a routing ConnectionFactory
acts as an intermediary - while the ‘real’ ConnectionFactory
is determined dynamically at runtime based on a lookup key. One potential use case is for catering to multi-tenancy by using multiple ConnectionFactory
instances that point to a separate database. For a routing ConnectionFactory
to determine which ConnectionFactory
to use, it requires contextual information. Spring Data R2DBC comes with an AbstractRoutingConnectionFactory
that can return a lookup key that is used to look up the right ConnectionFactory
.
Reactive flows can attach contextual information to the subscription by using Project Reactor's Context
.
An example implementation for a routing ConnectionFactory
might look like the following listing:
class RoutingConnectionFactory extends AbstractRoutingConnectionFactory {
public static final String ROUTING_KEY = "routing-key";
@Override
protected Mono<Object> determineCurrentLookupKey() {
return Mono.subscriberContext().filter(it -> it.hasKey(ROUTING_KEY)).map(it -> it.get(ROUTING_KEY));
}
}
The following listing shows how to initialize the connection factory a map between the lookup key and the target ConnectionFactory
:
RoutingConnectionFactory connectionFactory = new RoutingConnectionFactory();
Map<String, ConnectionFactory> factories = new HashMap<>();
factories.put("customer1", …);
factories.put("customer2", …);
connectionFactory.setTargetConnectionFactories(factories);
connectionFactory.afterPropertiesSet();
You can create a DatabaseClient
by using the routing ConnectionFactory
to determine the appropriate ConnectionFactory
for each subscription.
The routing key itself gets attached to the Context
and is evaluated by RoutingConnectionFactory
, as the following listing shows:
DatabaseClient client = DatabaseClient.create(connectionFactory);
Context routingContext = Context.of(ROUTING_KEY, "customer1");
Flux<User> users = client.execute("SELECT first_name, last_name FROM user")
.as(User.class)
.fetch()
.all()
.subscriberContext(routingContext);
The org.springframework.data.r2dbc.connectionfactory.init
package provides support for initializing an existing ConnectionFactory
. You may sometimes need to initialize an instance that runs on a server somewhere or that is an embedded database. The following listing shows how to register and configure a ConnectionFactoryInitializer
bean to initialize your database through a ConnectionFactory
.
@Configuration
public class InitializerConfiguration {
@Bean
public ConnectionFactoryInitializer initializer(ConnectionFactory connectionFactory) {
ConnectionFactoryInitializer initializer = new ConnectionFactoryInitializer();
initializer.setConnectionFactory(connectionFactory);
CompositeDatabasePopulator populator = new CompositeDatabasePopulator();
populator.addPopulators(new ResourceDatabasePopulator(new ClassPathResource("com/foo/sql/db-schema.sql")));
populator.addPopulators(new ResourceDatabasePopulator(new ClassPathResource("com/foo/sql/test-data1.sql")));
initializer.setDatabasePopulator(populator);
return initializer;
}
}
This initialization support is used by Spring Boot R2DBC auto-configuration to apply schema.sql
and data.sql
to the configured ConnectionFactory
.
This release ships with Kotlin extensions for idiomatic usage of Spring Data R2DBC when developing applications with Kotlin.
Spring Data R2DBC provides the following extensions:
DatabaseClient
and Criteria
DatabaseClient
To retrieve a list of User
objects in Java, you would normally write the following:
Flux<User> characters = client.select().from(User.class).fetch().all();
With Kotlin and the Spring Data extensions, you can instead write the following:
val users = client.select().from<User>().fetch().all()
Kotlin Coroutines are Kotlin lightweight threads that allow writing non-blocking code in an imperative style.
Spring Data R2DBC's Coroutines extensions leverages the reactive infrastructure to expose functions you can suspend
and bridge Flux
results into Kotlin's Flow
type.
The following example shows Coroutine usage with Spring Framework's TransactionalOperator
to atomically insert two rows:
operator.executeAndAwait {
client.execute("INSERT INTO person VALUES(:first_name, :last_name)")
.bind("first_name", "John")
.bind("last_name", "Doe")
.await()
client.execute("INSERT INTO person_events VALUES(:first_name, :last_name, :event_type)")
.bind("first_name", "John")
.bind("last_name", "Doe")
.bind("event_type", "CREATED")
.await()
}
Note that most suspend
methods end with await
, as an indicator for Coroutines methods.
As of now, the R2DBC driver eco-system consists of only a few known drivers, but the number of drivers is growing. To enable seamless integration with additional drivers, Spring Data R2DBC exposes an extension point for R2dbcDialect
registration. Spring Data R2DBC can auto-discover a R2dbcDialect
by registering a class that implements org.springframework.data.r2dbc.dialect.DialectResolver$R2dbcDialectProvider
through Spring's META-INF/spring.factories
mechanism. The following listing shows how to do so:
public class MyDialectProvider implements DialectResolver.R2dbcDialectProvider {
@Override
public Optional<R2dbcDialect> getDialect(ConnectionFactory connectionFactory) {
if (connectionFactory.getMetadata().getName().equals("my-dastabase")) {
return Optional.of(…);
}
return Optional.empty();
}
}
spring.factories
org.springframework.data.r2dbc.dialect.DialectResolver$R2dbcDialectProvider=com.example.MyDialectProvider
If you use Maven, add the following lines to your pom.xml
file to upgrade to Spring Data R2DBC 1.0 RC1:
<dependencies>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-r2dbc</artifactId>
<version>1.0.0.RC1</version>
</dependency>
</dependencies>
<!-- R2DBC 0.8.0.RC1 required -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.r2dbc</groupId>
<artifactId>r2dbc-bom</artifactId>
<version>Arabba-RC1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<repositories>
<repository>
<id>spring-milestone</id>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
R2DBC 0.8.0 RC1 was recently released, and we expect a GA release later this year. We're working towards a Spring Data R2DBC 1.0 GA release and will ship the next release once R2DBC itself is GA. Our backlog contains a series of enhancements, such as query derivation, optimistic locking, and allowing for query interception to alter bindings and SQL statements before they get executed.
To round things off, here are links to the changelog, GitHub repository, and docs: