Введение в rxjava-jdbc

Введение в rxjava-jdbc

1. обзор

Проще говоря, rxjava-jdbc - это API для взаимодействия с реляционными базами данных, который допускает вызовы методов в свободном стиле. В этом кратком руководстве мы рассмотрим библиотеку и то, как мы можем использовать некоторые из ее общих функций.

Если вы хотите познакомиться с основами RxJava, посмотритеthis article.

Дальнейшее чтение:

Введение в RxJava

Откройте для себя RxJava - библиотеку для составления асинхронных и событийных программ.

Read more

Работа с противодавлением с помощью RxJava

Руководство, демонстрирующее несколько стратегий обработки обратного давления в RxJava

Read more

Наблюдаемые служебные операторы в RxJava

Узнайте, как использовать различные операторы утилит RxJava.

Read more

2. Maven Dependency

Начнем с зависимости Maven, которую нам нужно добавить к нашемуpom.xml:


    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 имеет несколько реализаций, на которые стоит обратить внимание - например,ConnectionProviderFromContext,ConnectionProviderFromDataSource,ConnectionProviderFromUrl иConnectionProviderPooled.

Для выполнения основных операций мы можем использовать следующие APIDatabase:

  • 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. Автокарта

Функция Autop позволяет нам сопоставить выбранные записи базы данных с объектами.

Давайте рассмотрим два способа автоматического сопоставления записей базы данных.

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

Несколько заметок здесь:

  • create,insert1 иinsert2 - это ссылки наObservables, возвращаемые путем создания таблицыManager и вставки в нее записей

  • Количество выбранных столбцов в нашем запросе должно соответствовать количеству параметров в конструкторе классаManager

  • Столбцы должны иметь типы, которые могут быть автоматически сопоставлены с типами в конструкторе

Для получения дополнительной информации об автоматическом сопоставлении посетитеrxjava-jdbc repository на GitHub

6. Работа с большими объектами

API поддерживает работу с большими объектами, такими как CLOB и BLOBS. В следующих подразделах мы увидим, как можно использовать эту функцию.

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 очень похожим образом. Единственная разница в том, что вместо передачиString методуtoSentinelIfNull() мы должны передать массив байтов.

Вот как мы можем это сделать:

@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. операции

Теперь давайте посмотрим на поддержку транзакций.

Управление транзакциями позволяет нам обрабатывать транзакции, которые используются для группировки нескольких операций базы данных в одной транзакции, так что все они могут быть зафиксированы - навсегда сохранены в базе данных или полностью откатаны.

Давайте посмотрим на быстрый пример:

@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().

Мы можем использовать методrollback() при захватеException, чтобы откатить всю транзакцию в случае сбоя кода по какой-либо причине. Мы можем сделать это для всех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.