Skip to content

Commit ff69a41

Browse files
mp911deodrotbohm
authored andcommitted
#64 - Split documentation for R2DBC core into parts.
Original pull request: #106.
1 parent fd4472a commit ff69a41

File tree

5 files changed

+548
-530
lines changed

5 files changed

+548
-530
lines changed
+241
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
The R2DBC support contains a wide range of features:
2+
3+
* Spring configuration support with Java-based `@Configuration` classes for an R2DBC driver instance.
4+
* `DatabaseClient` helper class that increases productivity when performing common R2DBC operations with integrated object mapping between rows and POJOs.
5+
* Exception translation into Spring's portable Data Access Exception hierarchy.
6+
* Feature-rich Object Mapping integrated with Spring's Conversion Service.
7+
* Annotation-based mapping metadata that is extensible to support other metadata formats.
8+
* Automatic implementation of Repository interfaces, including support for custom query methods.
9+
10+
For most tasks, you should use `DatabaseClient` or the Repository support, which both leverage the rich mapping functionality.
11+
`DatabaseClient` is the place to look for accessing functionality such as ad-hoc CRUD operations.
12+
13+
[[r2dbc.getting-started]]
14+
== Getting Started
15+
16+
An easy way to bootstrap setting up a working environment is to create a Spring-based project through https://start.spring.io[start.spring.io].
17+
18+
. Add the following to the pom.xml files `dependencies` element:
19+
+
20+
[source,xml,subs="+attributes"]
21+
----
22+
<dependencyManagement>
23+
<dependencies>
24+
<dependency>
25+
<groupId>io.r2dbc</groupId>
26+
<artifactId>r2dbc-bom</artifactId>
27+
<version>${r2dbc-releasetrain.version}</version>
28+
<type>pom</type>
29+
<scope>import</scope>
30+
</dependency>
31+
</dependencies>
32+
</dependencyManagement>
33+
34+
<dependencies>
35+
36+
<!-- other dependency elements omitted -->
37+
38+
<dependency>
39+
<groupId>org.springframework.data</groupId>
40+
<artifactId>spring-data-r2dbc</artifactId>
41+
<version>{version}</version>
42+
</dependency>
43+
44+
<!-- a R2DBC driver -->
45+
<dependency>
46+
<groupId>io.r2dbc</groupId>
47+
<artifactId>r2dbc-h2</artifactId>
48+
<version>{r2dbcVersion}</version>
49+
</dependency>
50+
51+
</dependencies>
52+
----
53+
. Change the version of Spring in the pom.xml to be
54+
+
55+
[source,xml,subs="+attributes"]
56+
----
57+
<spring.framework.version>{springVersion}</spring.framework.version>
58+
----
59+
. Add the following location of the Spring Milestone repository for Maven to your `pom.xml` such that it is at the same level of your `<dependencies/>` element:
60+
+
61+
[source,xml]
62+
----
63+
<repositories>
64+
<repository>
65+
<id>spring-milestone</id>
66+
<name>Spring Maven MILESTONE Repository</name>
67+
<url>https://repo.spring.io/libs-milestone</url>
68+
</repository>
69+
</repositories>
70+
----
71+
72+
The repository is also https://repo.spring.io/milestone/org/springframework/data/[browseable here].
73+
74+
You may also want to set the logging level to `DEBUG` to see some additional information. To do so, edit the `application.properties` file to have the following content:
75+
76+
[source]
77+
----
78+
logging.level.org.springframework.data.r2dbc=DEBUG
79+
----
80+
81+
Then you can create a `Person` class to persist:
82+
83+
[source,java]
84+
----
85+
package org.spring.r2dbc.example;
86+
87+
public class Person {
88+
89+
private String id;
90+
private String name;
91+
private int age;
92+
93+
public Person(String id, String name, int age) {
94+
this.id = id;
95+
this.name = name;
96+
this.age = age;
97+
}
98+
99+
public String getId() {
100+
return id;
101+
}
102+
public String getName() {
103+
return name;
104+
}
105+
public int getAge() {
106+
return age;
107+
}
108+
109+
@Override
110+
public String toString() {
111+
return "Person [id=" + id + ", name=" + name + ", age=" + age + "]";
112+
}
113+
}
114+
----
115+
116+
Next, you need to create a table structure in your database:
117+
118+
[source,sql]
119+
----
120+
CREATE TABLE person
121+
(id VARCHAR(255) PRIMARY KEY,
122+
name VARCHAR(255),
123+
age INT);
124+
----
125+
126+
You also need a main application to run:
127+
128+
[source,java]
129+
----
130+
package org.spring.r2dbc.example;
131+
132+
public class R2dbcApp {
133+
134+
private static final Log log = LogFactory.getLog(R2dbcApp.class);
135+
136+
public static void main(String[] args) throws Exception {
137+
138+
ConnectionFactory connectionFactory = ConnectionFactories.get("rdbc:h2:mem:///test?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE");
139+
140+
DatabaseClient client = DatabaseClient.create(connectionFactory);
141+
142+
client.execute()
143+
.sql("CREATE TABLE person" +
144+
"(id VARCHAR(255) PRIMARY KEY," +
145+
"name VARCHAR(255)," +
146+
"age INT)")
147+
.fetch()
148+
.rowsUpdated()
149+
.as(StepVerifier::create)
150+
.expectNextCount(1)
151+
.verifyComplete();
152+
153+
client.insert()
154+
.into(Person.class)
155+
.using(new Person("joe", "Joe", 34))
156+
.then()
157+
.as(StepVerifier::create)
158+
.verifyComplete();
159+
160+
client.select()
161+
.from(Person.class)
162+
.fetch()
163+
.first()
164+
.doOnNext(it -> log.info(it))
165+
.as(StepVerifier::create)
166+
.expectNextCount(1)
167+
.verifyComplete();
168+
}
169+
}
170+
----
171+
172+
When you run the main program, the preceding examples produce output similar to the following:
173+
174+
[source]
175+
----
176+
2018-11-28 10:47:03,893 DEBUG ata.r2dbc.function.DefaultDatabaseClient: 310 - Executing SQL statement [CREATE TABLE person
177+
(id VARCHAR(255) PRIMARY KEY,
178+
name VARCHAR(255),
179+
age INT)]
180+
2018-11-28 10:47:04,074 DEBUG ata.r2dbc.function.DefaultDatabaseClient: 908 - Executing SQL statement [INSERT INTO person (id, name, age) VALUES($1, $2, $3)]
181+
2018-11-28 10:47:04,092 DEBUG ata.r2dbc.function.DefaultDatabaseClient: 575 - Executing SQL statement [SELECT id, name, age FROM person]
182+
2018-11-28 10:47:04,436 INFO org.spring.r2dbc.example.R2dbcApp: 43 - Person [id='joe', name='Joe', age=34]
183+
----
184+
185+
Even in this simple example, there are few things to notice:
186+
187+
* You can create an instance of the central helper class in Spring Data R2DBC, <<r2dbc.datbaseclient,`DatabaseClient`>>, by using a standard `io.r2dbc.spi.ConnectionFactory` object.
188+
* The mapper works against standard POJO objects without the need for any additional metadata (though you can optionally provide that information. See <<mapping-chapter,here>>.).
189+
* Mapping conventions can use field access. Notice that the `Person` class has only getters.
190+
* If the constructor argument names match the column names of the stored row, they are used to instantiate the object.
191+
192+
[[r2dbc.examples-repo]]
193+
== Examples Repository
194+
195+
There is a https://github.com/spring-projects/spring-data-examples[GitHub repository with several examples] that you can download and play around with to get a feel for how the library works.
196+
197+
[[r2dbc.drivers]]
198+
== Connecting to a Relational Database with Spring
199+
200+
One of the first tasks when using relational databases and Spring is to create a `io.r2dbc.spi.ConnectionFactory` object using the IoC container. The following example explains Java-based configuration.
201+
202+
[[r2dbc.connectionfactory]]
203+
=== Registering a `ConnectionFactory` Instance using Java-based Metadata
204+
205+
The following example shows an example of using Java-based bean metadata to register an instance of a `io.r2dbc.spi.ConnectionFactory`:
206+
207+
.Registering a `io.r2dbc.spi.ConnectionFactory` object using Java-based bean metadata
208+
====
209+
[source,java]
210+
----
211+
@Configuration
212+
public class ApplicationConfiguration extends AbstractR2dbcConfiguration {
213+
214+
@Override
215+
@Bean
216+
public ConnectionFactory connectionFactory() {
217+
return …;
218+
}
219+
}
220+
----
221+
====
222+
223+
This approach lets you use the standard `io.r2dbc.spi.ConnectionFactory` instance, with the container using Spring's `AbstractR2dbcConfiguration`. As compared to registering a `ConnectionFactory` instance directly, the configuration support has the added advantage of also providing the container with an `ExceptionTranslator` implementation that translates R2DBC exceptions to exceptions in Spring's portable `DataAccessException` hierarchy for data access classes annotated with the `@Repository` annotation. This hierarchy and the use of `@Repository` is described in https://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/data-access.html[Spring's DAO support features].
224+
225+
`AbstractR2dbcConfiguration` registers also `DatabaseClient` that is required for database interaction and for Repository implementation.
226+
227+
[[r2dbc.drivers]]
228+
=== R2DBC Drivers
229+
230+
Spring Data R2DBC supports drivers by R2DBC's pluggable SPI mechanism. Any driver implementing the R2DBC spec can be used with Spring Data R2DBC.
231+
R2DBC is a relatively young initiative that gains significance by maturing through adoption.
232+
As of writing the following 3 drivers are available:
233+
234+
* https://github.com/r2dbc/r2dbc-postgresql[Postgres] (`io.r2dbc:r2dbc-postgresql`)
235+
* https://github.com/r2dbc/r2dbc-h2[H2] (`io.r2dbc:r2dbc-h2`)
236+
* https://github.com/r2dbc/r2dbc-mssql[Microsoft SQL Server] (`io.r2dbc:r2dbc-mssql`)
237+
* https://github.com/jasync-sql/jasync-sql[Microsoft SQL Server] (`com.github.jasync-sql:jasync-r2dbc-mysql`)
238+
239+
Spring Data R2DBC reacts to database specifics by inspecting `ConnectionFactoryMetadata` and selects the appropriate database dialect.
240+
You can configure an own `Dialect` if the used driver is not yet known to Spring Data R2DBC.
241+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
[[r2dbc.datbaseclient]]
2+
= Introduction to `DatabaseClient`
3+
4+
Spring Data R2DBC includes a reactive, non-blocking `DatabaseClient` for database interaction. The client has a functional, fluent API with reactive types for declarative composition.
5+
`DatabaseClient` encapsulates resource handling such as opening and closing connections so your application code can make use of executing SQL queries or calling higher-level functionality such as inserting or selecting data.
6+
7+
NOTE: `DatabaseClient` is a young application component providing a minimal set of convenience methods that is likely to be extended through time.
8+
9+
NOTE: Once configured, `DatabaseClient` is thread-safe and can be reused across multiple instances.
10+
11+
Another central feature of `DatabaseClient` is translation of exceptions thrown by R2DBC drivers into Spring's portable Data Access Exception hierarchy. See "`<<r2dbc.exception>>`" for more information.
12+
13+
The next section contains an example of how to work with the `DatabaseClient` in the context of the Spring container.
14+
15+
[[r2dbc.datbaseclient.create]]
16+
== Creating `DatabaseClient`
17+
18+
The simplest way to create a `DatabaseClient` is through a static factory method:
19+
20+
[source,java]
21+
----
22+
DatabaseClient.create(ConnectionFactory connectionFactory)
23+
----
24+
25+
The above method creates a `DatabaseClient` with default settings.
26+
27+
You can also use `DatabaseClient.builder()` with further options to customize the client:
28+
29+
* `exceptionTranslator`: Supply a specific `R2dbcExceptionTranslator` to customize how R2DBC exceptions are translated into Spring's portable Data Access Exception hierarchy. See "`<<r2dbc.exception>>`" for more information.
30+
* `dataAccessStrategy`: Strategy how SQL queries are generated and how objects are mapped.
31+
32+
Once built, a `DatabaseClient` instance is immutable. However, you can clone it and build a modified copy without affecting the original instance, as the following example shows:
33+
34+
[source,java]
35+
----
36+
DatabaseClient client1 = DatabaseClient.builder()
37+
.exceptionTranslator(exceptionTranslatorA).build();
38+
39+
DatabaseClient client2 = client1.mutate()
40+
.exceptionTranslator(exceptionTranslatorB).build();
41+
----
42+
43+
== Controlling Database Connections
44+
45+
Spring Data R2DBC obtains a connection to the database through a `ConnectionFactory`.
46+
A `ConnectionFactory` is part of the R2DBC specification and is a generalized connection factory.
47+
It lets a container or a framework hide connection pooling and transaction management issues from the application code.
48+
49+
When you use Spring Data R2DBC, you can create a `ConnectionFactory` using your R2DBC driver.
50+
`ConnectionFactory` implementations can either return the same connection, different connections or provide connection pooling.
51+
`DatabaseClient` uses `ConnectionFactory` to create and release connections per operation without affinity to a particular connection across multiple operations.
52+
53+
[[r2dbc.exception]]
54+
= Exception Translation
55+
56+
The Spring framework provides exception translation for a wide variety of database and mapping technologies.
57+
The Spring support for R2DBC extends this feature by providing implementations of the `R2dbcExceptionTranslator` interface.
58+
59+
`R2dbcExceptionTranslator` is an interface to be implemented by classes that can translate between `R2dbcException` and Spring’s own `org.springframework.dao.DataAccessException`, which is agnostic in regard to data access strategy.
60+
Implementations can be generic (for example, using SQLState codes) or proprietary (for example, using Postgres error codes) for greater precision.
61+
62+
`R2dbcExceptionSubclassTranslator` is the implementation of `R2dbcExceptionTranslator` that is used by default.
63+
It considers R2DBC's categorized exception hierarchy to translate these into Spring's consistent exception hierarchy.
64+
`R2dbcExceptionSubclassTranslator` uses `SqlStateR2dbcExceptionTranslator` as fallback if it is not able to translate an exception.
65+
66+
`SqlErrorCodeR2dbcExceptionTranslator` uses specific vendor codes using Spring JDBC's `SQLErrorCodes`.
67+
It is more precise than the SQLState implementation.
68+
The error code translations are based on codes held in a JavaBean type class called `SQLErrorCodes`.
69+
This class is created and populated by an `SQLErrorCodesFactory`, which (as the name suggests) is a factory for creating SQLErrorCodes based on the contents of a configuration file named `sql-error-codes.xml` from Spring's Data Access module.
70+
This file is populated with vendor codes and based on the `ConnectionFactoryName` taken from `ConnectionFactoryMetadata`.
71+
The codes for the actual database you are using are used.
72+
73+
The `SqlErrorCodeR2dbcExceptionTranslator` applies matching rules in the following sequence:
74+
75+
1. Any custom translation implemented by a subclass. Normally, the provided concrete `SqlErrorCodeR2dbcExceptionTranslator` is used, so this rule does not apply. It applies only if you have actually provided a subclass implementation.
76+
2. Any custom implementation of the `SQLExceptionTranslator` interface that is provided as the `customSqlExceptionTranslator` property of the `SQLErrorCodes` class.
77+
3. Error code matching is applied.
78+
4. Use a fallback translator.
79+
80+
NOTE: The `SQLErrorCodesFactory` is used by default to define Error codes and custom exception translations. They are looked up in a file named `sql-error-codes.xml` from the classpath, and the matching `SQLErrorCodes` instance is located based on the database name from the database metadata of the database in use. `SQLErrorCodesFactory` requires Spring JDBC.
81+
82+
You can extend `SqlErrorCodeR2dbcExceptionTranslator`, as the following example shows:
83+
84+
[source,java]
85+
----
86+
public class CustomSqlErrorCodeR2dbcExceptionTranslator extends SqlErrorCodeR2dbcExceptionTranslator {
87+
88+
protected DataAccessException customTranslate(String task, String sql, R2dbcException r2dbcex) {
89+
if (sqlex.getErrorCode() == -12345) {
90+
return new DeadlockLoserDataAccessException(task, r2dbcex);
91+
}
92+
return null;
93+
}
94+
}
95+
----
96+
97+
In the preceding example, the specific error code (`-12345`) is translated, while other errors are left to be translated by the default translator implementation.
98+
To use this custom translator, you must configure `DatabaseClient` through the builder method `exceptionTranslator`, and you must use this `DatabaseClient` for all of the data access processing where this translator is needed.
99+
The following example shows how you can use this custom translator:
100+
101+
[source,java]
102+
----
103+
ConnectionFactory connectionFactory = …;
104+
105+
CustomSqlErrorCodeR2dbcExceptionTranslator exceptionTranslator = new CustomSqlErrorCodeR2dbcExceptionTranslator();
106+
107+
DatabaseClient client = DatabaseClient.builder()
108+
.connectionFactory(connectionFactory)
109+
.exceptionTranslator(exceptionTranslator)
110+
.build();
111+
----

0 commit comments

Comments
 (0)