Перейти к основному содержимому

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

· 6 мин. чтения

1. Обзор

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

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

2. Зависимость от Maven

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

<dependency>
<groupId>com.github.davidmoten</groupId>
<artifactId>rxjava-jdbc</artifactId>
<version>0.7.11</version>
</dependency>

Мы можем найти последнюю версию API на Maven Central .

3. Основные компоненты

Класс базы данных является основной точкой входа для выполнения всех распространенных типов взаимодействия с базой данных. Чтобы создать объект базы данных , мы можем передать экземпляр реализации интерфейса ConnectionProvider в статический метод from() :

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

ConnectionProvider имеет несколько реализаций, на которые стоит обратить внимание, например ConnectionProviderFromContext , ConnectionProviderFromDataSource , ConnectionProviderFromUrl и ConnectionProviderPooled .

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

  • select() — используется для SQL-запросов на выборку
  • update() — используется для операторов DDL, таких как создание и удаление, а также вставка, обновление и удаление.

4. Запуск

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

public class BasicQueryTypesTest {

Observable<Integer> 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<String> 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);
}
}

Небольшое примечание: мы вызываем dependOn() , чтобы определить порядок выполнения запросов.

В противном случае код завершится ошибкой или выдаст непредсказуемые результаты, если только мы не укажем, в какой последовательности должны выполняться запросы.

5. Автокарта

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

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

5.1. Автоматическое сопоставление с использованием интерфейса

Мы можем автоматизировать() записи базы данных для объектов, используя аннотированные интерфейсы. Для этого мы можем создать аннотированный интерфейс:

public interface Employee {

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

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

Затем мы можем запустить наш тест:

@Test
public void whenSelectFromTableAndAutomap_thenCorrect() {
List<Employee> 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<Manager> 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 на 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 в строку.

6.2. BLOB-объекты

Мы можем использовать API для работы с BLOB очень похожим образом. Единственное отличие состоит в том, что вместо передачи строки методу 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<Boolean> begin = db.beginTransaction();
Observable<Integer> createStatement = db.update(
"CREATE TABLE IF NOT EXISTS EMPLOYEE(id int primary key, name varchar(255))")
.dependsOn(begin)
.count();
Observable<Integer> insertStatement = db.update(
"INSERT INTO EMPLOYEE(id, name) VALUES(1, 'John')")
.dependsOn(createStatement)
.count();
Observable<Integer> updateStatement = db.update(
"UPDATE EMPLOYEE SET name = 'Tom' WHERE id = 1")
.dependsOn(insertStatement)
.count();
Observable<Boolean> 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() при перехвате исключения , чтобы откатить всю транзакцию в случае сбоя кода по какой-либо причине. Мы можем сделать это для всех исключений или определенных ожидаемых исключений .

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. Заключение

В этом руководстве мы увидели, как использовать rxjava методы свободного стиля jdbc. Мы также рассмотрели некоторые функции, которые он предоставляет, такие как автоматическое сопоставление, работа с большими объектами и транзакциями.

Как всегда, полная версия кода доступна на GitHub .