Skip to content

Commit 36da637

Browse files
committed
#64 - API polishing.
Document fluent API. Add fluent API for update. Introduce StatementMapper. Migrate Insert to StatementMapper. Refactoring and cleanup. Migrate Select to StatementMapper.
1 parent 92c89a7 commit 36da637

28 files changed

+2368
-1733
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
[[r2dbc.datbaseclient.fluent-api]]
2+
= Fluent Data Access API
3+
4+
You have already seen ``DatabaseClient``s SQL API that offers you maximum flexibility to execute any type of SQL.
5+
`DatabaseClient` provides a more narrow interface for typical ad-hoc use-cases such as querying, inserting, updating, and deleting data.
6+
7+
The entry points (`insert()`, `select()`, `update()`, and others) follow a natural naming schema based on the operation to be run. Moving on from the entry point, the API is designed to offer only context-dependent methods that lead to a terminating method that creates and runs a SQL statement. Spring Data R2DBC uses a `Dialect` abstraction to determine bind markers, pagination support and data types natively supported by the underlying driver.
8+
9+
Let's take a look at a simple query:
10+
11+
====
12+
[source,java]
13+
----
14+
Flux<Person> people = databaseClient.select()
15+
.from(Person.class) <1>
16+
.fetch()
17+
.all(); <2>
18+
----
19+
<1> Using `Person` with the `from(…)` method sets the `FROM` table based on mapping metadata. It also maps tabular results on `Person` result objects.
20+
<2> Fetching `all()` rows returns a `Flux<Person>` without limiting results.
21+
====
22+
23+
The following example declares a more complex query that specifies the table name by name, a `WHERE` condition and `ORDER BY` clause:
24+
25+
====
26+
[source,java]
27+
----
28+
Mono<Person> first = databaseClient.select()
29+
.from("legoset") <1>
30+
.matching(where("firstname").is("John") <2>
31+
.and("lastname").in("Doe", "White"))
32+
.orderBy(desc("id")) <3>
33+
.as(Person.class)
34+
.fetch()
35+
.one(); <4>
36+
----
37+
<1> Selecting from a table by name returns row results as `Map<String, Object>` with case-insensitive column name matching.
38+
<2> The issued query declares a `WHERE` condition on `firstname` and `lastname` columns to filter results.
39+
<3> Results can be ordered by individual column names resulting in an `ORDER BY` clause.
40+
<4> Selecting the one result fetches just a single row. This way of consuming rows expects the query to return exactly a single result. `Mono` emits a `IncorrectResultSizeDataAccessException` if the query yields more than a single result.
41+
====
42+
43+
You can consume Query results in three ways:
44+
45+
* Through object mapping (e.g. `as(Class<T>)`) using Spring Data's mapping-metadata.
46+
* As `Map<String, Object>` where column names are mapped to their value. Column names are looked up case-insensitive.
47+
* By supplying a mapping `BiFunction` for direct access to R2DBC `Row` and `RowMetadata`
48+
49+
You can switch between retrieving a single entity and retrieving multiple entities as through the terminating methods:
50+
51+
* `first()`: Consume only the first row returning a `Mono`. The returned `Mono` completes without emitting an object if the query returns no results.
52+
* `one()`: Consume exactly one row returning a `Mono`. The returned `Mono` completes without emitting an object if the query returns no results. If the query returns more than row then `Mono` completes exceptionally emitting `IncorrectResultSizeDataAccessException`.
53+
* `all()`: Consume all returned rows returning a `Flux`.
54+
* `rowsUpdated`: Consume the number of affected rows. Typically used with `INSERT`/`UPDATE`/`DELETE` statements.
55+
56+
[[r2dbc.datbaseclient.fluent-api.select]]
57+
== Selecting Data
58+
59+
Use the `select()` entry point to express your `SELECT` queries.
60+
The resulting `SELECT` queries support the commonly used clauses `WHERE`, `ORDER BY` and support pagination.
61+
The fluent API style allows you to chain together multiple methods while having easy-to-understand code.
62+
To improve readability, use static imports that allow you avoid using the 'new' keyword for creating `Criteria` instances.
63+
64+
[r2dbc.datbaseclient.fluent-api.criteria]]
65+
==== Methods for the Criteria Class
66+
67+
The `Criteria` class provides the following methods, all of which correspond to SQL operators:
68+
69+
* `Criteria` *and* `(String column)` Adds a chained `Criteria` with the specified `property` to the current `Criteria` and returns the newly created one.
70+
* `Criteria` *or* `(String column)` Adds a chained `Criteria` with the specified `property` to the current `Criteria` and returns the newly created one.
71+
* `Criteria` *greaterThan* `(Object o)` Creates a criterion using the `>` operator.
72+
* `Criteria` *greaterThanOrEquals* `(Object o)` Creates a criterion using the `>=` operator.
73+
* `Criteria` *in* `(Object... o)` Creates a criterion using the `IN` operator for a varargs argument.
74+
* `Criteria` *in* `(Collection<?> collection)` Creates a criterion using the `IN` operator using a collection.
75+
* `Criteria` *is* `(Object o)` Creates a criterion using column matching (`property = value`).
76+
* `Criteria` *isNull* `()` Creates a criterion using the `IS NULL` operator.
77+
* `Criteria` *isNotNull* `()` Creates a criterion using the `IS NOT NULL` operator.
78+
* `Criteria` *lessThan* `(Object o)` Creates a criterion using the `<` operator.
79+
* `Criteria` *lessThanOrEquals* `(Object o)` Creates a criterion using the `<=` operator.
80+
* `Criteria` *like* `(Object o)` Creates a criterion using the `LIKE` operator without escape character processing.
81+
* `Criteria` *not* `(Object o)` Creates a criterion using the `!=` operator.
82+
* `Criteria` *notIn* `(Object... o)` Creates a criterion using the `NOT IN` operator for a varargs argument.
83+
* `Criteria` *notIn* `(Collection<?> collection)` Creates a criterion using the `NOT IN` operator using a collection.
84+
85+
You can use `Criteria` with `SELECT`, `UPDATE`, and `DELETE` queries.
86+
87+
[r2dbc.datbaseclient.fluent-api.select.methods]]
88+
==== Methods for SELECT operations
89+
90+
The `select()` entry point exposes some additional methods that provide options for the query:
91+
92+
* *from* `(Class<T>)` used to specify the source table using a mapped object. Returns results by default as `T`.
93+
* *from* `(String)` used to specify the source table name. Returns results by default as `Map<String, Object>`.
94+
* *as* `(Class<T>)` used to map results to `T`.
95+
* *map* `(BiFunction<Row, RowMetadata, T>)` used to supply a mapping function to extract results.
96+
* *project* `(String... columns)` used to specify which columns to return.
97+
* *matching* `(Criteria)` used to declare a `WHERE` condition to filter results.
98+
* *orderBy* `(Order)` used to declare a `ORDER BY` clause to sort results.
99+
* *page* `(Page pageable)` used to retrieve a particular page within the result. Limits the size of the returned results and reads from a offset.
100+
* *fetch* `()` transition call declaration to the fetch stage to declare result consumption multiplicity.
101+
102+
[[r2dbc.datbaseclient.fluent-api.insert]]
103+
== Inserting Data
104+
105+
Use the `insert()` entry point to insert data. Similar to `select()`, `insert()` allows free-form and mapped object inserts.
106+
107+
Take a look at a simple typed insert operation:
108+
109+
====
110+
[source,java]
111+
----
112+
Mono<Void> insert = databaseClient.insert()
113+
.into(Person.class) <1>
114+
.using(new Person(…)) <2>
115+
.then(); <3>
116+
----
117+
<1> Using `Person` with the `into(…)` method sets the `INTO` table based on mapping metadata. It also prepares the insert statement to accept `Person` objects for inserting.
118+
<2> Provide a scalar `Person` object. Alternatively, you can supply a `Publisher` to execute a stream of `INSERT` statements. This method extracts all non-``null`` values and inserts these.
119+
<3> Use `then()` to just insert an object without consuming further details. Modifying statements allow consumption of the number of affected rows or tabular results for consuming generated keys.
120+
====
121+
122+
Inserts also support untyped operations:
123+
124+
====
125+
[source,java]
126+
----
127+
Mono<Void> insert = databaseClient.insert()
128+
.into("person") <1>
129+
.value("firstname", "John") <2>
130+
.nullValue("lastname") <3>
131+
.then(); <4>
132+
----
133+
<1> Start an insert into the `person` table.
134+
<2> Provide a non-null value for `firstname`.
135+
<3> Set `lastname` to `null`.
136+
<3> Use `then()` to just insert an object without consuming further details. Modifying statements allow consumption of the number of affected rows or tabular results for consuming generated keys.
137+
====
138+
139+
[r2dbc.datbaseclient.fluent-api.insert.methods]]
140+
==== Methods for INSERT operations
141+
142+
The `insert()` entry point exposes some additional methods that provide options for the operation:
143+
144+
* *into* `(Class<T>)` used to specify the target table using a mapped object. Returns results by default as `T`.
145+
* *into* `(String)` used to specify the target table name. Returns results by default as `Map<String, Object>`.
146+
* *using* `(T)` used to specify the object to insert.
147+
* *using* `(Publisher<T>)` used to accept a stream of objects to insert.
148+
* *table* `(String)` used to override the target table name.
149+
* *value* `(String, Object)` used to provide a column value to insert.
150+
* *nullValue* `(String)` used to provide a null value to insert.
151+
* *map* `(BiFunction<Row, RowMetadata, T>)` used to supply a mapping function to extract results.
152+
* *then* `()` execute `INSERT` without consuming any results.
153+
* *fetch* `()` transition call declaration to the fetch stage to declare result consumption multiplicity.
154+
155+
[[r2dbc.datbaseclient.fluent-api.update]]
156+
== Updating Data
157+
158+
Use the `update()` entry point to update rows.
159+
Updating data starts with a specification of the table to update accepting `Update` specifying assignments. It also accepts `Criteria` to create a `WHERE` clause.
160+
161+
Take a look at a simple typed update operation:
162+
163+
====
164+
[source,java]
165+
----
166+
Person modified = …
167+
168+
Mono<Void> update = databaseClient.update()
169+
.table(Person.class) <1>
170+
.using(modified) <2>
171+
.then(); <3>
172+
----
173+
<1> Using `Person` with the `table(…)` method sets the table to update based on mapping metadata.
174+
<2> Provide a scalar `Person` object value. `using(…)` accepts the modified object and derives primary keys and updates all column values.
175+
<3> Use `then()` to just update rows an object without consuming further details. Modifying statements allow also consumption of the number of affected rows.
176+
====
177+
178+
Update also support untyped operations:
179+
180+
====
181+
[source,java]
182+
----
183+
Mono<Void> update = databaseClient.update()
184+
.table("person") <1>
185+
.using(Update.update("firstname", "Jane")) <2>
186+
.matching(where("firstname").is("John")) <3>
187+
.then(); <4>
188+
----
189+
<1> Update table `person`.
190+
<2> Provide a `Update` definition, which columns to update.
191+
<3> The issued query declares a `WHERE` condition on `firstname` columns to filter rows to update.
192+
<4> Use `then()` to just update rows an object without consuming further details. Modifying statements allow also consumption of the number of affected rows.
193+
====
194+
195+
[r2dbc.datbaseclient.fluent-api.delete.methods]]
196+
==== Methods for DELETE operations
197+
198+
The `delete()` entry point exposes some additional methods that provide options for the operation:
199+
200+
* *table* `(Class<T>)` used to specify the target table using a mapped object. Returns results by default as `T`.
201+
* *table* `(String)` used to specify the target table name. Returns results by default as `Map<String, Object>`.
202+
* *using* `(T)` used to specify the object to update. Derives criteria itself.
203+
* *using* `(Update)` used to specify the update definition.
204+
* *matching* `(Criteria)` used to declare a `WHERE` condition to rows to update.
205+
* *then* `()` execute `UPDATE` without consuming any results.
206+
* *fetch* `()` transition call declaration to the fetch stage to fetch the number of updated rows.
207+
208+
[[r2dbc.datbaseclient.fluent-api.delete]]
209+
== Deleting Data
210+
211+
Use the `delete()` entry point to delete rows.
212+
Removing data starts with a specification of the table to delete from and optionally accepts a `Criteria` to create a `WHERE` clause.
213+
214+
Take a look at a simple insert operation:
215+
216+
====
217+
[source,java]
218+
----
219+
Mono<Void> delete = databaseClient.delete()
220+
.from(Person.class) <1>
221+
.matching(where("firstname").is("John") <2>
222+
.and("lastname").in("Doe", "White"))
223+
.then(); <3>
224+
----
225+
<1> Using `Person` with the `from(…)` method sets the `FROM` table based on mapping metadata.
226+
<2> The issued query declares a `WHERE` condition on `firstname` and `lastname` columns to filter rows to delete.
227+
<3> Use `then()` to just delete rows an object without consuming further details. Modifying statements allow also consumption of the number of affected rows.
228+
====
229+
230+
[r2dbc.datbaseclient.fluent-api.delete.methods]]
231+
==== Methods for DELETE operations
232+
233+
The `delete()` entry point exposes some additional methods that provide options for the operation:
234+
235+
* *from* `(Class<T>)` used to specify the target table using a mapped object. Returns results by default as `T`.
236+
* *from* `(String)` used to specify the target table name. Returns results by default as `Map<String, Object>`.
237+
* *matching* `(Criteria)` used to declare a `WHERE` condition to rows to delete.
238+
* *then* `()` execute `DELETE` without consuming any results.
239+
* *fetch* `()` transition call declaration to the fetch stage to fetch the number of deleted rows.

src/main/asciidoc/reference/r2dbc-sql.adoc

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[[r2dbc.datbaseclient.statements]]
2-
= Running Statements
2+
= Executing Statements
33

44
Running a statement is the basic functionality that is covered by `DatabaseClient`.
55
The following example shows what you need to include for a minimal but fully functional class that creates a new table:

src/main/asciidoc/reference/r2dbc-transactions.adoc

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[[r2dbc.datbaseclient.transactions]]
2-
== Transactions
2+
= Transactions
33

44
A common pattern when using relational databases is grouping multiple queries within a unit of work that is guarded by a transaction.
55
Relational databases typically associate a transaction with a single transport connection.

src/main/asciidoc/reference/r2dbc.adoc

+2
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,6 @@ include::r2dbc-databaseclient.adoc[leveloffset=+1]
77

88
include::r2dbc-sql.adoc[leveloffset=+1]
99

10+
include::r2dbc-fluent.adoc[leveloffset=+1]
11+
1012
include::r2dbc-transactions.adoc[leveloffset=+1]

0 commit comments

Comments
 (0)