© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2021
R. HedgpethR2DBC Revealedhttps://doi.org/10.1007/978-1-4842-6989-3_12

12. Managing Connections

Robert Hedgpeth1  
(1)
Chicago, IL, USA
 

In the previous chapter, you were introduced to the process of creating a new Java project and, utilizing the capabilities of Apache Maven, adding the MariaDB R2DBC driver as a dependency to the project. Now that you’ve successfully created an application capable of taking advantage of an R2DBC implementation, it’s time to dive into the capabilities of the specification.

In this chapter, we’re going to expand on that project to examine the Connection object implementations available in the driver. Before continuing, if you haven’t done so yet, I highly recommend reading Chapter 4, which dives into much greater detail on the R2DBC specification connection hierarchy and workflow.

Configuration

For the purposes of this chapter, I’m going to be highlighting what I would consider conventional connection parameters for examples of how to establish a connection to MariaDB. More specifically, I’ll be providing examples that target a local database instance.

Note

To run a program locally means to run it on the machine you are sitting at (or to run it on the same machine it is running on itself), as opposed to causing it to run on some remote machine.

Beyond this sample, if you’d like more information on the configuration options available for MariaDB, or any other DBMS, I highly suggest checking out the official MariaDB documentation.

Database Connectivity

In the last chapter, we walked through the process of getting a database instance up and running on your machine. This was done so that you have access to a MariaDB database instance that you can use to test the capabilities of the MariaDB R2DBC driver. I specifically provided guidance on setting up a local database instance as it requires minimal configuration information in order to establish a connection.

For instance, in Table 12-1, I’ve indicated the information necessary to connect to a local instance of MariaDB.
Table 12-1

Sample connection parameters

Property

Value

Description

Hostname

127.0.0.1

The IP address, or domain, of your MariaDB Server. The default IP address for a local database instance is 127.0.0.1.

Port

3306

Port numbers are used as a way to identify how specific processes are to be forwarded. The default port number for MariaDB is 3306.

Username

app_user

The username needed to connect to a MariaDB database instance.

Password

Password123!

The password needed to connect to a MariaDB database instance.

Note

In Chapter 11, I provided SQL statements for creating a new user, app_user, with the password Password123! for a MariaDB database instance. While it’s certainly possible to use any credentials you want, for the sake of simplicity and consistency, I will be using app_user for all connection code settings in this book.

Driver Implementation and Extensibility

As you can see in Table 12-1, the information is minimal. This helps you on two fronts. One, it provides you with the simplest approach to connecting to a MariaDB database. And, two, the requirement of a host, port number, username, and password is something that is common among all other relational databases and, thus, is an example that can be applied to other database vendors and their corresponding R2DBC driver implementations.

Running Reactive Code

Before continuing, it’s important to note that many of the examples going forward in this book will be using non-blocking method execution, such as the subscribe method .

Because we will be using a simple console application, which utilizes a single class and main method, it’s possible, due to the nature of asynchronous event-driven communication, that the application may complete execution before publishers send information to their subscribers.

As a possible workaround, and an approach I’ll be utilizing throughout the next several chapters, code can be added to keep the application running by blocking the current thread.

First, modify the main method to allow for throwing an InterruptedException . Doing so will allow you to add code to join the current thread, which will prevent the main method from exiting (Listing 12-1).
public static void main(String[] arg     s) throws InterruptedException {
    // This is where we’ll be executing R2DBC sample code
    Thread.currentThread().join();
}
Listing 12-1

Keep the current thread threading to allow time for publishers and subscribers to complete processing

Caution

The code block from Listing 12-1 is merely a workaround for demonstration purposes. It is unlikely that you’ll want to use such code within more practical, or “real-world,” solutions.

Establishing Connections

In Chapter 4, you learned that the creation of R2DBC Connection object implementations is managed through ConnectionFactory object implementations of a driver.

Before proceeding, it’s important to note that the MariaDB R2DBC driver implements the full hierarchy of connection interfaces and provides a simple naming convention for each of the objects by preceding the names of each object with “Mariadb.” Thus, the driver implements the Connection and ConnectionFactory interfaces as MariadbConnection and MariadbConnectionFactory, respectively.

Note

This type of naming convention is common for other R2DBC driver implementations.

Obtaining MariadbConnectionFactory

Above all else, the MariadbConnectionFactory object is used to manage MariadbConnection objects. Of course, in order to be able to manage MariadbConnection objects, they need to exist, and before that you’ll need to get your hands on a ConnectionFactory implementation. The MariaDB driver provides three approaches for obtaining MariadbConnectionFactory.

