Spring Framework
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

774 lines
27 KiB

[[r2dbc]]
= Data Access with R2DBC
https://r2dbc.io[R2DBC] ("Reactive Relational Database Connectivity") is a community-driven
specification effort to standardize access to SQL databases using reactive patterns.
[[r2dbc-packages]]
== Package Hierarchy
The Spring Framework's R2DBC abstraction framework consists of two different packages:
* `core`: The `org.springframework.r2dbc.core` package contains the `DatabaseClient`
class plus a variety of related classes. See
xref:data-access/r2dbc.adoc#r2dbc-core[Using the R2DBC Core Classes to Control Basic R2DBC Processing and Error Handling].
* `connection`: The `org.springframework.r2dbc.connection` package contains a utility class
for easy `ConnectionFactory` access and various simple `ConnectionFactory` implementations
that you can use for testing and running unmodified R2DBC. See
xref:data-access/r2dbc.adoc#r2dbc-connections[Controlling Database Connections].
[[r2dbc-core]]
== Using the R2DBC Core Classes to Control Basic R2DBC Processing and Error Handling
This section covers how to use the R2DBC core classes to control basic R2DBC processing,
including error handling. It includes the following topics:
* xref:data-access/r2dbc.adoc#r2dbc-DatabaseClient[Using `DatabaseClient`]
* xref:data-access/r2dbc.adoc#r2dbc-DatabaseClient-examples-statement[Executing Statements]
* xref:data-access/r2dbc.adoc#r2dbc-DatabaseClient-examples-query[Querying (`SELECT`)]
* xref:data-access/r2dbc.adoc#r2dbc-DatabaseClient-examples-update[Updating (`INSERT`, `UPDATE`, and `DELETE`) with `DatabaseClient`]
* xref:data-access/r2dbc.adoc#r2dbc-DatabaseClient-filter[Statement Filters]
* xref:data-access/r2dbc.adoc#r2dbc-auto-generated-keys[Retrieving Auto-generated Keys]
[[r2dbc-DatabaseClient]]
=== Using `DatabaseClient`
`DatabaseClient` is the central class in the R2DBC core package. It handles the
creation and release of resources, which helps to avoid common errors, such as
forgetting to close the connection. It performs the basic tasks of the core R2DBC
workflow (such as statement creation and execution), leaving application code to provide
SQL and extract results. The `DatabaseClient` class:
* Runs SQL queries
* Update statements and stored procedure calls
* Performs iteration over `Result` instances
* Catches R2DBC exceptions and translates them to the generic, more informative,
exception hierarchy defined in the `org.springframework.dao` package.
(See xref:data-access/dao.adoc#dao-exceptions[Consistent Exception Hierarchy].)
The client has a functional, fluent API using reactive types for declarative composition.
When you use the `DatabaseClient` for your code, you need only to implement
`java.util.function` interfaces, giving them a clearly defined contract.
Given a `Connection` provided by the `DatabaseClient` class, a `Function`
callback creates a `Publisher`. The same is true for mapping functions that
extract a `Row` result.
You can use `DatabaseClient` within a DAO implementation through direct instantiation
with a `ConnectionFactory` reference, or you can configure it in a Spring IoC container
and give it to DAOs as a bean reference.
The simplest way to create a `DatabaseClient` object is through a static factory method, as follows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
DatabaseClient client = DatabaseClient.create(connectionFactory);
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
val client = DatabaseClient.create(connectionFactory)
----
======
NOTE: The `ConnectionFactory` should always be configured as a bean in the Spring IoC
container.
The preceding method creates a `DatabaseClient` with default settings.
You can also obtain a `Builder` instance from `DatabaseClient.builder()`.
You can customize the client by calling the following methods:
* `….bindMarkers(…)`: Supply a specific `BindMarkersFactory` to configure named
parameter to database bind marker translation.
* `….executeFunction(…)`: Set the `ExecuteFunction` how `Statement` objects get
run.
* `….namedParameters(false)`: Disable named parameter expansion. Enabled by default.
TIP: Dialects are resolved by {api-spring-framework}/r2dbc/core/binding/BindMarkersFactoryResolver.html[`BindMarkersFactoryResolver`]
from a `ConnectionFactory`, typically by inspecting `ConnectionFactoryMetadata`.
+
You can let Spring auto-discover your `BindMarkersFactory` by registering a
class that implements `org.springframework.r2dbc.core.binding.BindMarkersFactoryResolver$BindMarkerFactoryProvider`
through `META-INF/spring.factories`.
`BindMarkersFactoryResolver` discovers bind marker provider implementations from
the class path using Spring's `SpringFactoriesLoader`.
+
Currently supported databases are:
* H2
* MariaDB
* Microsoft SQL Server
* MySQL
* Postgres
All SQL issued by this class is logged at the `DEBUG` level under the category
corresponding to the fully qualified class name of the client instance (typically
`DefaultDatabaseClient`). Additionally, each execution registers a checkpoint in
the reactive sequence to aid debugging.
The following sections provide some examples of `DatabaseClient` usage. These examples
are not an exhaustive list of all of the functionality exposed by the `DatabaseClient`.
See the attendant {api-spring-framework}/r2dbc/core/DatabaseClient.html[javadoc] for that.
[[r2dbc-DatabaseClient-examples-statement]]
==== Executing Statements
`DatabaseClient` provides the basic functionality of running a statement.
The following example shows what you need to include for minimal but fully functional
code that creates a new table:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
Mono<Void> completion = client.sql("CREATE TABLE person (id VARCHAR(255) PRIMARY KEY, name VARCHAR(255), age INTEGER);")
.then();
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
client.sql("CREATE TABLE person (id VARCHAR(255) PRIMARY KEY, name VARCHAR(255), age INTEGER);")
.await()
----
======
`DatabaseClient` is designed for convenient, fluent usage.
It exposes intermediate, continuation, and terminal methods at each stage of the
execution specification. The preceding example above uses `then()` to return a completion
`Publisher` that completes as soon as the query (or queries, if the SQL query contains
multiple statements) completes.
NOTE: `execute(…)` accepts either the SQL query string or a query `Supplier<String>`
to defer the actual query creation until execution.
[[r2dbc-DatabaseClient-examples-query]]
==== Querying (`SELECT`)
SQL queries can return values through `Row` objects or the number of affected rows.
`DatabaseClient` can return the number of updated rows or the rows themselves,
depending on the issued query.
The following query gets the `id` and `name` columns from a table:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
Mono<Map<String, Object>> first = client.sql("SELECT id, name FROM person")
.fetch().first();
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
val first = client.sql("SELECT id, name FROM person")
.fetch().awaitSingle()
----
======
The following query uses a bind variable:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
Mono<Map<String, Object>> first = client.sql("SELECT id, name FROM person WHERE first_name = :fn")
.bind("fn", "Joe")
.fetch().first();
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
val first = client.sql("SELECT id, name FROM person WHERE first_name = :fn")
.bind("fn", "Joe")
.fetch().awaitSingle()
----
======
You might have noticed the use of `fetch()` in the example above. `fetch()` is a
continuation operator that lets you specify how much data you want to consume.
Calling `first()` returns the first row from the result and discards remaining rows.
You can consume data with the following operators:
* `first()` return the first row of the entire result. Its Kotlin Coroutine variant
is named `awaitSingle()` for non-nullable return values and `awaitSingleOrNull()`
if the value is optional.
* `one()` returns exactly one result and fails if the result contains more rows.
Using Kotlin Coroutines, `awaitOne()` for exactly one value or `awaitOneOrNull()`
if the value may be `null`.
* `all()` returns all rows of the result. When using Kotlin Coroutines, use `flow()`.
* `rowsUpdated()` returns the number of affected rows (`INSERT`/`UPDATE`/`DELETE`
count). Its Kotlin Coroutine variant is named `awaitRowsUpdated()`.
Without specifying further mapping details, queries return tabular results
as `Map` whose keys are case-insensitive column names that map to their column value.
You can take control over result mapping by supplying a `Function<Row, T>` that gets
called for each `Row` so it can return arbitrary values (singular values,
collections and maps, and objects).
The following example extracts the `name` column and emits its value:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
Flux<String> names = client.sql("SELECT name FROM person")
.map(row -> row.get("name", String.class))
.all();
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
val names = client.sql("SELECT name FROM person")
.map{ row: Row -> row.get("name", String.class) }
.flow()
----
======
Alternatively, there is a shortcut for mapping to a single value:
[source,java]
----
Flux<String> names = client.sql("SELECT name FROM person")
.mapValue(String.class)
.all();
----
Or you may map to a result object with bean properties or record components:
[source,java]
----
// assuming a name property on Person
Flux<Person> persons = client.sql("SELECT name FROM person")
.mapProperties(Person.class)
.all();
----
[[r2dbc-DatabaseClient-mapping-null]]
.What about `null`?
****
Relational database results can contain `null` values.
The Reactive Streams specification forbids the emission of `null` values.
That requirement mandates proper `null` handling in the extractor function.
While you can obtain `null` values from a `Row`, you must not emit a `null`
value. You must wrap any `null` values in an object (for example, `Optional`
for singular values) to make sure a `null` value is never returned directly
by your extractor function.
****
[[r2dbc-DatabaseClient-examples-update]]
==== Updating (`INSERT`, `UPDATE`, and `DELETE`) with `DatabaseClient`
The only difference of modifying statements is that these statements typically
do not return tabular data so you use `rowsUpdated()` to consume results.
The following example shows an `UPDATE` statement that returns the number
of updated rows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
Mono<Integer> affectedRows = client.sql("UPDATE person SET first_name = :fn")
.bind("fn", "Joe")
.fetch().rowsUpdated();
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
val affectedRows = client.sql("UPDATE person SET first_name = :fn")
.bind("fn", "Joe")
.fetch().awaitRowsUpdated()
----
======
[[r2dbc-DatabaseClient-named-parameters]]
==== Binding Values to Queries
A typical application requires parameterized SQL statements to select or
update rows according to some input. These are typically `SELECT` statements
constrained by a `WHERE` clause or `INSERT` and `UPDATE` statements that accept
input parameters. Parameterized statements bear the risk of SQL injection if
parameters are not escaped properly. `DatabaseClient` leverages R2DBC's
`bind` API to eliminate the risk of SQL injection for query parameters.
You can provide a parameterized SQL statement with the `execute(…)` operator
and bind parameters to the actual `Statement`. Your R2DBC driver then runs
the statement by using prepared statements and parameter substitution.
Parameter binding supports two binding strategies:
* By Index, using zero-based parameter indexes.
* By Name, using the placeholder name.
The following example shows parameter binding for a query:
[source,java]
----
db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
.bind("id", "joe")
.bind("name", "Joe")
.bind("age", 34);
----
Alternatively, you may pass in a map of names and values:
[source,java]
----
Map<String, Object> params = new LinkedHashMap<>();
params.put("id", "joe");
params.put("name", "Joe");
params.put("age", 34);
db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
.bindValues(params);
----
Or you may pass in a parameter object with bean properties or record components:
[source,java]
----
// assuming id, name, age properties on Person
db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
.bindProperties(new Person("joe", "Joe", 34);
----
.R2DBC Native Bind Markers
****
R2DBC uses database-native bind markers that depend on the actual database vendor.
As an example, Postgres uses indexed markers, such as `$1`, `$2`, `$n`.
Another example is SQL Server, which uses named bind markers prefixed with `@`.
This is different from JDBC which requires `?` as bind markers.
In JDBC, the actual drivers translate `?` bind markers to database-native
markers as part of their statement execution.
Spring Framework's R2DBC support lets you use native bind markers or named bind
markers with the `:name` syntax.
Named parameter support leverages a `BindMarkersFactory` instance to expand named
parameters to native bind markers at the time of query execution, which gives you
a certain degree of query portability across various database vendors.
****
The query-preprocessor unrolls named `Collection` parameters into a series of bind
markers to remove the need of dynamic query creation based on the number of arguments.
Nested object arrays are expanded to allow usage of (for example) select lists.
Consider the following query:
[source,sql]
----
SELECT id, name, state FROM table WHERE (name, age) IN (('John', 35), ('Ann', 50))
----
The preceding query can be parameterized and run as follows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
List<Object[]> tuples = new ArrayList<>();
tuples.add(new Object[] {"John", 35});
tuples.add(new Object[] {"Ann", 50});
client.sql("SELECT id, name, state FROM table WHERE (name, age) IN (:tuples)")
.bind("tuples", tuples);
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
val tuples: MutableList<Array<Any>> = ArrayList()
tuples.add(arrayOf("John", 35))
tuples.add(arrayOf("Ann", 50))
client.sql("SELECT id, name, state FROM table WHERE (name, age) IN (:tuples)")
.bind("tuples", tuples)
----
======
NOTE: Usage of select lists is vendor-dependent.
The following example shows a simpler variant using `IN` predicates:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
client.sql("SELECT id, name, state FROM table WHERE age IN (:ages)")
.bind("ages", Arrays.asList(35, 50));
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
val tuples: MutableList<Array<Any>> = ArrayList()
tuples.add(arrayOf("John", 35))
tuples.add(arrayOf("Ann", 50))
client.sql("SELECT id, name, state FROM table WHERE age IN (:ages)")
.bind("tuples", arrayOf(35, 50))
----
======
NOTE: R2DBC itself does not support Collection-like values. Nevertheless,
expanding a given `List` in the example above works for named parameters
in Spring's R2DBC support, e.g. for use in `IN` clauses as shown above.
However, inserting or updating array-typed columns (e.g. in Postgres)
requires an array type that is supported by the underlying R2DBC driver:
typically a Java array, e.g. `String[]` to update a `text[]` column.
Do not pass `Collection<String>` or the like as an array parameter.
[[r2dbc-DatabaseClient-filter]]
==== Statement Filters
Sometimes you need to fine-tune options on the actual `Statement`
before it gets run. To do so, register a `Statement` filter
(`StatementFilterFunction`) with the `DatabaseClient` to intercept and
modify statements in their execution, as the following example shows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
.filter((s, next) -> next.execute(s.returnGeneratedValues("id")))
.bind("name", …)
.bind("state", …);
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
.filter { s: Statement, next: ExecuteFunction -> next.execute(s.returnGeneratedValues("id")) }
.bind("name", …)
.bind("state", …)
----
======
`DatabaseClient` also exposes a simplified `filter(…)` overload that accepts
a `Function<Statement, Statement>`:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
.filter(statement -> s.returnGeneratedValues("id"));
client.sql("SELECT id, name, state FROM table")
.filter(statement -> s.fetchSize(25));
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
.filter { statement -> s.returnGeneratedValues("id") }
client.sql("SELECT id, name, state FROM table")
.filter { statement -> s.fetchSize(25) }
----
======
`StatementFilterFunction` implementations allow filtering of the
`Statement` and filtering of `Result` objects.
[[r2dbc-DatabaseClient-idioms]]
==== `DatabaseClient` Best Practices
Instances of the `DatabaseClient` class are thread-safe, once configured. This is
important because it means that you can configure a single instance of a `DatabaseClient`
and then safely inject this shared reference into multiple DAOs (or repositories).
The `DatabaseClient` is stateful, in that it maintains a reference to a `ConnectionFactory`,
but this state is not conversational state.
A common practice when using the `DatabaseClient` class is to configure a `ConnectionFactory`
in your Spring configuration file and then dependency-inject
that shared `ConnectionFactory` bean into your DAO classes. The `DatabaseClient` is created in
the setter for the `ConnectionFactory`. This leads to DAOs that resemble the following:
--
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
public class R2dbcCorporateEventDao implements CorporateEventDao {
private DatabaseClient databaseClient;
public void setConnectionFactory(ConnectionFactory connectionFactory) {
this.databaseClient = DatabaseClient.create(connectionFactory);
}
// R2DBC-backed implementations of the methods on the CorporateEventDao follow...
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
class R2dbcCorporateEventDao(connectionFactory: ConnectionFactory) : CorporateEventDao {
private val databaseClient = DatabaseClient.create(connectionFactory)
// R2DBC-backed implementations of the methods on the CorporateEventDao follow...
}
----
======
--
An alternative to explicit configuration is to use component-scanning and annotation
support for dependency injection. In this case, you can annotate the class with `@Component`
(which makes it a candidate for component-scanning) and annotate the `ConnectionFactory` setter
method with `@Autowired`. The following example shows how to do so:
--
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
@Component // <1>
public class R2dbcCorporateEventDao implements CorporateEventDao {
private DatabaseClient databaseClient;
@Autowired // <2>
public void setConnectionFactory(ConnectionFactory connectionFactory) {
this.databaseClient = DatabaseClient.create(connectionFactory); // <3>
}
// R2DBC-backed implementations of the methods on the CorporateEventDao follow...
}
----
<1> Annotate the class with `@Component`.
<2> Annotate the `ConnectionFactory` setter method with `@Autowired`.
<3> Create a new `DatabaseClient` with the `ConnectionFactory`.
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
@Component // <1>
class R2dbcCorporateEventDao(connectionFactory: ConnectionFactory) : CorporateEventDao { // <2>
private val databaseClient = DatabaseClient(connectionFactory) // <3>
// R2DBC-backed implementations of the methods on the CorporateEventDao follow...
}
----
<1> Annotate the class with `@Component`.
<2> Constructor injection of the `ConnectionFactory`.
<3> Create a new `DatabaseClient` with the `ConnectionFactory`.
======
--
Regardless of which of the above template initialization styles you choose to use (or
not), it is seldom necessary to create a new instance of a `DatabaseClient` class each
time you want to run SQL. Once configured, a `DatabaseClient` instance is thread-safe.
If your application accesses multiple
databases, you may want multiple `DatabaseClient` instances, which requires multiple
`ConnectionFactory` and, subsequently, multiple differently configured `DatabaseClient`
instances.
[[r2dbc-auto-generated-keys]]
== Retrieving Auto-generated Keys
`INSERT` statements may generate keys when inserting rows into a table
that defines an auto-increment or identity column. To get full control over
the column name to generate, simply register a `StatementFilterFunction` that
requests the generated key for the desired column.
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
Mono<Integer> generatedId = client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
.filter(statement -> s.returnGeneratedValues("id"))
.map(row -> row.get("id", Integer.class))
.first();
// generatedId emits the generated key once the INSERT statement has finished
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
val generatedId = client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
.filter { statement -> s.returnGeneratedValues("id") }
.map { row -> row.get("id", Integer.class) }
.awaitOne()
// generatedId emits the generated key once the INSERT statement has finished
----
======
[[r2dbc-connections]]
== Controlling Database Connections
This section covers:
* xref:data-access/r2dbc.adoc#r2dbc-ConnectionFactory[Using `ConnectionFactory`]
* xref:data-access/r2dbc.adoc#r2dbc-ConnectionFactoryUtils[Using `ConnectionFactoryUtils`]
* xref:data-access/r2dbc.adoc#r2dbc-SingleConnectionFactory[Using `SingleConnectionFactory`]
* xref:data-access/r2dbc.adoc#r2dbc-TransactionAwareConnectionFactoryProxy[Using `TransactionAwareConnectionFactoryProxy`]
* xref:data-access/r2dbc.adoc#r2dbc-R2dbcTransactionManager[Using `R2dbcTransactionManager`]
[[r2dbc-ConnectionFactory]]
=== Using `ConnectionFactory`
Spring obtains an R2DBC connection to the database through a `ConnectionFactory`.
A `ConnectionFactory` is part of the R2DBC specification and is a common entry-point
for drivers. It lets a container or a framework hide connection pooling
and transaction management issues from the application code. As a developer,
you need not know details about how to connect to the database. That is the
responsibility of the administrator who sets up the `ConnectionFactory`. You
most likely fill both roles as you develop and test code, but you do not
necessarily have to know how the production data source is configured.
When you use Spring's R2DBC layer, you can configure your own with a
connection pool implementation provided by a third party. A popular
implementation is R2DBC Pool (`r2dbc-pool`). Implementations in the Spring
distribution are meant only for testing purposes and do not provide pooling.
To configure a `ConnectionFactory`:
. Obtain a connection with `ConnectionFactory` as you typically obtain an R2DBC `ConnectionFactory`.
. Provide an R2DBC URL
(See the documentation for your driver for the correct value).
The following example shows how to configure a `ConnectionFactory`:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
ConnectionFactory factory = ConnectionFactories.get("r2dbc:h2:mem:///test?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE");
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
val factory = ConnectionFactories.get("r2dbc:h2:mem:///test?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE");
----
======
[[r2dbc-ConnectionFactoryUtils]]
=== Using `ConnectionFactoryUtils`
The `ConnectionFactoryUtils` class is a convenient and powerful helper class
that provides `static` methods to obtain connections from `ConnectionFactory`
and close connections (if necessary).
It supports subscriber ``Context``-bound connections with, for example
`R2dbcTransactionManager`.
[[r2dbc-SingleConnectionFactory]]
=== Using `SingleConnectionFactory`
The `SingleConnectionFactory` class is an implementation of `DelegatingConnectionFactory`
interface that wraps a single `Connection` that is not closed after each use.
If any client code calls `close` on the assumption of a pooled connection (as when using
persistence tools), you should set the `suppressClose` property to `true`. This setting
returns a close-suppressing proxy that wraps the physical connection. Note that you can
no longer cast this to a native `Connection` or a similar object.
`SingleConnectionFactory` is primarily a test class and may be used for specific requirements
such as pipelining if your R2DBC driver permits for such use.
In contrast to a pooled `ConnectionFactory`, it reuses the same connection all the time, avoiding
excessive creation of physical connections.
[[r2dbc-TransactionAwareConnectionFactoryProxy]]
=== Using `TransactionAwareConnectionFactoryProxy`
`TransactionAwareConnectionFactoryProxy` is a proxy for a target `ConnectionFactory`.
The proxy wraps that target `ConnectionFactory` to add awareness of Spring-managed transactions.
NOTE: Using this class is required if you use a R2DBC client that is not integrated otherwise
with Spring's R2DBC support. In this case, you can still use this client and, at
the same time, have this client participating in Spring managed transactions. It is generally
preferable to integrate a R2DBC client with proper access to `ConnectionFactoryUtils`
for resource management.
See the {api-spring-framework}/r2dbc/connection/TransactionAwareConnectionFactoryProxy.html[`TransactionAwareConnectionFactoryProxy`]
javadoc for more details.
[[r2dbc-R2dbcTransactionManager]]
=== Using `R2dbcTransactionManager`
The `R2dbcTransactionManager` class is a `ReactiveTransactionManager` implementation for
a single R2DBC `ConnectionFactory`. It binds an R2DBC `Connection` from the specified
`ConnectionFactory` to the subscriber `Context`, potentially allowing for one subscriber
`Connection` for each `ConnectionFactory`.
Application code is required to retrieve the R2DBC `Connection` through
`ConnectionFactoryUtils.getConnection(ConnectionFactory)`, instead of R2DBC's standard
`ConnectionFactory.create()`. All framework classes (such as `DatabaseClient`) use this
strategy implicitly. If not used with a transaction manager, the lookup strategy behaves
exactly like `ConnectionFactory.create()` and can therefore be used in any case.