Einführung in rxjava-jdbc

Einführung in rxjava-jdbc

1. Überblick

Einfach ausgedrückt ist rxjava-jdbc eine API für die Interaktion mit relationalen Datenbanken, die fließende Methodenaufrufe ermöglicht. In diesem kurzen Tutorial werden wir uns die Bibliothek ansehen und wie wir einige ihrer gemeinsamen Funktionen nutzen können.

Wenn Sie die Grundlagen von RxJava kennenlernen möchten, lesen Siethis article.

Weitere Lektüre:

Einführung in RxJava

Discover RxJava - eine Bibliothek zum Erstellen von asynchronen und ereignisbasierten Programmen.

Read more

Umgang mit Gegendruck mit RxJava

Eine Anleitung, die verschiedene Strategien zum Umgang mit Gegendruck in RxJava demonstriert

Read more

Observable Utility Operators in RxJava

Erfahren Sie, wie Sie verschiedene RxJava-Dienstprogrammoperatoren verwenden.

Read more

2. Maven-Abhängigkeit

Beginnen wir mit der Maven-Abhängigkeit, die wir zu unserenpom.xml hinzufügen müssen:


    com.github.davidmoten
    rxjava-jdbc
    0.7.11

Wir finden die neueste Version der API aufMaven Central.

3. Hauptbestandteile

The Database class is the main entry point for running all common types of database interactions. Um einDatabase-Objekt zu erstellen, können wir eine Instanz einer Implementierung derConnectionProvider-Schnittstelle an die statische Methode vonfrom()übergeben:

public static ConnectionProvider connectionProvider
  = new ConnectionProviderFromUrl(
  DB_CONNECTION, DB_USER, DB_PASSWORD);
Database db = Database.from(connectionProvider);

ConnectionProvider hat mehrere Implementierungen, die einen Blick wert sind - wieConnectionProviderFromContext,ConnectionProviderFromDataSource,ConnectionProviderFromUrl undConnectionProviderPooled.

Um grundlegende Operationen auszuführen, können wir die folgenden APIs vonDatabase verwenden:

  • select() - wird für SQL-Auswahlabfragen verwendet

  • update() - wird für DDL-Anweisungen wie Erstellen und Löschen sowie Einfügen, Aktualisieren und Löschen verwendet

4. Inbetriebnahme

Im nächsten kurzen Beispiel zeigen wir, wie wir alle grundlegenden Datenbankoperationen ausführen können:

public class BasicQueryTypesTest {

    Observable create,
      insert1,
      insert2,
      insert3,
      update,
      delete = null;

    @Test
    public void whenCreateTableAndInsertRecords_thenCorrect() {
        create = db.update(
          "CREATE TABLE IF NOT EXISTS EMPLOYEE("
          + "id int primary key, name varchar(255))")
          .count();
        insert1 = db.update(
          "INSERT INTO EMPLOYEE(id, name) VALUES(1, 'John')")
          .dependsOn(create)
          .count();
        update = db.update(
          "UPDATE EMPLOYEE SET name = 'Alan' WHERE id = 1")
          .dependsOn(create)
          .count();
        insert2 = db.update(
          "INSERT INTO EMPLOYEE(id, name) VALUES(2, 'Sarah')")
          .dependsOn(create)
          .count();
        insert3 = db.update(
          "INSERT INTO EMPLOYEE(id, name) VALUES(3, 'Mike')")
          .dependsOn(create)
          .count();
        delete = db.update(
          "DELETE FROM EMPLOYEE WHERE id = 2")
          .dependsOn(create)
          .count();
        List names = db.select(
          "select name from EMPLOYEE where id < ?")
          .parameter(3)
          .dependsOn(create)
          .dependsOn(insert1)
          .dependsOn(insert2)
          .dependsOn(insert3)
          .dependsOn(update)
          .dependsOn(delete)
          .getAs(String.class)
          .toList()
          .toBlocking()
          .single();

        assertEquals(Arrays.asList("Alan"), names);
    }
}

Ein kurzer Hinweis hier: Wir rufendependsOn() auf, um die Reihenfolge der Ausführung von Abfragen zu bestimmen.

Andernfalls schlägt der Code fehl oder führt zu unvorhersehbaren Ergebnissen, sofern nicht angegeben wird, in welcher Reihenfolge die Abfragen ausgeführt werden sollen.

