Eine Einführung in den reaktiven relationalen Datenbankzugriff mit Spring und R2DBC

Vor nicht allzu langer Zeit wurde eine reaktive Variante des JDBC-Treibers veröffentlicht, die als R2DBC bekannt ist. Damit können Daten asynchron zu allen Endpunkten gestreamt werden, die sie abonniert haben. Mit einem reaktiven Treiber wie R2DBC zusammen mit Spring können Sie mit WebFlux eine vollständige Anwendung schreiben, die das asynchrone Empfangen und Senden von Daten übernimmt.

In diesem Beitrag konzentrieren wir uns auf die Datenbank, indem wir eine Verbindung zur Datenbank herstellen und schließlich Daten speichern und abrufen. Dazu verwenden wir Spring Data. Wie bei allen Spring Data-Modulen bietet es uns eine sofort einsatzbereite Konfiguration. Dies verringert die Menge an Boilerplate-Code, die wir schreiben müssen, um unser Anwendungs-Setup zu erhalten. Darüber hinaus bietet es eine Ebene auf dem Datenbanktreiber, die das Ausführen einfacher Aufgaben und die schwierigeren Aufgaben etwas weniger schmerzhaft macht.

Für den Inhalt dieses Beitrags verwende ich eine Postgres-Datenbank. Zum Zeitpunkt des Schreibens verfügen nur Postgres, H2 und Microsoft SQL Server über eigene Implementierungen von R2DBC-Treibern.

Ich habe zuvor zwei Posts über reaktive Spring Data-Bibliotheken geschrieben, einen über Mongo und einen über Cassandra. Möglicherweise haben Sie bemerkt, dass keine dieser Datenbanken RDBMS-Datenbanken sind. Jetzt sind andere reaktive Treiber für eine lange Zeit verfügbar (ich habe den Mongo-Beitrag vor fast 2 Jahren geschrieben), aber zum Zeitpunkt des Schreibens ist ein reaktiver Treiber für eine RDBMS-Datenbank noch eine ziemlich neue Sache. Dieser Beitrag hat ein ähnliches Format wie diese.

Darüber hinaus habe ich einen Beitrag über die Verwendung von Spring WebFlux geschrieben, den ich in der Einleitung erwähnt habe. Schauen Sie sich das an, wenn Sie an einer vollständig reaktiven Webanwendung interessiert sind.

Abhängigkeiten

Hier sind einige Dinge zu beachten.

Je häufiger Sie Spring Boot verwenden, desto mehr werden Sie sich daran gewöhnen, eine einzelne spring-boot-starterAbhängigkeit für die coole Sache zu importieren , die Sie tun möchten. Ich habe zum Beispiel gehofft, dass es eine spring-boot-starter-r2dbcAbhängigkeit gegeben hätte, aber leider gibt es keine. Noch.

Einfach ausgedrückt, diese Bibliothek befindet sich auf der neueren Seite und verfügt zum Zeitpunkt des Schreibens nicht über ein eigenes Spring Boot-Modul, das die erforderlichen Abhängigkeiten sowie eine schnellere Einrichtung über die automatische Konfiguration enthält. Ich bin sicher, dass diese Dinge irgendwann kommen und das Einrichten eines R2DBC-Treibers noch einfacher machen werden.

Im Moment müssen wir einige zusätzliche Abhängigkeiten manuell ausfüllen.

Darüber hinaus verfügen die R2DBC-Bibliotheken nur über Milestone-Versionen (ein weiterer Beweis dafür, dass sie neu sind). Daher müssen wir sicherstellen, dass wir das Spring Milestone-Repository einbinden. Ich werde diesen Beitrag wahrscheinlich in Zukunft aktualisieren müssen, wenn er eine Release-Version erhält.

Verbindung zur Datenbank herstellen

Da Spring Data einen Großteil der Arbeit für uns erledigt, muss die einzige Bean, die manuell erstellt werden muss ConnectionFactory, die Verbindungsdetails der Datenbank enthalten:

Das erste, was hier zu bemerken ist, ist die Erweiterung von AbstractR2dbcConfiguration. Diese Klasse enthält eine Menge Beans, die wir nicht mehr manuell erstellen müssen. Die Implementierung connectionFactoryist die einzige Anforderung der Klasse, da sie zum Erstellen der DatabaseClientBean erforderlich ist . Diese Art von Struktur ist typisch für Spring Data-Module, daher ist es ziemlich vertraut, wenn Sie ein anderes ausprobieren. Darüber hinaus würde ich erwarten, dass diese manuelle Konfiguration entfernt wird, sobald die automatische Konfiguration verfügbar ist, und ausschließlich über das gesteuert wird application.properties.

