rxjava-jdbcの紹介

rxjava-jdbcの概要

1. 概要

簡単に言えば、rxjava-jdbcは、流databasesなスタイルのメソッド呼び出しを可能にするリレーショナルデータベースと対話するためのAPIです。 この簡単なチュートリアルでは、ライブラリと、その一般的な機能のいくつかを利用する方法について説明します。

RxJavaの基本を知りたい場合は、this articleを確認してください。

参考文献:

RxJavaの概要

RxJava-非同期およびイベントベースのプログラムを作成するためのライブラリをご覧ください。

RxJavaを使用したバックプレッシャーへの対処

RxJavaで背圧を処理するいくつかの戦略を示すガイド

RxJavaの観察可能なユーティリティオペレーター

さまざまなRxJavaユーティリティ演算子の使用方法を学びます。

2. メーベン依存

pom.xmlに追加する必要のあるMaven依存関係から始めましょう。


    com.github.davidmoten
    rxjava-jdbc
    0.7.11

APIの最新バージョンはMaven Centralにあります。

3. メインコンポーネント

The Database class is the main entry point for running all common types of database interactions.Databaseオブジェクトを作成するために、ConnectionProviderインターフェースの実装のインスタンスをfrom()静的メソッドに渡すことができます。

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

ConnectionProviderには、ConnectionProviderFromContextConnectionProviderFromDataSourceConnectionProviderFromUrlConnectionProviderPooledなど、一見の価値のあるいくつかの実装があります。

基本的な操作を行うために、Databaseの次のAPIを使用できます。

  • select() –SQL選択クエリに使用

  • update() –作成、削除、挿入、更新、削除などのDDLステートメントに使用されます

4. 起動

次の簡単な例では、すべての基本的なデータベース操作を実行する方法を示します。

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

ここで簡単に説明します。クエリの実行順序を決定するためにdependsOn()を呼び出しています。

そうしないと、クエリを実行する順序を指定しない限り、コードが失敗するか、予測できない結果が生成されます。

5. 自動マップ

自動マップ機能により、選択したデータベースレコードをオブジェクトにマップできます。

データベースレコードを自動マッピングする2つの方法を見てみましょう。

5.1. インターフェイスを使用した自動マッピング

注釈付きインターフェースを使用して、データベースレコードをオブジェクトにautomap()することができます。 これを行うには、注釈付きインターフェイスを作成できます。

public interface Employee {

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

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

次に、テストを実行できます。

@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. クラスを使用した自動マッピング

具象クラスを使用して、データベースレコードをオブジェクトに自動マッピングすることもできます。 クラスがどのように見えるか見てみましょう:

public class Manager {

    private int id;
    private String name;

    // standard constructors, getters, and setters
}

これで、テストを実行できます。

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

ここにいくつかのメモ:

  • createinsert1、およびinsert2は、Managerテーブルを作成し、それにレコードを挿入することによって返されるObservablesへの参照です。

  • クエリで選択した列の数は、Managerクラスコンストラクターのパラメーターの数と一致する必要があります

  • 列は、コンストラクターの型に自動的にマップできる型である必要があります

自動マッピングの詳細については、GitHubのrxjava-jdbc repositoryにアクセスしてください。

6. 大きなオブジェクトの操作

APIは、CLOBやBLOBなどのラージオブジェクトの操作をサポートしています。 次のサブセクションでは、この機能をどのように利用できるかを見ていきます。

6.1. CLOB

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

getStringFromInputStream()は、InputStream to a String.のコンテンツを変換するメソッドであることに注意してください

6.2. BLOB

APIを使用して、非常によく似た方法でBLOBを操作できます。 唯一の違いは、StringtoSentinelIfNull()メソッドに渡す代わりに、バイト配列を渡す必要があることです。

これを行う方法は次のとおりです。

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

その後、前の例で同じテストを再利用できます。

7. トランザクション

次に、トランザクションのサポートを見てみましょう。

トランザクション管理により、複数のデータベース操作を1つのトランザクションにグループ化するために使用されるトランザクションを処理できるため、データベースに永続的に保存するか、完全にロールバックできます。

簡単な例を見てみましょう:

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

トランザクションを開始するために、メソッドbeginTransaction()を呼び出します。 このメソッドが呼び出された後、メソッドcommit()またはrollback()のいずれかが呼び出されるまで、すべてのデータベース操作が同じトランザクションで実行されます。

Exceptionをキャッチしながら、rollback()メソッドを使用して、何らかの理由でコードが失敗した場合にトランザクション全体をロールバックできます。 すべてのExceptionsまたは特定の予想されるExceptionsに対してこれを行うことができます。

8. 生成されたキーを返す

作業中のテーブルにauto_incrementフィールドを設定すると、生成された値を取得する必要がある場合があります。 これは、returnGeneratedKeys()メソッドを呼び出すことで実行できます。

簡単な例を見てみましょう:

@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. 結論

このチュートリアルでは、rxjavajdbcの流暢なスタイルのメソッドを利用する方法を見てきました。 また、自動マッピング、ラージオブジェクトの操作、トランザクションなど、提供される機能のいくつかについても説明しました。

いつものように、コードのフルバージョンはover on GitHubで利用できます。