Introduction à rxjava-jdbc

Introduction à rxjava-jdbc

1. Vue d'ensemble

En termes simples, rxjava-jdbc est une API pour l’interaction avec des bases de données relationnelles qui permet des appels de méthode de style fluide. Dans ce rapide didacticiel, nous allons examiner la bibliothèque et comment nous pouvons utiliser certaines de ses fonctionnalités communes.

Si vous souhaitez découvrir les bases de RxJava, consultezthis article.

Lectures complémentaires:

Introduction à RxJava

Discover RxJava - une bibliothèque pour composer des programmes asynchrones et basés sur des événements.

Read more

Faire face à la contre-pression avec RxJava

Un guide présentant plusieurs stratégies de gestion de la contre-pression dans RxJava

Read more

Observable Utility Operators dans RxJava

Apprenez à utiliser divers opérateurs d’utilitaire RxJava.

Read more

2. Dépendance Maven

Commençons par la dépendance Maven que nous devons ajouter à nospom.xml:


    com.github.davidmoten
    rxjava-jdbc
    0.7.11

Nous pouvons trouver la dernière version de l'API surMaven Central.

3. Composants principaux

The Database class is the main entry point for running all common types of database interactions. Pour créer un objetDatabase, nous pouvons passer une instance d'une implémentation de l'interfaceConnectionProvider à la méthode statique defrom():

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

ConnectionProvider a plusieurs implémentations qui méritent d'être examinées - telles queConnectionProviderFromContext,ConnectionProviderFromDataSource,ConnectionProviderFromUrl etConnectionProviderPooled.

Afin d'effectuer des opérations de base, nous pouvons utiliser les API suivantes deDatabase:

  • select() - utilisé pour les requêtes de sélection SQL

  • update() - utilisé pour les instructions DDL telles que créer et supprimer, ainsi que pour insérer, mettre à jour et supprimer

4. Démarrage

Dans l'exemple rapide suivant, nous allons montrer comment nous pouvons effectuer toutes les opérations de base sur la base de données:

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);
    }
}

Une note rapide ici - nous appelonsdependsOn() pour déterminer l'ordre d'exécution des requêtes.

Sinon, le code échouera ou produira des résultats imprévisibles, sauf si nous spécifions dans quel ordre nous voulons que les requêtes soient exécutées.

5. Automap

La fonctionnalité de cartographie automatique nous permet de mapper les enregistrements de base de données sélectionnés aux objets.

Jetons un coup d'œil aux deux méthodes d'automatisation des enregistrements de base de données.

5.1. Mappage automatique à l'aide d'une interface

Nous pouvonsautomap() les enregistrements de base de données vers des objets en utilisant des interfaces annotées. Pour ce faire, nous pouvons créer une interface annotée:

public interface Employee {

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

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

Ensuite, nous pouvons exécuter notre test:

@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. Mappage automatique à l'aide d'une classe

Nous pouvons également automatiser des enregistrements de base de données en objets à l'aide de classes concrètes. Voyons à quoi le cours peut ressembler:

public class Manager {

    private int id;
    private String name;

    // standard constructors, getters, and setters
}

Maintenant, nous pouvons exécuter notre test:

@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");
}

Quelques notes ici:

  • create,insert1 etinsert2 sont des références àObservables renvoyées en créant la tableManager et en y insérant des enregistrements

  • Le nombre de colonnes sélectionnées dans notre requête doit correspondre au nombre de paramètres dans le constructeur de classeManager

  • Les colonnes doivent être de types pouvant être automatiquement mappés aux types du constructeur.

Pour plus d'informations sur le mappage automatique, visitez lerxjava-jdbc repository sur GitHub

6. Travailler avec de grands objets

L'API prend en charge l'utilisation d'objets volumineux tels que CLOB et BLOBS. Dans les sous-sections suivantes, nous allons voir comment nous pouvons utiliser cette fonctionnalité.

6.1. CLOB

Voyons comment nous pouvons insérer et sélectionner un CLOB:

@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);
}

Notez quegetStringFromInputStream() est une méthode qui convertit le contenu d'unInputStream to a String.

6.2. BLOB

Nous pouvons utiliser l'API pour travailler avec des objets BLOB de manière très similaire. La seule différence est qu'au lieu de passer unString à la méthodetoSentinelIfNull(), nous devons passer un tableau d'octets.

Voici comment nous pouvons y parvenir:

@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();
}

Ensuite, nous pouvons réutiliser le même test dans l'exemple précédent.

7. Transactions

Voyons ensuite la prise en charge des transactions.

La gestion des transactions nous permet de gérer les transactions qui sont utilisées pour regrouper plusieurs opérations de base de données dans une seule transaction, de sorte qu'elles puissent toutes être validées - enregistrées de manière permanente dans la base de données ou totalement annulées.

Voyons un exemple rapide:

@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);
}

Afin de démarrer une transaction, nous appelons la méthodebeginTransaction(). Une fois cette méthode appelée, chaque opération de base de données est exécutée dans la même transaction jusqu'à ce que l'une des méthodescommit() ourollback() soit appelée.

Nous pouvons utiliser la méthoderollback() lors de la capture d'unException pour annuler toute la transaction au cas où le code échouerait pour une raison quelconque. Nous pouvons le faire pour tous lesExceptions ou certainsExceptions attendus.

8. Retour des clés générées

Si nous définissons le champauto_increment dans la table sur laquelle nous travaillons, nous devrons peut-être récupérer la valeur générée. Nous pouvons le faire en appelant la méthodereturnGeneratedKeys().

Voyons un exemple rapide:

@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. Conclusion

Dans ce didacticiel, nous avons vu comment utiliser les méthodes de style fluide de rxjavajdbc. Nous avons également décrit certaines des fonctionnalités qu'il propose, telles que le mappage automatique, l'utilisation de grands objets et les transactions.

Comme toujours, la version complète du code est disponibleover on GitHub.