Skip to content

Latest commit

 

History

History
111 lines (79 loc) · 6.7 KB

r2dbc-databaseclient.adoc

File metadata and controls

111 lines (79 loc) · 6.7 KB

Introduction to DatabaseClient

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 your application code can make use of executing SQL queries or calling higher-level functionality such as inserting or selecting data.

Note
DatabaseClient is a young application component providing 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 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.

Creating DatabaseClient

The simplest way to create a DatabaseClient is through a static factory method:

DatabaseClient.create(ConnectionFactory connectionFactory)

The above method creates a DatabaseClient with default settings.

You can also use DatabaseClient.builder() with further options to customize the client:

  • exceptionTranslator: Supply a specific R2dbcExceptionTranslator to customize how R2DBC exceptions are translated into Spring’s portable Data Access Exception hierarchy. See “Exception Translation” for more information.

  • dataAccessStrategy: 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();

Controlling Database Connections

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 using your R2DBC driver. ConnectionFactory implementations can either return the same connection, different connections or provide connection pooling. DatabaseClient uses ConnectionFactory to create and release connections per operation without affinity to a particular connection across multiple operations.

Exception Translation

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 fallback if it is not able to translate an exception.

SqlErrorCodeR2dbcExceptionTranslator uses specific vendor codes 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. 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. 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:

  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.

  2. Any custom implementation of the SQLExceptionTranslator interface that is provided as the customSqlExceptionTranslator property of the SQLErrorCodes class.

  3. Error code matching is applied.

  4. Use a fallback translator.

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.

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 builder method exceptionTranslator, 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();