Creating a New Connection Factory

One approach is to use the MariadbConnectionConfiguration object, which allows you to provide a variety of information, such as the host address, port number, username, and password, to identify a target MariaDB server instance. The MariadbConnectionFactory object can then be constructed using a MariadbConnectionConfiguration instance (Listing 12-2).
MariadbConnectionConfiguration connectionConfiguration = MariadbConnectionConfiguration.builder()
                            .host("127.0.0.1")
                            .port(3306)
                            .username("app_user")
                            .password("Password123!")
                            .build();
MariadbConnectionFactory connectionFactory = new MariadbConnectionFactory(connectionConfiguration);
Listing 12-2

Creating a new MariadbConnectionFactory object using MariadbConnectionConfiguration

MariadbConnectionConfiguration

The MariadbConnectionConfiguration class is specific to the MariaDB driver in that it doesn’t derive from or implement an entity that exists within the R2DBC SPI.

However, the [DriverName]ConnectionConfiguration object is common among every driver implementation to date, but not only because of the naming convention. Connection configuration objects function to manage both standard and vendor-specific connectivity options.

Once a ConnectionConfiguration object is created, there are a variety of get methods you can use to read its current configuration settings (Listing 12-3).
// Where connectionConfiguration is an existing MariadbConnectionConfiguration object
String host = connectionConfiguration.getHost();
int post = connectionConfiguration.getPort();
String username = connectionConfiguration.getUsername();
int prepareCacheSize = connectionConfiguration.getPrepareCacheSize();
Listing 12-3

Example MariadbConnectionConfiguration getter method usages

Connection configuration objects also provide a sort of bridge, through ConnectionFactoryProvider implementations, to assist in the ConnectionFactory implementation discovery process using the ConnectionFactoryOptions class.

ConnectionFactory Discovery

Harkening again back to Chapter 4, remember that ConnectionFactories, a class within the R2DBC SPI, provides two ways, both using the get method, to retrieve a driver ConnectionFactory implementation.

The first approach is to use a ConnectionFactoryOptions object to specify the appropriate connection settings for the target database instance (Listing 12-4).
ConnectionFactoryOptions connectionFactoryOptions = ConnectionFactoryOptions.builder()
                        .option(ConnectionFactoryOptions.DRIVER, "mariadb")
                        .option(ConnectionFactoryOptions.PROTOCOL, "pipes")
                        .option(ConnectionFactoryOptions.HOST, "127.0.0.1")
                        .option(ConnectionFactoryOptions.PORT, 3306)
                        .option(ConnectionFactoryOptions.USER, "app_user")
                        .option(ConnectionFactoryOptions.PASSWORD, "Password123!").build();
MariadbConnectionFactory connectionFactory = (MariadbConnectionFactory)ConnectionFactories.get(connectionFactoryOptions);
Listing 12-4

Retrieving an existing MariadbConnectionFactory object using a ConnectionFactoryOptions object

Note

Remember that in Chapter 11 I provided you with the SQL commands for adding a new user, app_user, to a MariaDB database instance.

Second, you also have the option of passing an R2DBC URL, which you learned about in Chapter 4, into ConnectionFactories’ get method (Listing 12-5).
MariadbConnectionFactory connectionFactory = (MariadbConnectionFactory)ConnectionFactories.get("r2dbc:mariadb:pipes://app_user:[email protected]:3306");
Listing 12-5

Retrieving an existing MariadbConnectionFactory object using an R2DBC connection URL

Ultimately, the R2DBC URL is parsed to create a ConnectionFactoryOptions object that is then used by the ConnectionFactories class to obtain a ConnectionFactory just as it did in Listing 12-4.

Creating a Connection

Once you’ve created a MariadbConnectionFactory object , you can then use the create method to obtain a MariadbConnection object (Listing 12-6).
Mono<MariadbConnection> monoConnection = connectionFactory.create();
monoConnection.subscribe(connection -> {
     // Do something with connection
});
Listing 12-6

Creating a new database connection

Note

A Mono is a Reactive Streams Publisher object implementation, provided by the Project Reactor library, with the specific intention of streaming 0–1 elements.

Notice that the ConnectionFactory interface’s create method returns a Mono<MariadbConnection>, which is a Project Reactor implementation of the Reactive Streams API’s Publisher<T> and the R2DBC specification’s Publisher<Connection>.

