Una introducción al acceso a bases de datos relacionales reactivas con Spring y R2DBC

No hace mucho tiempo, se lanzó una variante reactiva del controlador JDBC, conocida como R2DBC. Permite que los datos se transmitan de forma asincrónica a cualquier punto final que se haya suscrito a él. Utilizando un controlador reactivo como R2DBC junto con Spring, WebFlux le permite escribir una aplicación completa que maneja la recepción y el envío de datos de forma asincrónica.

En esta publicación, nos centraremos en la base de datos, desde la conexión a la base de datos y luego finalmente guardar y recuperar datos. Para hacer esto, usaremos Spring Data. Al igual que con todos los módulos de Spring Data, nos proporciona una configuración lista para usar. Esto disminuye la cantidad de código repetitivo que necesitamos escribir para configurar nuestra aplicación. Además de eso, proporciona una capa sobre el controlador de la base de datos que hace que las tareas simples sean más fáciles y las más difíciles un poco menos dolorosas.

Para el contenido de esta publicación, estoy haciendo uso de una base de datos de Postgres. En el momento de redactar este artículo, solo Postgres, H2 y Microsoft SQL Server tienen sus propias implementaciones de controladores R2DBC.

Anteriormente escribí dos publicaciones sobre bibliotecas reactivas de Spring Data, una sobre Mongo y otra sobre Cassandra. Es posible que haya notado que ninguna de estas bases de datos son bases de datos RDBMS. Ahora hay otros controladores reactivos disponibles desde hace mucho tiempo (escribí la publicación de Mongo hace casi 2 años) pero en el momento de escribir un controlador reactivo para una base de datos RDBMS todavía es algo bastante nuevo. Esta publicación seguirá un formato similar a los.

Además, también escribí una publicación sobre el uso de Spring WebFlux que mencioné en la introducción. No dude en echarle un vistazo si está interesado en producir una aplicación web completamente reactiva.

Dependencias

Hay algunas cosas que señalar aquí.

Cuanto más uses Spring Boot, más te acostumbrarás a importar una sola spring-boot-starterdependencia para lo que quieres hacer. Por ejemplo, esperaba que hubiera habido una spring-boot-starter-r2dbcdependencia, pero desafortunadamente, no la hay. Todavía.

En pocas palabras, esta biblioteca está en el lado más nuevo y, en el momento de escribir este artículo, no tiene su propio módulo Spring Boot que contenga las dependencias que necesita junto con una configuración más rápida a través de la configuración automática. Estoy seguro de que estas cosas llegarán en algún momento y facilitarán aún más la configuración de un controlador R2DBC.

Por ahora, necesitaremos completar algunas dependencias adicionales manualmente.

Además, las bibliotecas R2DBC solo tienen versiones de Milestone (más pruebas de que son nuevas), por lo que debemos asegurarnos de incorporar el repositorio de Spring Milestone. Probablemente necesite actualizar esta publicación en el futuro cuando tenga una versión de lanzamiento.

Conectando a la base de datos

Gracias a que Spring Data hizo gran parte del trabajo por nosotros, el único Bean que debe crearse manualmente es el ConnectionFactoryque contiene los detalles de conexión de la base de datos:

Lo primero que hay que notar aquí es la extensión de AbstractR2dbcConfiguration. Esta clase contiene una gran cantidad de Beans que ya no necesitamos crear manualmente. La implementación connectionFactoryes el único requisito de la clase, ya que se requiere para crear el DatabaseClientBean. Este tipo de estructura es típica de los módulos Spring Data, por lo que resulta bastante familiar cuando se prueba uno diferente. Además, esperaría que esta configuración manual se elimine una vez que la configuración automática esté disponible y se maneje únicamente a través de application.properties.

He incluido la portpropiedad aquí, pero si no ha jugado con su configuración de Postgres, puede confiar en el valor predeterminado de 5432.

Las cuatro propiedades: host, database, usernamey passworddefinidos por el PostgresqlConnectionFactoryson el mínimo para que funcione. Menos y experimentará excepciones durante el inicio.

Con esta configuración, Spring puede conectarse a una instancia de Postgres en ejecución.

La última pieza de información digna de mención de este ejemplo es el uso de @EnableR2dbcRepositories. Esta anotación le indica a Spring que busque cualquier interfaz de repositorio que extienda la Repositoryinterfaz de Spring . Se utiliza como interfaz base para instrumentar los repositorios de Spring Data. Veremos esto un poco más de cerca en la siguiente sección. La información principal que debe extraerse de aquí es que debe usar la @EnableR2dbcRepositoriesanotación para aprovechar al máximo las capacidades de Spring Data.

Creando un repositorio de datos de Spring