5. Automap

Die Automap-Funktion ermöglicht es uns, ausgewählte Datenbankdatensätze Objekten zuzuordnen.

Schauen wir uns die beiden Möglichkeiten zur automatischen Zuordnung von Datenbankeinträgen an.

5.1. Automatische Zuordnung über eine Schnittstelle

Wir könnenautomap()Datenbankeinträge für Objekte mithilfe von kommentierten Schnittstellen erstellen. Zu diesem Zweck können wir eine mit Anmerkungen versehene Schnittstelle erstellen:

public interface Employee {

    @Column("id")
    int id();

    @Column("name")
    String name();
}

Dann können wir unseren Test durchführen:

@Test
public void whenSelectFromTableAndAutomap_thenCorrect() {
    List employees = db.select("select id, name from EMPLOYEE")
      .dependsOn(create)
      .dependsOn(insert1)
      .dependsOn(insert2)
      .autoMap(Employee.class)
      .toList()
      .toBlocking()
      .single();

    assertThat(
      employees.get(0).id()).isEqualTo(1);
    assertThat(
      employees.get(0).name()).isEqualTo("Alan");
    assertThat(
      employees.get(1).id()).isEqualTo(2);
    assertThat(
      employees.get(1).name()).isEqualTo("Sarah");
}

5.2. Automapping mit einer Klasse

Wir können Datenbankdatensätze auch mithilfe konkreter Klassen automatisch Objekten zuordnen. Mal sehen, wie die Klasse aussehen kann:

public class Manager {

    private int id;
    private String name;

    // standard constructors, getters, and setters
}

Jetzt können wir unseren Test durchführen:

@Test
public void whenSelectManagersAndAutomap_thenCorrect() {
    List managers = db.select("select id, name from MANAGER")
      .dependsOn(create)
      .dependsOn(insert1)
      .dependsOn(insert2)
      .autoMap(Manager.class)
      .toList()
      .toBlocking()
      .single();

    assertThat(
      managers.get(0).getId()).isEqualTo(1);
    assertThat(
     managers.get(0).getName()).isEqualTo("Alan");
    assertThat(
      managers.get(1).getId()).isEqualTo(2);
    assertThat(
      managers.get(1).getName()).isEqualTo("Sarah");
}

Ein paar Anmerkungen hier:

  • create,insert1 undinsert2 sind Verweise aufObservables, die durch Erstellen der TabelleManager und Einfügen von Datensätzen zurückgegeben werden

  • Die Anzahl der ausgewählten Spalten in unserer Abfrage muss mit der Anzahl der Parameter im KlassenkonstruktorManagerübereinstimmen

  • Die Spalten müssen Typen haben, die den Typen im Konstruktor automatisch zugeordnet werden können

Weitere Informationen zum automatischen Zuordnen finden Sie unterrxjava-jdbc repository auf GitHub

6. Arbeiten mit großen Objekten

Die API unterstützt die Arbeit mit großen Objekten wie CLOBs und BLOBS. In den nächsten Unterabschnitten werden wir sehen, wie wir diese Funktionalität nutzen können.

6.1. CLOBs

Mal sehen, wie wir ein CLOB einfügen und auswählen können:

@Before
public void setup() throws IOException {
    create = db.update(
      "CREATE TABLE IF NOT EXISTS " +
      "SERVERLOG (id int primary key, document CLOB)")
        .count();

    InputStream actualInputStream
      = new FileInputStream("src/test/resources/actual_clob");
    actualDocument = getStringFromInputStream(actualInputStream);

    InputStream expectedInputStream = new FileInputStream(
      "src/test/resources/expected_clob");

    expectedDocument = getStringFromInputStream(expectedInputStream);
    insert = db.update(
      "insert into SERVERLOG(id,document) values(?,?)")
        .parameter(1)
        .parameter(Database.toSentinelIfNull(actualDocument))
      .dependsOn(create)
      .count();
}

@Test
public void whenSelectCLOB_thenCorrect() throws IOException {
    db.select("select document from SERVERLOG where id = 1")
      .dependsOn(create)
      .dependsOn(insert)
      .getAs(String.class)
      .toList()
      .toBlocking()
      .single();

    assertEquals(expectedDocument, actualDocument);
}