And remember that due to the nature of event-based development, there’s no telling when the MariadbConnection object will be published. Because of that, it may be useful, in certain situations, to wait for the publisher to send a Connection object before proceeding.

In such a scenario, you can use the block method to wait for a MariadbConnection object before proceeding (Listing 12-7).
MariadbConnection connection = connectionFactory.create().block();
Listing 12-7

Creating and waiting on a new database connection

Validating and Closing Connections

After obtaining a MariadbConnection object , you can use the validate method to, yep, you guessed it, check whether the connection is still valid.

The validate method returns Publisher<Boolean>, which can be used by the Project Reactor library, using Mono.from, to create a Mono<Boolean> publisher. Then, as indicated in Listing 12-8, upon subscribing to monoValidated, the Boolean value, within the validated variable, can be used.
Publisher<Boolean> validatePublisher = connection.validate(ValidationDepth.LOCAL);
            Mono<Boolean> monoValidated = Mono.from(validatePublisher);
            monoValidated.subscribe(validated -> {
                if (validated) {
                    System.out.println("Connection is valid");
                }
                else {
                    System.out.println("Connection is not valid");
                }
            });
Listing 12-8

Validating a connection

Note

This example shows the usage of ValidationDepth.LOCAL, because of the local connection. For more information on ValidationDepth options, be sure to check out Chapter 4.

Likewise, the close method can be used to release the connection and its associated resources. In Listing 12-9, you can see how it’s possible to subscribe to the close method.
Publisher<Void> closePublisher = connection.close();
Mono<Void> monoClose = Mono.from(closePublisher);
monoClose.subscribe();
Listing 12-9

Closing a connection

Putting It All Together

In Listing 12-10, I’ve accumulated all of the code snippets mentioned in this chapter into a single, runnable sample. The purpose of this sample is to demonstrate how to first establish a connection to the MariaDB database instance and then validate it. Then the connection will be closed and once more checked for validity.
package com.example;
import org.mariadb.r2dbc.MariadbConnectionConfiguration;
import org.mariadb.r2dbc.MariadbConnectionFactory;
import org.mariadb.r2dbc.api.MariadbConnection;
import org.reactivestreams.Publisher;
import io.r2dbc.spi.ConnectionFactories;
import io.r2dbc.spi.ConnectionFactoryOptions;
import io.r2dbc.spi.ValidationDepth;
import reactor.core.publisher.Mono;
public class App
{
    public static void main( String[] args )
    {
        // Initialize Connection
        MariadbConnection connection = obtainConnection();
        // Validate Connection
        validateConnection(connection);
        // Close Connection
        closeConnection(connection);
        // Validate Connection
        validateConnection(connection);
    }
    public static MariadbConnection obtainConnection() {
        try {
            MariadbConnectionFactory connectionFactory;
            // Create a new Connection Factory using MariadbConnectionConfiguration
            connectionFactory = createConnectionFactory();
            // Discover Connection Factory using ConnectionFactoryOptions
            //connectionFactory = discoverConnectionFactoryWithConfiguration();
            // Discover Connection Factory using Url
            //connectionFactory = discoverConnectionFactoryWithUrl();
            // Create a MariadbConnection
            return connectionFactory.create().block();
        }
        catch (java.lang.IllegalArgumentException e) {
           printException("Issue encountered while attempting to obtain a connection", e);
           throw e;
        }
    }
    public static MariadbConnectionFactory createConnectionFactory() {
        try{
            // Configure the Connection
            MariadbConnectionConfiguration connectionConfiguration = MariadbConnectionConfiguration.builder()
                              .host("127.0.0.1")
                              .port(3306)
                              .username("app_user")
                              .password("Password123!")
                              .build();
            // Instantiate a Connection Factory
            MariadbConnectionFactory connectionFactory = new MariadbConnectionFactory(connectionConfiguration);
            print("Created new MariadbConnectionFactory");
            return connectionFactory;
        }
        catch(Exception e) {
            printException("Unable to create a new MariadbConnectionFactory", e);
            throw e;
        }
    }
    public static MariadbConnectionFactory discoverConnectionFactoryWithConfiguration() {
        try{
            ConnectionFactoryOptions connectionFactoryOptions = ConnectionFactoryOptions.builder()
                 .option(ConnectionFactoryOptions.DRIVER, "mariadb")
                 .option(ConnectionFactoryOptions.PROTOCOL, "pipes")
                 .option(ConnectionFactoryOptions.HOST, "127.0.0.1")
                 .option(ConnectionFactoryOptions.PORT, 3306)
                 .option(ConnectionFactoryOptions.USER, "app_user")
                 .option(ConnectionFactoryOptions.PASSWORD, "Password123!")
                 .option(ConnectionFactoryOptions.DATABASE, "todo")
                 .build();
            MariadbConnectionFactory connectionFactory = (MariadbConnectionFactory)ConnectionFactories.get(connectionFactoryOptions);
            return connectionFactory;
        }
        catch(Exception e) {
            printException("Unable to discover MariadbConnectionFactory using ConnectionFactoryOptions", e);
            throw e;
        }
    }
    public static MariadbConnectionFactory discoverConnectionFactoryWithUrl() {
        try {
            MariadbConnectionFactory connectionFactory = (MariadbConnectionFactory)ConnectionFactories.get("r2dbc:mariadb:pipes://app_user:[email protected]:3306/todo");
            return connectionFactory;
        }
        catch (Exception e) {
            printException("Unable to discover MariadbConnectionFactory using Url", e);
            throw e;
        }
    }
    public static void validateConnection(MariadbConnection connection) {
        try {
            Publisher<Boolean> validatePublisher = connection.validate(ValidationDepth.LOCAL);
            Mono<Boolean> monoValidated = Mono.from(validatePublisher);
            monoValidated.subscribe(validated -> {
                if (validated) {
                    print("Connection is valid");
                }
                else {
                    print("Connection is not valid");
                }
            });
        }
        catch (Exception e) {
           printException("Issue encountered while attempting to verify a connection", e);
        }
    }
    public static void closeConnection(MariadbConnection connection) {
        try {
            Publisher<Void> closePublisher = connection.close();
            Mono<Void> monoClose = Mono.from(closePublisher);
            monoClose.subscribe();
        }
        catch (java.lang.IllegalArgumentException e) {
           printException("Issue encountered while attempting to verify a connection", e);
        }
    }
    public static void printException(String description, Exception e) {
        print(description);
        e.printStackTrace();
    }
    public static void print(String val) {
        System.out.println(val);
    }
}
Listing 12-10