Como se mencionó anteriormente, en esta sección veremos cómo agregar un Repositorio de datos de Spring. Estos repositorios son una buena característica de Spring Data, lo que significa que no necesita escribir una carga de código adicional para simplemente escribir una consulta.

Desafortunadamente, al menos por ahora, Spring R2DBC no puede inferir consultas de la misma manera que otros módulos de Spring Data lo hacen actualmente (estoy seguro de que esto se agregará en algún momento). Esto significa que necesitará usar la @Queryanotación y escribir el SQL a mano. Vamos a ver:

Esta interfaz se extiende R2dbcRepository. Esto a su vez se extiende ReactiveCrudRepositoryy luego baja a Repository. ReactiveCrudRepositoryproporciona las funciones estándar de CRUD y, por lo que tengo entendido, R2dbcRepositoryno proporciona funciones adicionales y, en cambio, es una interfaz creada para una mejor denominación situacional.

R2dbcRepositorytoma dos parámetros genéricos, uno es la clase de entidad que toma como entrada y produce como salida. El segundo es el tipo de clave principal. Por lo tanto, en esta situación, la Personclase está siendo administrada por PersonRepository(tiene sentido) y el campo Clave principal dentro Persones un Int.

Los tipos de funciones devueltos en esta clase y los proporcionados por ReactiveCrudRepositoryson Fluxy Mono(no se ven aquí). Estos son los tipos de Project Reactor que Spring utiliza como tipos predeterminados de Reactive Stream. Fluxrepresenta un flujo de múltiples elementos mientras que a Monoes un único resultado.

Finally, as I mentioned before the example, each function is annotated with @Query. The syntax is quite straight forward, with the SQL being a string inside the annotation. The $1 ($2, $3, etc… for more inputs) represents the value input into the function. Once you have done this, Spring will handle the rest and pass the input(s) into their respective input parameter, gather the results and map it to the repository’s designated entity class.

A very quick look at the entity

Not going to say much here but simply show the Person class used by the PersonRepository.

Actually, there is one point to make here. id has been made nullable and provided a default value of null to allow Postgres to generate the next suitable value itself. If this is not nullable and an id value is provided, Spring will actually try to run an update instead of an insert upon saving. There are other ways around this, but I think this is good enough.

This entity will map to the people table defined below:

Seeing it all in action

Now let’s have a look at it actually doing something. Below is some code that inserts a few records and retrieves them in a few different ways:

One thing I will mention about this code. There is a very real possibility that it executes without actually inserting or reading some of the records. But, when you think about it, it makes sense. Reactive applications are meant to do things asynchronously and therefore this application has started processing the function calls in different threads. Without blocking the main thread, these asynchronous processes might never fully execute. For this reason, there are some Thread.sleep calls in this code, but I removed them from the example to keep everything tidy.

The output for running the code above would look something like the below:

[ main] onSubscribe(FluxConcatMap.ConcatMapImmediate)[ main] request(unbounded)[actor-tcp-nio-1] onNext(Person(id=35, name=Dan Newton, age=25))[actor-tcp-nio-1] onNext(Person(id=36, name=Laura So, age=23))[actor-tcp-nio-1] onComplete()[actor-tcp-nio-2] findAll - Person(id=35, name=Dan Newton, age=25)[actor-tcp-nio-2] findAll - Person(id=36, name=Laura So, age=23)[actor-tcp-nio-4] findAllByName - Person(id=36, name=Laura So, age=23)[actor-tcp-nio-5] findAllByAge - Person(id=35, name=Dan Newton, age=25)

A few things to take away here:

  • onSubscribe and request occur on the main thread where the Flux was called from. Only saveAll outputs this since it has included the log function. Adding this to the other calls would have lead to the same result of logging to the main thread.
  • The execution contained within the subscribe function and the internal steps of the Flux are run on separate threads.

This is not anywhere close to a real representation of how you would use Reactive Streams in an actual application but hopefully demonstrates how to use them and gives a bit of insight into how they execute.

Conclusion

In conclusion, Reactive Streams have come to some RDBMS databases thanks to the R2DBC driver and Spring Data that builds a layer on top to make everything a bit tidier. By using Spring Data R2DBC we are able to create a connection to a database and start querying it without the need of too much code.

Although Spring is already doing a lot for us, it could be doing more. Currently, it does not have Spring Boot auto-configuration support. Which is a bit annoying. But, I am sure that someone will get around to doing it soon and make everything even better than it already is.

The code used in this post can be found on my GitHub.

If you found this post helpful, you can follow me on Twitter at @LankyDanDev to keep up with my new posts.

View all posts by Dan Newton

Originally published at lankydanblog.com on February 16, 2019.