Skip to content

Latest commit

 

History

History
144 lines (107 loc) · 7.29 KB

r2dbc-databaseclient.adoc

File metadata and controls

144 lines (107 loc) · 7.29 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 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.

Creating a DatabaseClient Object

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 specific R2dbcExceptionTranslator 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();

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 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)
  1. Prepare the database specific configuration (host, port, credentials etc.)

  2. Create a connection factory using that configuration.

  3. Create a DatabaseClient to use that connection factory.

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 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:

  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
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();