Using the asynchronous SQL client
Finally, back… This post is the fifth post of the introduction to vert.x blog series, after a not-that-small break. In this post we are going to see how we can use JDBC in a vert.x application, and this, using the asynchronous API provided by the vertx-jdbc-client.
Previously in the introduction to vert.x series
As it was quite some time since the last post, let’s start by refreshing our mind about the four previous posts:
- The first post has described how to build a vert.x application with Maven and execute unit tests.
- The second post has described how this application can become configurable.
- The third post has introduced vertx-web, and a small collection management application has been developed. This application offers a REST API used by a HTML/JavaScript frontend.
- The previous post has presented how you can run integration tests to ensure the behavior of your application.
In this post, back to code. The current application uses an in-memory map to store the products. It’s time to use a database. In this post we are going to use HSQL, but you can use any database providing a JDBC driver. Interactions with the database will be asynchronous and made using the vertx-jdbc-client.
The code of this post are available on this Github project, in the post-5 branch branch.
Asynchronous?
One of the vert.x characteristics is being asynchronous. With an asynchronous API, you don’t wait for a result, but you are notified when this result is ready. Just to illustrate this, let’s take a very simple example.
Let’s imagine an add
method. Traditionally, you would use it like this: int r = add(1, 1)
. This is a synchronous API as you are waiting for the result. An asynchronous version of this API would be: add(1, 1, r -> { /* do something with the result */ })
. In this version, you pass a Handler
called when the result has been computed. The method does not return anything, and could be implemented as:
Just to avoid misconceptions, asynchronous API are not about threads. As we can see in the add
example, there are no threads involved.
JDBC yes, but asynchronous
So, now that we have seen some basics about asynchronous API, let’s have a look to the vertx-jdbc-client. This component lets us interact with a database through a JDBC driver. These interactions are asynchronous, so when you were doing:
it will be:
This model is more efficient as it avoids waiting for the result. You are notified when the result is available.
Let’s now modify our application to use a database to store our products.
Some maven dependencies
The first things we need to do it to declare two new Maven dependencies in our pom.xml
file:
The first dependency provides the vertx-jdbc-client, while the second one provide the HSQL JDBC driver. If you want to use another database, change this dependency. You will also need to change the JDBC url and JDBC driver class name later.
Initializing the JDBC client
Now that we have added these dependencies, it’s time to create our JDBC client:
In the MyFirstVerticle
class, declare a new field JDBCClient jdbc;
, and add the following line at the beginning of the start
method:
This creates an instance of JDBC client, configured with the configuration provided to the verticle. To work correctly this configuration needs to provide:
- url - the JDBC url such as
jdbc:hsqldb:mem:db?shutdown=true
- driver_class - the JDBC driver class such as
org.hsqldb.jdbcDriver
Ok, we have the client, we need a connection to the database. This is achieved using the jdbc.getConnection
that take a Handler<AsyncResult<SQLConnection>>
as parameter. Let’s have a deeper look to this type. It’s a Handler
, so it is called when the result is ready. This result is an instance of AsyncResult<SQLConnection>
. AsyncResult
is a structure provided by vert.x that lets us know if the operation was completed successfully or failed. In case of success, it provides the result, here an instance of SQLConnection
.
When you receive an instance of AsyncResult
, your code generally looks like:
So, let’s go back to our SQLConnection
. We need to retrieve it, and then start the rest of the application. This changes how we start the application, as it will become asynchronous. So, if we divide our startup sequence into small chunks it would be something like:
with:
startBackend
- retrieves aSQLConnection
and then calls the next stepcreateSomeData
- initializes the database and inserts some data. When done, it calls the next stepstartWebApp
- starts our web applicationcompleteStartup
- finalizes our start sequence
fut
is the completion future passed by vert.x that let us report when we are started, or if an issue has been encountered while starting.
Let’s have a look to startBackend
:
This method retrieves a SQLConnection
, check whether this operation succeeded. If so, it calls the next step. In case of failure, it reports it.
The other methods follow the same pattern: 1) check if the last operation has succeeded, 2) do the task, 3) call the next step.
A bit of SQL…
Our client is ready, let’s now write some SQL statements. Let’s start by the createSomeData
method that is part of the startup sequence:
This method checks that the SQLConnection
is available and then start executing some SQL statements. First, it creates the tables if there are not there yet. As you can see, the method called is structured as follows:
The handler receives an AsyncResult<Void>
, i.e. a notification of the completion without an actual result.
Don’t forget to close the SQL connection when you are done. The connection will be given back to the connection pool and be reused.
In the code of this handler, we check whether or not the statement has been executed correctly, and if so we check to see if the table already contains some data, if not, it inserts data using the insert
method:
This method uses the updateWithParams
method with an INSERT statement, and pass values. This approach avoids SQL injection. Once the the statement has been executed, we creates a new Whisky
object with the created (auto-generated) id.
Some REST with a pinch of SQL
The method described above is part of our start sequence. But what about the method invoked by our REST API. Let’s have a look to the getAll
method. This method is called by the web front-end to retrieve all stored products:
This method gets a SQLConnection
, and then issue a query. Once the result has been retrieved it writes the HTTP response as before. The getOne
, deleteOne
, updateOne
and addOne
methods follow the same pattern. Notice that the connection can be closed after the response has been written.
Let’s have a look to the result provided to the handler passed to the query
method. It gets a ResultSet
, which contains the query result. Each row is a JsonObject
, so if your data object has a constructor taking a JsonObject
as unique argument, creating there objects is straightforward.
Test, test, and test again
We need to slightly update our tests to configure the JDBCClient
. In the MyFirstVertilceTest
class, change the DeploymentOption
object created in the setUp
method to be:
In addition to the http.port
, we also put the JDBC url and the class of the JDBC driver. We use an in-memory database for tests.
The same modification needs to be done in the src/test/resources/my-it-config.json
file:
The src/main/conf/my-application-conf.json
file also needs to be updated, not for the tests, but to run the application:
The JDBC url is a bit different in this last file, as we store the database on the file system.
Show time!
Let’s now build our application:
As we didn’t change the API (neither the public java one nor the REST), test should run smoothly.
Then launch the application with:
java -jar target/my-first-app-1.0-SNAPSHOT-fat.jar -conf src/main/conf/my-application-conf.json
Open your browser to http://localhost:8082/assets/index.html
, and you should see the application using the database. This time the products are stored in a database persisted on the file system. So, if we stop and restart the application, the data is restored.
Conclusion
In this post, we saw how you can use JDBC database with vert.x, and thus without too much burden. You may have been surprised by the asynchronous development model, but once you start using it, it’s hard to come back.
In the next post, we see how the same application can use MongoDB instead of HSQL.
Stay tuned, and happy coding!