Ich habe die portEigenschaft hier aufgenommen, aber wenn Sie nicht mit Ihrer Postgres-Konfiguration herumgespielt haben, können Sie sich auf den Standardwert von verlassen 5432.

Die vier Eigenschaften: host, database, usernameund passworddurch die definierte PostgresqlConnectionFactorysind das absolute Minimum , um es zu arbeiten. Wenn Sie weniger verwenden, treten beim Start Ausnahmen auf.

Mit dieser Konfiguration kann Spring eine Verbindung zu einer laufenden Postgres-Instanz herstellen.

Die letzte bemerkenswerte Information aus diesem Beispiel ist die Verwendung von @EnableR2dbcRepositories. Diese Anmerkung weist Spring an, alle Repository-Schnittstellen zu finden, die die Spring- RepositorySchnittstelle erweitern. Dies wird als Basisschnittstelle für die Instrumentierung von Spring Data-Repositorys verwendet. Wir werden uns dies im nächsten Abschnitt etwas genauer ansehen. Die wichtigste Information, die Sie hier wegnehmen sollten, ist, dass Sie die @EnableR2dbcRepositoriesAnmerkung verwenden müssen, um die Funktionen von Spring Data voll auszuschöpfen.

Erstellen eines Spring Data Repository

Wie oben erwähnt, werden wir uns in diesem Abschnitt mit dem Hinzufügen eines Spring Data Repository befassen. Diese Repositorys sind eine nette Funktion von Spring Data, was bedeutet, dass Sie nicht viel zusätzlichen Code schreiben müssen, um einfach eine Abfrage zu schreiben.

Leider kann Spring R2DBC zumindest vorerst nicht auf die gleiche Weise wie andere Spring Data-Module auf Abfragen schließen (ich bin sicher, dass dies irgendwann hinzugefügt wird). Dies bedeutet, dass Sie die @QueryAnnotation verwenden und die SQL von Hand schreiben müssen. Lass uns einen Blick darauf werfen:

Diese Schnittstelle wird erweitert R2dbcRepository. Dies erstreckt sich wiederum ReactiveCrudRepositoryund dann bis hinunter Repository. ReactiveCrudRepositorybietet die Standard-CRUD-Funktionen und bietet meines Wissens R2dbcRepositorykeine zusätzlichen Funktionen und ist stattdessen eine Schnittstelle, die für eine bessere Situationsbenennung erstellt wurde.

R2dbcRepositoryNimmt zwei generische Parameter auf, von denen einer die Entitätsklasse ist, die als Eingabe verwendet und als Ausgabe erzeugt wird. Der zweite ist der Typ des Primärschlüssels. Daher wird in dieser Situation die PersonKlasse von PersonRepository(sinnvoll) verwaltet, und das darin enthaltene Primärschlüsselfeld Personist ein Int.

Die Rückgabetypen von Funktionen in dieser Klasse und die von bereitgestellten ReactiveCrudRepositorysind Fluxund Mono(hier nicht zu sehen). Dies sind Projektreaktortypen, die Spring als Standardtypen für reaktive Streams verwendet. Fluxstellt einen Strom mehrerer Elemente dar, während a Monoein einzelnes Ergebnis ist.

Schließlich wird, wie bereits im Beispiel erwähnt, jede Funktion mit Anmerkungen versehen @Query. Die Syntax ist recht einfach, wobei SQL eine Zeichenfolge innerhalb der Annotation ist. Die $1( $2, $3, etc ... für mehr Eingängen) repräsentiert den Eingabewert in die Funktion. Sobald Sie dies getan haben, übernimmt Spring den Rest und übergibt die Eingabe (n) an den jeweiligen Eingabeparameter, sammelt die Ergebnisse und ordnet sie der vom Repository festgelegten Entitätsklasse zu.

Ein sehr kurzer Blick auf die Entität

Ich werde hier nicht viel sagen, sondern nur die PersonKlasse zeigen, die von der PersonRepository.

Eigentlich gibt es hier einen Punkt zu machen. idwurde auf null gesetzt und mit einem Standardwert von versehen null, damit Postgres den nächsten geeigneten Wert selbst generieren kann. Wenn dies nicht nullwertfähig ist und ein idWert angegeben wird, versucht Spring beim Speichern tatsächlich, ein Update anstelle einer Einfügung auszuführen. Es gibt andere Möglichkeiten, aber ich denke, das ist gut genug.

Diese Entität wird der peopleunten definierten Tabelle zugeordnet:

Alles in Aktion sehen

Schauen wir uns jetzt an, wie es tatsächlich etwas tut. Im Folgenden finden Sie einen Code, der einige Datensätze einfügt und auf verschiedene Arten abruft:

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.