The complete connection sample

Successfully running the sample code in Listing 12-10 should yield a result similar to that indicated in Listing 12-11 .
Connection is valid
Connection is not valid
Listing 12-11

The resulting output from executing the code in Listing 12-10

The code in Listing 12-10, while very simple, provides foundational connection functionality that I’ll continue to expand on in upcoming chapters.

Metadata

Lastly, it’s possible to inspect information, or metadata, about the ConnectionFactory and Connection object implementations (Listing 12-12, Listing 12-13).

The MariadbConnectionFactoryMetadata object exposes a single method, getName, that returns the name of the product that R2DBC is connected to.
ConnectionFactoryMetadata metadata = connectionFactory.getMetadata();
String name = metadata.getName();
Listing 12-12

Using MariadbConnectionFactoryMetadata

You also have the ability, as indicated in Listing 12-13, to retrieve metadata about an established connection using the MariadbConnection object. The MariadbConnectionMetadata object implements the ConnectionMetadata interface, as well as its required methods. MariadbConnectionMetadata also exposes several additional methods specific to the MariaDB driver implementation of R2DBC.
MariadbConnectionMetadata metadata = connection.getMetadata();
// Methods required by the ConnectionMetadata interface
String productName = metadata.getDatabaseProductName();
String databaseVersion = metadata.getDatabaseVersion();
// Extension methods added to MariadbConnectionMetadata
boolean isMariaDBServer = metadata.isMariaDBServer();
int majorVersion = metadata.getMajorVersion();
int minorVersion = metadata.getMinorVersion();
int patchVersion = metadata.getPatchVersion();
Listing 12-13

Using MariadbConnectionMetadata

Show Me the Code

You can find a complete, fully compilable sample application in the GitHub repository dedicated to this book. If you haven’t done so already, simply navigate to https://github.com/apress/r2dbc-revealed to either git clone or directly download the contents of the repository. From there you can find a sample application dedicated to this chapter in the ch12 folder.

Summary

There’s no doubt that the ability to connect to a data source is one of the, if not the most, important capabilities of a database driver. This chapter is crucial for taking the theoretical information you learned about in Chapter 4 and seeing it in action through the use of a driver implementation.

In this chapter, you gained an understanding of how to use the MariaDB R2DBC driver to create and manage database connections. The foundation provided in this chapter will be incredibly useful for upcoming chapters where we’ll dive into concepts like statement execution, transaction management, connection pooling, and many more.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.137.243