775 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
			
		
		
	
	
			775 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
| [[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.
 | |
| 
 | |
| 
 | |
| 
 |