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.
DatabaseClient
encapsulates resource handling (such as opening and closing connections) so that your application code can run SQL queries or call higher-level functionality (such as inserting or selecting data).
Note
|
DatabaseClient is a recently developed application component that provides a minimal set of convenience methods that is likely to be extended through time.
|
Note
|
Once configured, DatabaseClient is thread-safe and can be reused across multiple instances.
|
Another central feature of DatabaseClient
is the translation of exceptions thrown by R2DBC drivers into Spring’s portable Data Access Exception hierarchy. See “Exception Translation” for more information.
The next section contains an example of how to work with the DatabaseClient
in the context of the Spring container.
The simplest way to create a DatabaseClient
object is through a static factory method, as follows:
DatabaseClient.create(ConnectionFactory connectionFactory)
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:
-
….exceptionTranslator(…)
: Supply a specificR2dbcExceptionTranslator
to customize how R2DBC exceptions are translated into Spring’s portable Data Access Exception hierarchy. See “Exception Translation” for more information. -
….dataAccessStrategy(…)
: Set the strategy how SQL queries are generated and how objects are mapped.
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:
DatabaseClient client1 = DatabaseClient.builder()
.exceptionTranslator(exceptionTranslatorA).build();
DatabaseClient client2 = client1.mutate()
.exceptionTranslator(exceptionTranslatorB).build();
Spring Data R2DBC obtains a connection to the database through a ConnectionFactory
.
A ConnectionFactory
is part of the R2DBC specification and is a generalized connection factory.
It lets a container or a framework hide connection pooling and transaction management issues from the application code.
When you use Spring Data R2DBC, you can create a ConnectionFactory
by using your R2DBC driver.
ConnectionFactory
implementations can either return the same connection or different connections or provide connection pooling.
DatabaseClient
uses ConnectionFactory
to create and release connections for each operation without affinity to a particular connection across multiple operations.
Assuming you use H2 as a database, a typical programmatic setup looks something like the following listing:
H2ConnectionConfiguration config = … (1)
ConnectionFactory factory = new H2ConnectionFactory(config); (2)
DatabaseClient client = DatabaseClient.create(factory); (3)
-
Prepare the database specific configuration (host, port, credentials etc.)
-
Create a connection factory using that configuration.
-
Create a
DatabaseClient
to use that connection factory.
The Spring framework provides exception translation for a wide variety of database and mapping technologies.
The Spring support for R2DBC extends this feature by providing implementations of the R2dbcExceptionTranslator
interface.
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.
Implementations can be generic (for example, using SQLState codes) or proprietary (for example, using Postgres error codes) for greater precision.
R2dbcExceptionSubclassTranslator
is the implementation of R2dbcExceptionTranslator
that is used by default.
It considers R2DBC’s categorized exception hierarchy to translate these into Spring’s consistent exception hierarchy.
R2dbcExceptionSubclassTranslator
uses SqlStateR2dbcExceptionTranslator
as its fallback if it is not able to translate an exception.
SqlErrorCodeR2dbcExceptionTranslator
uses specific vendor codes by using Spring JDBC’s SQLErrorCodes
.
It is more precise than the SQLState
implementation.
The error code translations are based on codes held in a JavaBean type class called SQLErrorCodes
.
Instances of this class are 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.
This file is populated with vendor codes and based on the ConnectionFactoryName
taken from ConnectionFactoryMetadata
.
The codes for the actual database you are using are used.
The SqlErrorCodeR2dbcExceptionTranslator
applies matching rules in the following sequence:
-
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. -
Any custom implementation of the
SQLExceptionTranslator
interface that is provided as thecustomSqlExceptionTranslator
property of theSQLErrorCodes
class. -
Error code matching is applied.
-
Use a fallback translator.
Note
|
By default, the SQLErrorCodesFactory is used to define error codes and custom exception translations.
They are looked up from a file named sql-error-codes.xml (which must be on 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.
|
You can extend SqlErrorCodeR2dbcExceptionTranslator
, as the following example shows:
public class CustomSqlErrorCodeR2dbcExceptionTranslator extends SqlErrorCodeR2dbcExceptionTranslator {
protected DataAccessException customTranslate(String task, String sql, R2dbcException r2dbcex) {
if (sqlex.getErrorCode() == -12345) {
return new DeadlockLoserDataAccessException(task, r2dbcex);
}
return null;
}
}
In the preceding example, the specific error code (-12345
) is translated, while other errors are left to be translated by the default translator implementation.
To use this custom translator, you must configure DatabaseClient
through the exceptionTranslator
builder method, and you must use this DatabaseClient
for all of the data access processing where this translator is needed.
The following example shows how you can use this custom translator:
ConnectionFactory connectionFactory = …;
CustomSqlErrorCodeR2dbcExceptionTranslator exceptionTranslator =
new CustomSqlErrorCodeR2dbcExceptionTranslator();
DatabaseClient client = DatabaseClient.builder()
.connectionFactory(connectionFactory)
.exceptionTranslator(exceptionTranslator)
.build();