Beachten Sie, dassgetStringFromInputStream() eine Methode ist, die den Inhalt vonInputStream to a String. konvertiert

6.2. BLOBs

Mit der API können wir auf sehr ähnliche Weise mit BLOBs arbeiten. Der einzige Unterschied besteht darin, dass wir anstelle vonString an dietoSentinelIfNull()-Methode ein Byte-Array übergeben müssen.

So können wir das machen:

@Before
public void setup() throws IOException {
    create = db.update(
      "CREATE TABLE IF NOT EXISTS "
      + "SERVERLOG (id int primary key, document BLOB)")
        .count();

    InputStream actualInputStream
      = new FileInputStream("src/test/resources/actual_clob");
    actualDocument = getStringFromInputStream(actualInputStream);
    byte[] bytes = this.actualDocument.getBytes(StandardCharsets.UTF_8);

    InputStream expectedInputStream = new FileInputStream(
      "src/test/resources/expected_clob");
    expectedDocument = getStringFromInputStream(expectedInputStream);
    insert = db.update(
      "insert into SERVERLOG(id,document) values(?,?)")
      .parameter(1)
      .parameter(Database.toSentinelIfNull(bytes))
      .dependsOn(create)
      .count();
}

Dann können wir den gleichen Test im vorherigen Beispiel wiederverwenden.

7. Transaktionen

Schauen wir uns als nächstes die Unterstützung für Transaktionen an.

Das Transaktionsmanagement ermöglicht es uns, Transaktionen zu verarbeiten, mit denen mehrere Datenbankvorgänge in einer einzigen Transaktion zusammengefasst werden, sodass alle festgeschrieben werden können - dauerhaft in der Datenbank gespeichert oder vollständig zurückgesetzt.

Sehen wir uns ein kurzes Beispiel an:

@Test
public void whenCommitTransaction_thenRecordUpdated() {
    Observable begin = db.beginTransaction();
    Observable createStatement = db.update(
      "CREATE TABLE IF NOT EXISTS EMPLOYEE(id int primary key, name varchar(255))")
      .dependsOn(begin)
      .count();
    Observable insertStatement = db.update(
      "INSERT INTO EMPLOYEE(id, name) VALUES(1, 'John')")
      .dependsOn(createStatement)
      .count();
    Observable updateStatement = db.update(
      "UPDATE EMPLOYEE SET name = 'Tom' WHERE id = 1")
      .dependsOn(insertStatement)
      .count();
    Observable commit = db.commit(updateStatement);
    String name = db.select("select name from EMPLOYEE WHERE id = 1")
      .dependsOn(commit)
      .getAs(String.class)
      .toBlocking()
      .single();

    assertEquals("Tom", name);
}

Um eine Transaktion zu starten, rufen wir die MethodebeginTransaction() auf. Nach dem Aufruf dieser Methode wird jede Datenbankoperation in derselben Transaktion ausgeführt, bis eine der Methodencommit() oderrollback() aufgerufen wird.

Wir können dierollback()-Methode verwenden, während wir einException abfangen, um die gesamte Transaktion zurückzusetzen, falls der Code aus irgendeinem Grund fehlschlägt. Wir können dies für alleExceptions oder bestimmte erwarteteExceptions tun.

8. Generierte Schlüssel zurückgeben

Wenn wir das Feldauto_incrementin der Tabelle festlegen, an der wir arbeiten, müssen wir möglicherweise den generierten Wert abrufen. Wir können dies tun, indem wir die MethodereturnGeneratedKeys()aufrufen.

Sehen wir uns ein kurzes Beispiel an:

@Test
public void whenInsertAndReturnGeneratedKey_thenCorrect() {
    Integer key = db.update("INSERT INTO EMPLOYEE(name) VALUES('John')")
      .dependsOn(createStatement)
      .returnGeneratedKeys()
      .getAs(Integer.class)
      .count()
      .toBlocking()
      .single();

    assertThat(key).isEqualTo(1);
}

9. Fazit

In diesem Tutorial haben wir gesehen, wie Sie die fließenden Methoden von rxjavajdbc verwenden. Wir haben auch einige der darin enthaltenen Funktionen erläutert, z. B. das automatische Zuordnen, Arbeiten mit großen Objekten und Transaktionen.

Wie immer ist die Vollversion des Codesover on GitHub verfügbar.